Hedronite · Curriculum · UIUX · Ops · Sat 2026-11-07

The operator's UI-status policy A banner contract and a would-it-be-green report

Refuse a policy that would let the banner lie, then ask of every derived contract whether the banner would be green, and name the fields that disagree.

Lesson Class: Curriculum · O-UIUX · rung A
Tested with: Nickel 1.18.0 · Nushell 0.116.1 · jq 1.7.1
Lab: lab/ · sh check.sh (11 checks) · no network
Source: Nickel manual: Contracts · Nushell command reference · jq 1.7 Manual · WCAG 2.2 SC 4.1.3 · MDN aria-live · DOP-C02 task 4.2
Refuse the Policy First
A policy that lets the button act while unhealthy, drops drift_failed from the assertive set, or invents a green state is refused before any page binds it.
Ask If It Would Be Green
The report answers one question per contract and exits 0 only when the answer is yes.
Hide Nothing
A contract that derived healthy over 4 unreadable lines of 7 is not green under a policy that allows none.
The contract refuses a lying policy. The report refuses a lying banner.

O-UIUX · A · arc: self-healing-edge · langs: nickel, nushell · DOP-4.2, CKA-TROUBLE-2

By Evan Eugene Pincham.

Concept

A status banner is the strip at the top of an operations page that tells a person whether a service is fine. A UI status contract is the small, typed record a page (or a screen reader) reads to decide what that banner says, whether a button may act, and how loudly to announce a change. The companion lesson, "A UI status contract that never paints healthy after gave_up, in Rust", built a tool called uistatus. Its derive command reads an operator log (a JSON Lines file, one JSON object per line, each with at least an event key) and prints a derive output: eight key: value lines, named state, banner, headline, can_act, aria_live, last_event, lines_read and lines_unreadable.

Some terms from that output, defined once here:

The companion contract has one gap. It derives state from the last readable event only. A log of 7 lines in which 4 lines are unreadable and the last readable event is completed derives state: healthy, even though more than half of the log was never understood. The count is honest, but the banner is green.

This lesson is the operator's side of the same contract, in two pieces.

  1. A Nickel contract. Nickel is a configuration language in which a contract is a check attached to a value with |. If the value fails the check, evaluation stops with an error. The Nickel user manual says record contracts "encode the data schema of a configuration", and a validator built with std.contract.from_validator returns 'Ok or 'Error { message = ... }. Here the contract describes the operator's UI-status policy, and nickel export refuses a policy that would let the banner lie before any page binds it.
  2. A Nushell report. Nushell is a shell whose pipelines carry structured data (tables, records, lists) instead of plain text. The report reads one derive output plus the exported policy. It answers "would the banner be green?" and "how many unreadable?". It also names every field that contradicts the policy. Each such named contradiction is a violation. In this lesson a violation is not the same thing as "not green": a log that ends in resumed is not green and has no violation, because the contract is truthful about being unhealthy. A violation means the contract itself disagrees with the policy.

The report's exit code is the useful part. It exits 0 when the banner would be green and nothing disagrees, 1 when it would not be green or a field contradicts the policy, and 2 when a file cannot be opened. A promote button or a page build calls that exit code as its gate.

Honesty line: this is a policy check over text fixtures. No browser runs, no screen reader announces anything, and no cloud dashboard is involved. What is real is the policy contract, the field comparisons, the unreadable-line rule and the exit codes. The page that would bind the contract is not run.

The lesson maps to AWS DOP-C02 Task Statement 4.2, which covers auditing, monitoring and analyzing logs and metrics to detect issues, including building dashboards. Here the contract is a local stand-in for a dashboard panel that must not greenwash a failed log. It also maps to the Kubernetes CKA curriculum's Troubleshooting item "Manage and evaluate container output streams", because the state comes from a log stream and the unreadable lines stay visible.

Worked example

Work in one directory with these files: ui-policy.ncl, policy.ncl, ui-report.nu, check.sh, and a fixtures/ directory.

The contract

