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.
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:
- Terminal failure event: a log event after which the service has stopped retrying or has reverted. In this lesson the set is
gave_up,job_failed,drift_failedandrolled_back. - **
can_act**:trueonly when a button such as "promote" may enable itself. It should betrueonly in thehealthystate. - **
aria-live: an HTML attribute (an ARIA attribute) that marks a region of a page as a live region**, meaning a region whose changes assistive technology announces without the user moving focus there. MDN describes three values.offdoes not announce unless the region is focused.politenotifies without interrupting.assertivepresents immediately and may clear the speech queue, and MDN adds "don't use theassertivevalue unless the interruption is imperative." A failure that has ended the service's attempts to recover is that kind of interruption. - Unreadable line: a physical line of the log that is blank, not JSON, truncated, wrong in shape, or not UTF-8.
uistatuscounts it inlines_unreadableinstead of dropping it.
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.
- 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 withstd.contract.from_validatorreturns'Okor'Error { message = ... }. Here the contract describes the operator's UI-status policy, andnickel exportrefuses a policy that would let the banner lie before any page binds it. - 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
resumedis 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:
let Known = [...] inbinds a name for use in the rest of the file.Knownis the only vocabulary of states, andTerminalis the set of terminal failure events.- Each validator is a function from the value to
'Okor'Error { message = ... }.Statesaccepts a non-empty array whose members are all strings found inKnown. A made-up state such as"green"fails that test. Assertivechecks the opposite direction. Every member ofTerminalmust be present in the list, so a policy may add events but may not drop one.MustRefuseaccepts onlytrue. A policy cannot switch off "refuse to act unless healthy".MaxUnreadablerequires a whole number of at least 0.- The file returns a record whose one field,
UiPolicy, is the record contract: four field names, each with| <validator>.
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:
def main [contract: path, --policy: path]makes the script take one positional file and a--policyflag. A missing contract file, or a missing or absent policy file, prints to standard error and exits 2.open $policyreads the exported policy. As the run below shows, a.jsonfile comes back as a structured record, so$pol.assertive_onis a real list. (from jsondoes the same for JSON text already in hand.)open --raw $contractreturns the file as text.linessplits it.parse -rwith named capture groups turns eachkey: valueline into a row withkeyandvaluecolumns.reduce -f {}folds those rows into one record by upserting each key. The starting value is{}.- Every value from the file is a string. That is why the comparisons are
$can_act == 'true'and why the two counts go throughinto int.$c.state? | default 'missing'reads a field that may be absent without crashing. terminalis true whenlast_eventis in the policy'sassertive_onlist. The policy file therefore supplies the terminal set, and the Nickel contract guarantees that set contains all four events.- Five rules append to the mutable list
v: a state outsideallowed_states,healthyafter a terminal event, anaria_livequieter thanassertiveon a terminal event,can_acttrue while unhealthy, andhealthywith a banner that is notok. - A banner is green only if the fields say green, there are no violations, and
unreaddoes not exceedmax_unreadable. Thereasonline gives the first applicable cause, in that order: contradictions, then non-green fields, then unreadable lines. - The script prints the report and calls
exit 1when 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
- In
ui-policy.ncl, replace the three type-only contracts with validators so thatnickel exportrefuses a policy whoseallowed_stateslists anything outsidehealthy,recovering,degraded,gave_up, whoseassertive_onomits any terminal failure event, or whoserefuse_act_unless_healthyis nottrue. Makemax_unreadableaccept only a whole number of at least 0. Use the exact refusal messages that appear in the output above, because the checker prints them. - In
ui-report.nu, read the policy, name each contradicting field as one indented line underviolations:, make a green banner depend onmax_unreadable, add areason:line, and exit 1 when the banner is not green. Keep exit 2 for a missing contract file. - 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
- Symptom:
nickel exportprints a policy withrefuse_act_unless_healthy = false, or one that lists"green", and exits 0. Cause: the contract checks only types.Boolacceptsfalse, andArray Stringaccepts any string. A validator has to inspect the value, not only its type. - Symptom: the report prints
green: yesfor the damaged log withunreadable: 4 of 7. Cause: green is computed fromstatealone. The derive output can sayhealthywhile hiding unreadable lines, so the report must comparelines_unreadablewithmax_unreadable. - Symptom: the forged contract shows
violations: 0. Cause: the report never compares the fields with the policy, and the starter hard-codes the count. - Symptom: a build step that runs the report goes on after a failure. Cause: the report never calls
exit 1, so every run exits 0 and a wrapper cannot tell green from not green. - Symptom: a comparison such as
$unread > $pol.max_unreadablefails or compares text. Cause: every value parsed from the derive output is a string. Convert counts withinto int, and compare booleans against the string'true'.
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:
- The rules are written into the program, so there is no
policy.jsonto read. Changingmax_unreadablemeans editing the script, and the Nickel contract no longer guards the policy. - There is no
aria_liverule, so a quietaria_liveon a terminal event goes unnamed. - The exit code is always 0 here. The jq manual says jq "exits with 2 if there was any usage problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran" (that sentence sits under its
--exit-statusoption). The verdict is in the printedgreen:line, so a wrapper has to read that line and make its own exit code.
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
- Nickel User Manual, "Contracts", sections "Records" and "User-defined contracts" (record contracts, trailing
..for an open record contract,std.contract.from_validatorreturning'Okor'Error { message = ... }). The behaviour ofnickel exportand the refusal messages were observed in Nickel 1.18.0 in the lab run. - Nushell command reference,
from jsonandopen(https://www.nushell.sh/commands/docs/open.html ), includingopen --raw. - Nushell command reference,
parse(https://www.nushell.sh/commands/docs/parse.html ) with--regex/-rnamed capture groups, andreduce(https://www.nushell.sh/commands/docs/reduce.html ) with--fold/-f. Observed in Nushell 0.116 in the lab run. - jq 1.7 Manual, "Invoking jq":
--slurp/-s,--raw-output/-r, and the--exit-statuspassage on exit codes 2, 3 and 0. - W3C WAI, Understanding SC 4.1.3 Status Messages, WCAG 2.2 (https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html ): the definition of a status message and Situation B for warnings and errors.
- W3C WAI-ARIA Authoring Practices, Alert Pattern (https://www.w3.org/WAI/ARIA/apg/patterns/alert/ ).
- MDN Web Docs,
aria-live(https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-live ): theoff,politeandassertivevalues and the advice onassertive. - AWS Certified DevOps Engineer - Professional (DOP-C02) Exam Guide, version 1.6, Domain 4, Task Statement 4.2, "Audit, monitor, and analyze logs and metrics to detect issues".
- Certified Kubernetes Administrator (CKA) Curriculum, version 1.37, Troubleshooting, "Manage and evaluate container output streams".
- Beyer, Jones, Petoff and Murphy (eds.), Site Reliability Engineering, O'Reilly, 2016, chapter "Monitoring Distributed Systems" (a banner is a symptom surface, not a cause).