#!/usr/bin/env nu # scripts/witness.nu — dumb executor for standing laws (sow/laws.ncl). # # Verifies laws.ncl's signature before running anything (no witness, no start), # runs every Active law's `check` command against the current catalog state, and # appends one JSONL receipt line per law. Prints the exact command to sign the # resulting receipt — it does NOT sign it. # # Signing the receipt is a deliberate, separate, human-executed step, never # performed by this script. Confirmed 2026-07-03 while wiring T5: minisign has no # password environment variable BY DESIGN (`minisign -h`) — the reference # implementation refuses to let a passphrase-protected key be used # non-interactively, specifically to prevent scripting/automation from handling it. # A witness.nu that tried to call `minisign -S` internally could therefore never # succeed against a real, passphrase-protected key; it would either hang forever # waiting for interactive input (observed) or require weakening the key with `-W` # (no password), which defeats the point. The correct split, matching J3's own # precedent: this script produces content; the human signs it, in their own shell, # every time — the same boundary as `laws.ncl`'s ratification, now applied to # receipts too. # # Whole-file signing, not per-line: minisign signs files, and a per-line `sig` # field would need to reference a signature computed over the line's own bytes # minus that same field — the exact self-referential trap found and reverted in # laws-schema.ncl's `signature` field (see # provisioning/.coder/2026-07-03_laws-catalogo-provisioning.plan.md §3, §9 J3). # # Usage: # nu scripts/witness.nu [laws_path] [receipt_path] --wo [--pubkey path] # # Exit 0: every Active law's check passed (or none are Active). Exit 1: a guard # failed (missing/invalid signature) or at least one Active law's check failed. # Exit code reflects the LAW VERDICT only — receipt signing happens after, by the # human, regardless of this script's exit code (a Rejected verdict is still signed, # so the rejection itself is witnessed). def main [ laws_path: string = "sow/laws.ncl" receipt_path: string = "sow/receipts/catalog-witness.jsonl" --wo: string = "catalog-witness" --pubkey: string = "~/.minisign/witness.pub" ]: nothing -> nothing { let pubkey = ($pubkey | path expand) let sig_path = $"($laws_path).minisig" # Guard 1: no witness, no start. The public key and the detached signature must # both exist, and the signature must verify, before any check runs. if not ($pubkey | path exists) { print $"REFUSED: public key not found at ($pubkey)" exit 1 } if not ($sig_path | path exists) { print $"REFUSED: signature not found at ($sig_path) — laws are non-binding until signed" exit 1 } let verify = (do { ^minisign -V -p $pubkey -m $laws_path -x $sig_path } | complete) if ($verify.exit_code != 0) { print $"REFUSED: signature verification failed for ($laws_path)" print $verify.stderr exit 1 } print $"Signature verified: ($laws_path)" # Guard 2: laws.ncl must export cleanly against its own schema contract. let laws_export = (do { ^nickel export $laws_path --format json } | complete) if ($laws_export.exit_code != 0) { print $"REFUSED: ($laws_path) failed to export — ($laws_export.stderr)" exit 1 } let all_laws = ($laws_export.stdout | from json | get laws) let active_laws = ($all_laws | where status == "Active") let deferred_count = ($all_laws | where status == "Deferred" | length) if ($deferred_count > 0) { print ("Skipping " + ($deferred_count | into string) + " Deferred law(s) — not enforced, see laws.ncl") } let ts = (date now | format date "%+") let laws_hash = (open $laws_path | hash sha256) let results = ($active_laws | each {|law| let out = (do { ^bash -c $law.check } | complete) let verdict = (if $out.exit_code == 0 { "pass" } else { "fail" }) let scope_hash = ({scope: $law.scope, exclude: $law.exclude, exempt: $law.exempt} | to json | hash sha256) { wo: $wo, sow_ref: $laws_path, sow_hash: $laws_hash, contract: $law.id, phase: $law.kind, exit: $out.exit_code, verdict: $verdict, detail: ($out.stderr | str trim | str substring 0..300), scope_hash: $scope_hash, ts: $ts, } }) let receipt_dir = ($receipt_path | path dirname) ^mkdir -p $receipt_dir let lines = ($results | each {|r| $r | to json --raw } | str join "\n") $"($lines)\n" | save --append --raw $receipt_path let receipt_sig_path = $"($receipt_path).minisig" print "" print "Receipt written, NOT signed. Sign it yourself, in your own shell:" print (" minisign -S -s ~/.minisign/witness.key -m " + $receipt_path + " -x " + $receipt_sig_path + " -t \"wo=" + $wo + " " + ($results | length | into string) + " law(s) checked at " + $ts + "\" -c \"provisioning catalog witness receipt\"") print "" let failed = ($results | where verdict == "fail") if ($failed | is-not-empty) { let failed_count = ($failed | length) print ("REJECTED: " + ($failed_count | into string) + " law(s) failed") $failed | select contract exit detail | print exit 1 } let pass_count = ($results | length) print ("ACCEPTED: " + ($pass_count | into string) + " law(s) pass") }