# ui-policy.ncl: the operator's rules for the status banner, as a contract.
# `nickel export` refuses a policy that would let the banner lie, before any page binds it.
let Known = ["healthy", "recovering", "degraded", "gave_up"] in
let Terminal = ["gave_up", "job_failed", "drift_failed", "rolled_back"] in
let States = std.contract.from_validator (fun xs =>
  if std.is_array xs && std.array.length xs >= 1
     && std.array.all (fun s => std.is_string s && std.array.elem s Known) xs then 'Ok
  else 'Error { message = "allowed_states must list only healthy, recovering, degraded, gave_up" })
in
let Assertive = std.contract.from_validator (fun xs =>
  if std.is_array xs && std.array.all (fun t => std.array.elem t xs) Terminal then 'Ok
  else 'Error { message = "assertive_on must include gave_up, job_failed, drift_failed and rolled_back" })
in
let MustRefuse = std.contract.from_validator (fun b =>
  if b == true then 'Ok
  else 'Error { message = "refuse_act_unless_healthy must be true" })
in
let MaxUnreadable = std.contract.from_validator (fun n =>
  if std.is_number n && std.number.is_integer n && n >= 0 then 'Ok
  else 'Error { message = "max_unreadable must be a whole number of at least 0" })
in
{
  UiPolicy = {
    allowed_states | States,
    # Events that must interrupt a screen reader. Every terminal failure belongs here.
    assertive_on | Assertive,
    refuse_act_unless_healthy | MustRefuse,
    # How many unreadable log lines a green banner may hide. Today: none.
    max_unreadable | MaxUnreadable,
  },
}

Reading it from the top:

The policy itself is a separate file that imports the contract and applies it.

# policy.ncl: today's UI-status policy for the status banner.
let { UiPolicy, .. } = import "ui-policy.ncl" in
{
  allowed_states = ["healthy", "recovering", "degraded", "gave_up"],
  assertive_on = ["gave_up", "job_failed", "drift_failed", "rolled_back"],
  refuse_act_unless_healthy = true,
  max_unreadable = 0,
} | UiPolicy

let { UiPolicy, .. } = import ... in pulls the one field out of the imported record. The trailing | UiPolicy applies the contract to the record literal. nickel export policy.ncl then prints the policy as JSON only if every validator said 'Ok. The checker runs that export and saves the JSON as policy.json, which is what the Nushell report reads.

The checker also tries three damaged copies of the policy, made by editing one line with sed. This is how the contract is shown to refuse bad input. Here is the first part of a finished run, from sh check.sh:

policy accepted: {"allowed_states":["healthy","recovering","degraded","gave_up"],"assertive_on":["gave_up","job_failed","drift_failed","rolled_back"],"max_unreadable":0,"refuse_act_unless_healthy":true}
RESULT policy_accepted PASS
policy act_anyway: refused: refuse_act_unless_healthy must be true
RESULT rejects_act_while_unhealthy PASS
policy quiet_drift: refused: assertive_on must include gave_up, job_failed, drift_failed and rolled_back
RESULT rejects_quiet_drift_failed PASS
policy green_state: refused: allowed_states must list only healthy, recovering, degraded, gave_up
RESULT rejects_unknown_state PASS

The three refusals exercise three of the four rules in the contract (the checker does not probe max_unreadable): a policy that lets the button act while unhealthy, one that drops drift_failed from the assertive set, and one that invents a "green" state.

The fixtures

The report reads derive outputs. The damaged one is the case the companion tool printed as healthy:

state: healthy
banner: ok
headline: Service reports ready (last event: completed).
can_act: true
aria_live: off
last_event: completed
lines_read: 7
lines_unreadable: 4

The clean success output comes from a log that ends in promoted:

state: healthy
banner: ok
headline: Service reports ready (last event: promoted).
can_act: true
aria_live: off
last_event: promoted
lines_read: 2
lines_unreadable: 0

The third fixture is a hand-edited contract. It is the success output with one line changed, so that the fields claim green while last_event says the drift check failed.

state: healthy
banner: ok
headline: Service reports ready (last event: promoted).
can_act: true
aria_live: off
last_event: drift_failed
lines_read: 2
lines_unreadable: 0

The checker also reads fixtures/gaveup.out, fixtures/drift.out and fixtures/recovering.out. These are plain uistatus derive outputs from the companion lesson, for logs whose last readable event is gave_up, drift_failed and resumed. Following the companion lesson's mapping table, they carry state: gave_up, can_act: false and aria_live: assertive for the first two, and state: recovering, can_act: false and aria_live: polite for the third. The report reads only the fields named above, so any derive output with those fields works.

The report

# ui-report.nu: would the banner be green, and does the contract agree with itself?
# Reads one `uistatus derive` output (key: value lines) and the exported policy JSON.
# Usage: nu ui-report.nu <derive.out> --policy <policy.json from nickel export>
# Exit 0 when the banner would be green and nothing disagrees, 1 when it would not be green
# or a field contradicts the policy, 2 when a file cannot be opened.

def main [contract: path, --policy: path] {
  if not ($contract | path exists) {
    print -e $"ui-report.nu: cannot open ($contract)"
    exit 2
  }
  if ($policy == null) or (not ($policy | path exists)) {
    print -e "ui-report.nu: --policy <policy.json> is required"
    exit 2
  }
  let pol = (open $policy)
  let c = (open --raw $contract | lines
    | parse -r '^(?<key>[a-z_]+): (?<value>.*)$'
    | reduce -f {} {|it, acc| $acc | upsert $it.key $it.value })
  let state = ($c.state? | default 'missing')
  let banner = ($c.banner? | default 'missing')
  let can_act = ($c.can_act? | default 'missing')
  let aria = ($c.aria_live? | default 'missing')
  let last = ($c.last_event? | default 'none')
  let total = ($c.lines_read? | default '0' | into int)
  let unread = ($c.lines_unreadable? | default '0' | into int)
  let terminal = ($last in $pol.assertive_on)

  mut v = []
  if not ($state in $pol.allowed_states) { $v = ($v | append $"state ($state) is not an allowed state") }
  if $terminal and ($state == 'healthy') { $v = ($v | append $"state healthy but last_event ($last)") }
  if $terminal and ($aria != 'assertive') { $v = ($v | append $"aria_live ($aria) but last_event ($last) must be assertive") }
  if $pol.refuse_act_unless_healthy and ($can_act == 'true') and ($state != 'healthy') { $v = ($v | append $"can_act true while state ($state)") }
  if ($state == 'healthy') and ($banner != 'ok') { $v = ($v | append $"state healthy but banner ($banner)") }

  let green_fields = ($state == 'healthy') and ($banner == 'ok') and ($can_act == 'true')
  let reason = (if ($v | is-not-empty) { 'contract disagrees with policy'
    } else if not $green_fields { $"state ($state)"
    } else if $unread > $pol.max_unreadable { $"($unread) unreadable lines, policy allows ($pol.max_unreadable)"
    } else { 'healthy, nothing hidden' })
  let green = $green_fields and ($v | is-empty) and ($unread <= $pol.max_unreadable)

  print $"ui-report: ($contract | path basename)"
  print $"state: ($state)"
  print $"aria_live: ($aria)"
  print $"last_event: ($last)"
  print $"unreadable: ($unread) of ($total)"
  print $"violations: ($v | length)"
  for line in $v { print $"  ($line)" }
  print $"green: (if $green { 'yes' } else { 'no' })"
  print $"reason: ($reason)"
  if not $green { exit 1 }
}

How it works, in the order it runs:

  1. def main [contract: path, --policy: path] makes the script take one positional file and a --policy flag. A missing contract file, or a missing or absent policy file, prints to standard error and exits 2.
  2. open $policy reads the exported policy. As the run below shows, a .json file comes back as a structured record, so $pol.assertive_on is a real list. (from json does the same for JSON text already in hand.)
  3. open --raw $contract returns the file as text. lines splits it. parse -r with named capture groups turns each key: value line into a row with key and value columns. reduce -f {} folds those rows into one record by upserting each key. The starting value is {}.
  4. Every value from the file is a string. That is why the comparisons are $can_act == 'true' and why the two counts go through into int. $c.state? | default 'missing' reads a field that may be absent without crashing.
  5. terminal is true when last_event is in the policy's assertive_on list. The policy file therefore supplies the terminal set, and the Nickel contract guarantees that set contains all four events.
  6. Five rules append to the mutable list v: a state outside allowed_states, healthy after a terminal event, an aria_live quieter than assertive on a terminal event, can_act true while unhealthy, and healthy with a banner that is not ok.
  7. A banner is green only if the fields say green, there are no violations, and unread does not exceed max_unreadable. The reason line gives the first applicable cause, in that order: contradictions, then non-green fields, then unreadable lines.
  8. The script prints the report and calls exit 1 when not green. Falling off the end exits 0.

On the damaged log the fields all say green, so only the unreadable rule can catch it. This is the real report from the finished lab:

--- damaged.rep
ui-report: damaged.out
state: healthy
aria_live: off
last_event: completed
unreadable: 4 of 7
violations: 0
green: no
reason: 4 unreadable lines, policy allows 0

violations: 0 and green: no together are the point. The contract is not lying, but a green banner would hide 4 unreadable lines out of 7, and the policy allows 0.

On the forged contract, the fields look like a success but last_event says drift_failed:

--- forged.rep
ui-report: forged.out
state: healthy
aria_live: off
last_event: drift_failed
unreadable: 0 of 2
violations: 2
  state healthy but last_event drift_failed
  aria_live off but last_event drift_failed must be assertive
green: no
reason: contract disagrees with policy

Two fields are named, not painted: the state and the quiet aria_live. can_act is not named for the forged file because it is true while state is healthy, which is internally consistent.

The remaining checker lines show the exit-code behavior on every fixture:

success: exit=0 green=yes violations=0
RESULT success_would_be_green PASS
gaveup: exit=1 green=no state=gave_up reason=state gave_up
RESULT gaveup_not_green PASS
drift: exit=1 green=no last_event=drift_failed
RESULT drift_not_green PASS
recovering: exit=1 green=no violations=0 aria_live=polite
RESULT recovering_not_green PASS
damaged: exit=1 green=no unreadable=4 of 7 reason=4 unreadable lines, policy allows 0
RESULT damaged_hides_nothing PASS
forged: exit=1 green=no violations=2
RESULT forged_green_named PASS
missing contract: exit=2
RESULT missing_contract_exit_2 PASS

recovering is not green, yet it has no violation and aria_live is polite. That is correct: the contract is truthful about not being healthy yet. A page build that called the report would stop on exit 1 for every case except success.

The checker

The checker is plain POSIX sh. It makes the probe policies with sed, runs nickel export on each, and runs the report on each fixture. It reads the report's key: value lines back with a small field helper, so a finished lab is judged on what the report prints and on its exit code.

Lab

You write two files. The directory already holds check.sh, policy.ncl, and the fixtures/ directory (the last check depends on there being no fixtures/no-such.out). Replace the starters below until sh check.sh is green.

Starter ui-policy.ncl:

# ui-policy.ncl (starter): the operator's rules for the status banner, as a contract.
# TODO: allowed_states may list only healthy, recovering, degraded, gave_up.
# TODO: assertive_on must include gave_up, job_failed, drift_failed and rolled_back.
# TODO: refuse_act_unless_healthy must be true.
{
  UiPolicy = {
    allowed_states | Array String,
    assertive_on | Array String,
    refuse_act_unless_healthy | Bool,
    max_unreadable | Number,
    ..
  },
}

The starter checks only types. Array String accepts any list of strings, Bool accepts false as readily as true, and Number accepts any number. The trailing .. also makes the record contract open, so unlisted fields would be tolerated. All three probe policies pass, which is the bug.

Starter ui-report.nu:

# ui-report.nu (starter): would the banner be green?
# Usage: nu ui-report.nu <derive.out> --policy <policy.json from nickel export>
# TODO: name fields that contradict the policy (healthy after a terminal event, quiet aria_live, can_act while unhealthy).
# TODO: a green banner may not hide more unreadable lines than max_unreadable.
# TODO: exit 1 when not green.

def main [contract: path, --policy: path] {
  if not ($contract | path exists) { exit 2 }
  let c = (open --raw $contract | lines
    | parse -r '^(?<key>[a-z_]+): (?<value>.*)$'
    | reduce -f {} {|it, acc| $acc | upsert $it.key $it.value })
  print $"ui-report: ($contract | path basename)"
  print $"state: ($c.state)"
  print $"aria_live: ($c.aria_live)"
  print $"last_event: ($c.last_event)"
  print $"unreadable: ($c.lines_unreadable) of ($c.lines_read)"
  print "violations: 0"
  print $"green: (if $c.state == 'healthy' { 'yes' } else { 'no' })"
}

The starter says green whenever state is healthy, prints violations: 0 always, never looks at the unreadable count, never reads the policy, and never exits 1.

Task

  1. In ui-policy.ncl, replace the three type-only contracts with validators so that nickel export refuses a policy whose allowed_states lists anything outside healthy, recovering, degraded, gave_up, whose assertive_on omits any terminal failure event, or whose refuse_act_unless_healthy is not true. Make max_unreadable accept only a whole number of at least 0. Use the exact refusal messages that appear in the output above, because the checker prints them.
  2. In ui-report.nu, read the policy, name each contradicting field as one indented line under violations:, make a green banner depend on max_unreadable, add a reason: line, and exit 1 when the banner is not green. Keep exit 2 for a missing contract file.
  3. Run the checker beside the files (it needs nickel 1.18 and nu 0.116, and no network):
sh check.sh

On the unmodified starter, 8 of the 11 checks fail. The three that pass are policy_accepted, success_would_be_green and missing_contract_exit_2. Piping the starter run through grep '^RESULT' shows the real result lines:

RESULT policy_accepted PASS
RESULT rejects_act_while_unhealthy FAIL
RESULT rejects_quiet_drift_failed FAIL
RESULT rejects_unknown_state FAIL
RESULT success_would_be_green PASS
RESULT gaveup_not_green FAIL
RESULT drift_not_green FAIL
RESULT recovering_not_green FAIL
RESULT damaged_hides_nothing FAIL
RESULT forged_green_named FAIL
RESULT missing_contract_exit_2 PASS

A finished lab prints 11 checks, all PASS:

RESULT policy_accepted PASS
RESULT rejects_act_while_unhealthy PASS
RESULT rejects_quiet_drift_failed PASS
RESULT rejects_unknown_state PASS
RESULT success_would_be_green PASS
RESULT gaveup_not_green PASS
RESULT drift_not_green PASS
RESULT recovering_not_green PASS
RESULT damaged_hides_nothing PASS
RESULT forged_green_named PASS
RESULT missing_contract_exit_2 PASS

Pitfalls to watch for

Other paths

The same "would it be green?" question can be asked in jq 1.7. The jq manual describes --slurp/-s as reading "the entire input stream into a large array" and running the filter once. Combined with -R (raw input), the whole derive output arrives as one string. The script splits it on newlines, picks the key: value lines, captures the two parts, and builds an object with from_entries.

#!/bin/sh
# jq-report.sh: the same "would it be green?" answer in jq 1.7, for comparison.
# Reads one derive output as raw lines. Policy rules are written into the program, not read.
jq -R -s -r '
  [split("\n")[] | select(test("^[a-z_]+: ")) | capture("^(?<key>[a-z_]+): (?<value>.*)$")]
  | from_entries as $c
  | (["gave_up","job_failed","drift_failed","rolled_back"] | index($c.last_event) != null) as $term
  | ([ (if $term and $c.state == "healthy" then "state healthy but last_event \($c.last_event)" else empty end),
       (if $c.can_act == "true" and $c.state != "healthy" then "can_act true while state \($c.state)" else empty end) ]) as $v
  | "unreadable: \($c.lines_unreadable) of \($c.lines_read)",
    "violations: \($v | length)",
    "green: \(if $c.state == "healthy" and $c.can_act == "true" and ($v | length) == 0 and ($c.lines_unreadable == "0") then "yes" else "no" end)"' "$1"

Three differences from the Nushell report:

Real runs on three fixtures (jq 1.7.1):

$ sh jq-report.sh fixtures/success.out
unreadable: 0 of 2
violations: 0
green: yes
exit=0
$ sh jq-report.sh fixtures/damaged.out
unreadable: 4 of 7
violations: 0
green: no
exit=0
$ sh jq-report.sh fixtures/forged.out
unreadable: 0 of 2
violations: 1
green: no
exit=0

On forged.out jq counts 1 violation where Nushell counts 2, because it has no aria_live rule. Both say green: no. The disagreement is a reason to keep rules in one policy file rather than copy them into each tool. The exit=0 on damaged.out and forged.out is the third difference above in action.

A page template that binds the contract (a banner element with role="status" or role="alert", an aria-live attribute copied from the contract, a promote button disabled when can_act is false) is not run in this lesson. The W3C material cited below describes that binding, but nothing here executes it.

Citations