From f4713b05926fa2d6f5067583653796f2bfd9c9a6 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Sun, 20 Sep 2026 02:36:35 -0500 Subject: [PATCH] Bind audit seals to source jobs --- CHANGELOG.md | 5 ++ docs/local-audit-runner.md | 11 ++-- src/code_mower/audit_publication.py | 55 +++++++++++++++++-- tests/test_audit_publication.py | 83 +++++++++++++++++++++++++++++ tools/audit_publication.py | 55 +++++++++++++++++-- 5 files changed, 195 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d92f729f..fabf3faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ later entries are regular releases. ## Unreleased +- Local audit publication now binds each reviewer seal to the exact Actions + job, matrix lane, run, and first attempt that produced it. Independent Codex + and Claude lanes in one run no longer make publication ambiguous, while a + missing, duplicate, wrong-job, or wrong-attempt seal still fails closed + (#1032). - Hosted-agent installation guidance now includes a Python-based uv bootstrap that does not pipe a remote script into a shell. Remote-only orchestrators get an explicit packaged-starter doctor command, doctor JSON is identified as diff --git a/docs/local-audit-runner.md b/docs/local-audit-runner.md index 6d51af92..1455f885 100644 --- a/docs/local-audit-runner.md +++ b/docs/local-audit-runner.md @@ -165,7 +165,7 @@ metadata only while its original head and freshness checks still hold. Only canonical metadata leaves the machine: schema, numeric repository ID, PR number, reviewer lane, PASS/BLOCKED, full start/end head SHAs, artifact creation -time, and the originating audit run ID/attempt. The repository name, comment prose, findings, code, prompts, transcript, +time, and the originating audit run, attempt, and job IDs. The repository name, comment prose, findings, code, prompts, transcript, paths and provider output stay local. The SHA-256 digest covers those exact canonical metadata bytes. The publisher accepts only equal full start/end SHAs, an open PR at that SHA, and artifacts no more than 24 hours old. UNKNOWN, STALE, @@ -185,12 +185,15 @@ attempt is refused. Keep receipts and reservations for at least the 24-hour artifact lifetime. The global publication concurrency group serializes claims; GitHub may cancel an older queued dispatch, which requires inspecting its result. -The source job stages the metadata, independently validates it, and completes a +The source job stages the metadata with its exact GitHub Actions job ID, +independently validates it, and completes a `Code Mower reviewer seal ` step before dispatch. The publisher verifies -that immutable Actions step record in the matching `audit (claude|codex)` job, +that immutable Actions step record in that exact `audit (claude|codex)` job, source run ID/attempt, trusted `local-cli-audit.yml` `repository_dispatch` event, same repository and default branch. The sealed digest binds the PR, lane and -full start/end head. The small `local-audit-request.yml` trigger requests the +full start/end head plus the source job identity. Another audit lane in the +same matrix run cannot satisfy or make ambiguous that binding; a duplicate +seal within the requested lane is refused. The small `local-audit-request.yml` trigger requests the review; the source workflow validates the request against the live PR before starting a provider. Both source and publisher use `repository_dispatch`, which always executes default-branch code: a builder cannot counterfeit a source job diff --git a/src/code_mower/audit_publication.py b/src/code_mower/audit_publication.py index 248a1473..4c94eb0d 100644 --- a/src/code_mower/audit_publication.py +++ b/src/code_mower/audit_publication.py @@ -41,6 +41,7 @@ "created_at", "source_run_id", "source_run_attempt", + "source_job_id", } ) MARKER = "