Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
19 changes: 13 additions & 6 deletions examples/runcap-adjudicate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
14 changes: 14 additions & 0 deletions scripts/adjudicate-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading