diff --git a/README.md b/README.md index 16e2683..7059ca5 100644 --- a/README.md +++ b/README.md @@ -337,11 +337,11 @@ Make the adjudication a required red/green PR check in your own repo: 1. Add `.runcap/mission.yaml` (the policy - see the example above). 2. Copy `examples/runcap-adjudicate.yml` into `.github/workflows/`. -3. Replace the all-zero `RUNCAP_ACTION_SHA` placeholder with the full immutable commit SHA of the released version (resolve it with `gh api repos/kirder24-code/ai-agent-manager/git/refs/tags/vX.Y.Z --jq '.object.sha'`). +3. The template is pinned to Runcap v0.6.0. When intentionally upgrading, replace the SHA with the full 40-character target commit SHA shown for the GitHub Release you choose. For local verification in a clone of the Runcap repository, peel an annotated tag to its commit with `git rev-parse "vX.Y.Z^{}"`. Do not use an annotated tag object SHA - for the Proof Gate, pin the commit SHA that the release tag resolves to. Never use `@v1` or another floating tag for the Proof Gate. Use a full immutable commit SHA. 4. Configure the hardened GitHub branch profile (protected branch, required check, up-to-date-before-merge, dismiss stale approvals, CODEOWNERS for workflow/policy/verifier/dependency/protected paths, no bypass for ordinary authors) - the full list is in the [trust model](docs/trust-model.md#required-github-setup). 5. Make `Runcap adjudicate` a required status check. -> The template ships with an all-zero placeholder SHA and is **intentionally not runnable until you insert the release SHA**. This is deliberate: the judge must be an immutable release commit that lives outside the candidate PR, so a malicious PR cannot rewrite its own judge. +> The template is pinned to the **Runcap v0.6.0** release commit. This is deliberate: the judge must be an immutable release commit that lives outside the candidate PR, so a malicious PR cannot rewrite its own judge. A reviewer sees one of two things: diff --git a/examples/runcap-adjudicate.yml b/examples/runcap-adjudicate.yml index 81c219b..032a18b 100644 --- a/examples/runcap-adjudicate.yml +++ b/examples/runcap-adjudicate.yml @@ -22,8 +22,14 @@ # - GitHub-hosted runner, capped runtime, single self-sufficient required # check with no `needs:` on any upstream job. # -# Pin RUNCAP_ACTION_SHA to the commit a Runcap release tag points at. Resolve it -# with: gh api repos/kirder24-code/ai-agent-manager/git/refs/tags/vX.Y.Z --jq '.object.sha' +# This template is pinned to Runcap v0.6.0. When intentionally upgrading, replace +# the SHA with the full 40-character target commit SHA shown for the GitHub Release +# you choose. For local verification in a clone of the Runcap repository, peel an +# annotated tag to its commit with: +# git rev-parse "vX.Y.Z^{}" +# Do not use an annotated tag object SHA - for the Proof Gate, pin the commit SHA +# that the release tag resolves to. +# Never use @v1 or another floating tag for the Proof Gate. Use a full immutable commit SHA. name: Runcap adjudicate on: @@ -48,10 +54,11 @@ jobs: with: node-version: 22 - # The judge: the Runcap action pinned by a full commit SHA. Replace the SHA - # below with the commit a published Runcap release tag points at. This is - # the ONLY code that decides the verdict, and it cannot come from the PR. + # The judge: the Runcap action pinned by a full commit SHA. This template is + # pinned to Runcap v0.6.0; when intentionally upgrading, replace the SHA below + # with the commit a published Runcap release tag points at. This is the ONLY + # code that decides the verdict, and it cannot come from the PR. - name: Runcap independent adjudication - uses: kirder24-code/ai-agent-manager@0000000000000000000000000000000000000000 # pin to a release SHA + uses: kirder24-code/ai-agent-manager@1eb87456333093c9fb8da6e9c21eef8d850891bc # Runcap v0.6.0 with: mode: adjudicate diff --git a/scripts/adjudicate-test.mjs b/scripts/adjudicate-test.mjs index 336ad54..427f56f 100644 --- a/scripts/adjudicate-test.mjs +++ b/scripts/adjudicate-test.mjs @@ -304,6 +304,20 @@ check("reference workflow pins every action by a full 40-char commit SHA (no @v4 usesRefs.length > 0 && usesRefs.every((u) => /@[0-9a-f]{40}$/.test(u)), JSON.stringify(usesRefs)); check("reference workflow's judge is the released Runcap action, not workspace code", /uses:\s*kirder24-code\/ai-agent-manager@[0-9a-f]{40}/.test(wfText) && /mode:\s*adjudicate/.test(wfText), "released action judge"); +// SHA-resolution guidance must NOT teach the annotated-tag-object trap. Reading a +// tag ref's `.object.sha` returns the TAG OBJECT sha for an annotated tag, not the +// commit the Proof Gate must pin. The docs (workflow header comment AND README) must +// not contain that pattern, and must teach `git rev-parse "vX.Y.Z^{}"` instead. +const UNSAFE_SHA = /git\/refs\/tags\/[^\n]*--jq[^\n]*\.object\.sha/; +const readmeRaw = readFileSync(path.join(REPO_ROOT, "README.md"), "utf8"); +check("consumer template does not teach the unsafe `git/refs/tags ... --jq .object.sha` resolution", + !UNSAFE_SHA.test(wfRaw), "workflow header gh-api pattern"); +check("README does not teach the unsafe `git/refs/tags ... --jq .object.sha` resolution", + !UNSAFE_SHA.test(readmeRaw), "README gh-api pattern"); +check("consumer template teaches `git rev-parse \"vX.Y.Z^{}\"` to peel an annotated tag to its commit", + /git rev-parse "vX\.Y\.Z\^\{\}"/.test(wfRaw), "workflow rev-parse guidance"); +check("README teaches `git rev-parse \"vX.Y.Z^{}\"` to peel an annotated tag to its commit", + /git rev-parse "vX\.Y\.Z\^\{\}"/.test(readmeRaw), "README rev-parse guidance"); // --- 22. the judge is the adjudicator's OWN code, not the PR's bin ----------- // A head PR that rewrites bin/runcap.mjs to always print PASS, or rewrites