provisioning-catalog/scripts/witness.nu

122 lines
5.4 KiB
Text
Raw Normal View History

#!/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 <id> [--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")
}