From a6df5e31f64501eef0521effc11ec19541fdda9b Mon Sep 17 00:00:00 2001 From: Dave Thompson Date: Tue, 18 Aug 2026 18:12:59 -0400 Subject: [PATCH 1/2] chore: polish signed-release docs, rollback, and test build Quiet install-window rollback, run ceremony schema checks against a host binary from make test, and document how to verify a signed draft. Changes: - Single-shot rollback so TERM does not replay the restore - Build the host binary before ceremony probes - Overview verbs, consumer verify recipe, README pointer Generated by Grok 4.6 via Grok Build under supervision of @3leapsdave Co-Authored-By: Grok 4.6 Role: cxotech Committer-of-Record: Dave Thompson [@3leapsdave] --- Makefile | 1 + README.md | 13 ++++++ docs/decernor-overview.md | 6 ++- .../PDR-0001-committed-signing-anchors.md | 4 ++ docs/releases/v0.1.3.md | 22 ++++++++++ scripts/atomic-install-pair.sh | 4 ++ tests/release/ceremony_test.sh | 40 +++++++++++++++++++ 7 files changed, 89 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3e207bd..c9cf686 100644 --- a/Makefile +++ b/Makefile @@ -346,6 +346,7 @@ verify-release-key: ## Verify exported public key contains no private material test: verify-embedded-identity ## Run all tests @echo "Running test suite..." $(GOTEST) ./... -v -cover + @$(MAKE) build @bash tests/release/ceremony_test.sh lint: ## Run lint checks with goneat diff --git a/README.md b/README.md index e5112d3..9b951a5 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,19 @@ Users should be able to: This matters because the assets being scanned by this tool are often the same class of assets used to sign its releases. +## Verify a signed release + +Consume fingerprints, not secrets. Per-cut steps live in +[`docs/releases/v0.1.3.md`](docs/releases/v0.1.3.md). Committed pins and +the inserter are documented in [`keys/README.md`](keys/README.md). How +those pins enter the signed set is +[`docs/decisions/PDR-0001-committed-signing-anchors.md`](docs/decisions/PDR-0001-committed-signing-anchors.md). + +Download the draft (archives, signed SUMS, exported publics, staged pin +pair). Verify SUMS signatures, then run `decernor fingerprint` on the +exported publics and compare to `expected-fingerprints.txt`. Never +hand-type hex into notes or a README. + ## Build ```sh diff --git a/docs/decernor-overview.md b/docs/decernor-overview.md index 6ad8a02..268be2b 100644 --- a/docs/decernor-overview.md +++ b/docs/decernor-overview.md @@ -10,9 +10,11 @@ Decernor is a CLI tool for inspecting local signing and authentication material ## Scope -Decernor answers two questions: +Decernor answers a few questions: - `scan`: what risky or sensitive key material is present in this path? +- `guardread`: can this one file be written to stdout without first exposing supported key-material bytes? +- `fingerprint`: what safe public identity fingerprints can be emitted for this key material? - `readiness`: does an asset set appear to have enough protected material for a capability? It is intentionally CLI-only. It should not become a daemon, web service, remote scanner, or key store without an explicit architecture review. @@ -22,6 +24,8 @@ It is intentionally CLI-only. It should not become a daemon, web service, remote | Command | Purpose | | -------------------------------- | ------------------------------------------------------------------------- | | `scan PATH` | Inspect a directory and report local key-material findings. | +| `guardread FILE` | Write one file to stdout only if it is not supported key material. | +| `fingerprint PATH` | Emit public identity fingerprints (alias: `fp`). | | `readiness validate-config PATH` | Validate readiness configuration JSON. | | `version` | Print build version, with optional extended dependency details. | | `envinfo` | Print runtime, config, and app identity details. | diff --git a/docs/decisions/PDR-0001-committed-signing-anchors.md b/docs/decisions/PDR-0001-committed-signing-anchors.md index 8a53923..efe7f80 100644 --- a/docs/decisions/PDR-0001-committed-signing-anchors.md +++ b/docs/decisions/PDR-0001-committed-signing-anchors.md @@ -127,6 +127,10 @@ decernor fingerprint "$PUB" --class public --kind minisign \ # validate: each ndjson line against fingerprint-record.v0. ``` +Pin-pair install uses **process-lifetime rollback** on error or +INT/TERM/HUP (restore the prior pair, or remove a first-use dest). Two +dest files are not power-loss atomic. + Rekey = new export + same script. Do not immortalize today's hex in docs or commit messages. diff --git a/docs/releases/v0.1.3.md b/docs/releases/v0.1.3.md index f07d2f7..d07f5f8 100644 --- a/docs/releases/v0.1.3.md +++ b/docs/releases/v0.1.3.md @@ -11,3 +11,25 @@ are `DECERNOR_*` environment variables only. `envinfo`, `doctor`. Still private. Do not treat an undrafted GitHub release as a public launch. + +## Verify a draft + +Download the draft assets: archives, `SHA256SUMS` / `SHA512SUMS` and their +signatures, exported publics, and the staged pin pair +(`expected-fingerprints.txt`, `expected-fingerprints.ndjson`). Check the +signatures on the SUMS, then check the SUMS against the files. + +Recompute fingerprints from the **exported publics** and compare them to +the two-line pin file. Do not hand-type hex. + +```sh +decernor fingerprint decernor-release-signing-key.asc \ + --class public --kind gpg --format json --path-mode none --gpg-role primary +decernor fingerprint decernor-minisign.pub \ + --class public --kind minisign --format json --path-mode none +``` + +The GPG primary fingerprint and the minisign public-blob SHA-256 must +match the `gpg` and `minisign` lines in `expected-fingerprints.txt`. +See `keys/README.md` and +`docs/decisions/PDR-0001-committed-signing-anchors.md`. diff --git a/scripts/atomic-install-pair.sh b/scripts/atomic-install-pair.sh index 817385e..d525c3f 100755 --- a/scripts/atomic-install-pair.sh +++ b/scripts/atomic-install-pair.sh @@ -32,6 +32,8 @@ cp "$SRC_NDJSON" "$NEW_NDJSON" cp "$SRC_TXT" "$NEW_TXT" rollback() { + # Single-shot: a signal runs this handler, then EXIT must not run it again. + trap - EXIT INT TERM HUP rm -f "$NEW_NDJSON" "$NEW_TXT" if [ "$had_ndjson" -eq 1 ]; then mv -f "$BAK_NDJSON" "$DEST_NDJSON" @@ -53,6 +55,8 @@ if ! mv -f "$NEW_NDJSON" "$DEST_NDJSON"; then fi if [ "${DECERNOR_TEST_KILL_AFTER_FIRST:-}" = 1 ]; then kill -s TERM $$ + # A handled TERM returns here; do not continue the second install. + exit 143 fi if [ "${DECERNOR_TEST_FAIL_SECOND:-}" = 1 ]; then echo "error: failed to install txt pin; restored previous pair" >&2 diff --git a/tests/release/ceremony_test.sh b/tests/release/ceremony_test.sh index 6b292d3..3cff2ad 100755 --- a/tests/release/ceremony_test.sh +++ b/tests/release/ceremony_test.sh @@ -89,6 +89,28 @@ else fi rm -rf "$pair" "$empty" +# existing pair + TERM after first install restores prior bytes once (no second mv). +pair="$(mktemp -d)" +prior="$(mktemp -d)" +printf 'new-ndjson\n' >"$pair/expected-fingerprints.ndjson" +printf 'new-txt\n' >"$pair/expected-fingerprints.txt" +printf 'old-ndjson\n' >"$prior/expected-fingerprints.ndjson" +printf 'old-txt\n' >"$prior/expected-fingerprints.txt" +set +e +err="$(DECERNOR_TEST_KILL_AFTER_FIRST=1 "$INSTALL" "$pair" "$prior" 2>&1)" +set -e +if ! grep -qx 'old-ndjson' "$prior/expected-fingerprints.ndjson" || + ! grep -qx 'old-txt' "$prior/expected-fingerprints.txt"; then + fail "existing-pair signal did not restore prior dest files" +elif [ -e "$prior/expected-fingerprints.ndjson.new" ] || [ -e "$prior/expected-fingerprints.txt.new" ]; then + fail "existing-pair signal left .new residue" +elif printf '%s\n' "$err" | grep -Eqi 'no such file|cannot stat'; then + fail "existing-pair signal ran rollback twice ($err)" +else + pass "existing-pair signal restores prior dest once" +fi +rm -rf "$pair" "$prior" + # extra TXT token: helper must emit the two-field error (not keys/ cmp). if [ -f "$ROOT/keys/expected-fingerprints.txt" ]; then mut="$(mktemp -d)" @@ -118,6 +140,24 @@ if [ -f "$ROOT/keys/expected-fingerprints.ndjson" ]; then rm -rf "$mut" fi +# schema path: committed pair must validate when a host binary exists. +DECERNOR_BIN="${DECERNOR_BIN:-}" +if [ -z "$DECERNOR_BIN" ] && [ -x "$ROOT/bin/decernor" ]; then + DECERNOR_BIN="$ROOT/bin/decernor" +fi +if [ -n "$DECERNOR_BIN" ] && [ -f "$ROOT/keys/expected-fingerprints.txt" ]; then + out="$(DECERNOR_BIN="$DECERNOR_BIN" "$VALIDATE" \ + "$ROOT/keys/expected-fingerprints.txt" \ + "$ROOT/keys/expected-fingerprints.ndjson" 2>&1)" && status=0 || status=$? + if [ "$status" -eq 0 ]; then + pass "validate accepts committed pin pair (schema path)" + else + fail "validate rejected committed pin pair ($out)" + fi +else + note "SKIP: schema path (no host decernor binary)" +fi + if [ "$FAIL" -ne 0 ]; then note "$FAIL ceremony probe(s) failed" exit 1 From beab1003b8f1e9224255b9f0bd87e966190d08d6 Mon Sep 17 00:00:00 2001 From: Dave Thompson Date: Wed, 19 Aug 2026 16:20:48 -0400 Subject: [PATCH 2/2] docs: align guardread overview with the shipped contract The command table now matches the guarded-copy contract instead of implying a plain non-key cat. Generated by Grok 4.6 via Grok Build under supervision of @3leapsdave Co-Authored-By: Grok 4.6 Role: cxotech Committer-of-Record: Dave Thompson [@3leapsdave] --- docs/decernor-overview.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/decernor-overview.md b/docs/decernor-overview.md index 268be2b..6b80381 100644 --- a/docs/decernor-overview.md +++ b/docs/decernor-overview.md @@ -21,16 +21,16 @@ It is intentionally CLI-only. It should not become a daemon, web service, remote ## Current Commands -| Command | Purpose | -| -------------------------------- | ------------------------------------------------------------------------- | -| `scan PATH` | Inspect a directory and report local key-material findings. | -| `guardread FILE` | Write one file to stdout only if it is not supported key material. | -| `fingerprint PATH` | Emit public identity fingerprints (alias: `fp`). | -| `readiness validate-config PATH` | Validate readiness configuration JSON. | -| `version` | Print build version, with optional extended dependency details. | -| `envinfo` | Print runtime, config, and app identity details. | -| `doctor` | Run local installation diagnostics. | -| `validate` | Validate schema/data files; retained while readiness schema work matures. | +| Command | Purpose | +| -------------------------------- | ---------------------------------------------------------------------------- | +| `scan PATH` | Inspect a directory and report local key-material findings. | +| `guardread FILE` | Copy one regular file to stdout only after guarded key-material checks pass. | +| `fingerprint PATH` | Emit public identity fingerprints (alias: `fp`). | +| `readiness validate-config PATH` | Validate readiness configuration JSON. | +| `version` | Print build version, with optional extended dependency details. | +| `envinfo` | Print runtime, config, and app identity details. | +| `doctor` | Run local installation diagnostics. | +| `validate` | Validate schema/data files; retained while readiness schema work matures. | ## Scanner Model