This plan turns the design spec into work items. The spec is the source of truth. If the plan and the spec differ, the spec wins, and the plan gets fixed in the same PR.
Items marked "Owner:" need an org owner or the owner of the Anthropic account. "V" numbers are items in the spec's Verify before building list. "T" numbers are test rounds in claude-review-test.
Every PR to a supported OperationCode repo gets the three-pass Claude review, and the review ruleset requires the central workflow at a pinned SHA, and the baseline ruleset requires an approval, before a merge.
Done when:
- The central workflow runs on every supported repo, from
OperationCode/.githubat a pinned SHA. - A failed run blocks the merge. Only org admins can bypass, through a pull request.
- Every rule in the CLAUDE.md holds, and the unit tests check the ones that code can check.
- The owner has accepted the residuals in writing (spec "Which run counts").
0 platform tests ──> 1 build ──> 2 fixture evals ──> 3 smoke test ──> 4 pilot
(build V items) (this repo) (live API, owner) (claude-review- (pilot ruleset,
test, fork PR) bypass on)
│
6 org-wide enforcement <── 5 before org-wide <───────┘
(org ruleset on, (ruleset fixes,
bypass: org admins) risk acceptance, re-tests)
Gate on each arrow: the exit criteria of the phase before it.
Exception: 1a may start in parallel with phase 0 (see phase 1a).
V6 fails (an older run decides) -> stop, change the gate design.
V2 or V3 fails (no federation) -> stop, change the auth design.
There is one build of the central workflow. Its gate job fails for real when the result is fail. The pilot and org-wide enforcement differ only in who can merge past a failed run (spec "Pilot and enforcement"). The pilot does not protect merges against a failed run: a maintainer or repo admin can merge past any failed run, also on their own PR, but only with the approval that the baseline ruleset 25787833 requires. The baseline gives each repo its approval protection, and its only bypass is org admins through a pull request (spec P5-1; before it, each pilot repo had its own repo ruleset, P4-3).
Use claude-review-test as the target and a scratch source repo for the workflow. Only one ruleset requires the workflow on claude-review-test at a time. Record each result in the spec, in the same PR as any design change it causes.
Tasks:
- V6, first: which run counts when one head SHA has several runs. Cases (a) to (g). Use a scratch-only gate job that can pause after its final check.
- Note for the open V6 rounds, such as (f) run deletion: the V6 kit
0ec8b86in the scratch source repo always does the token exchange, and the test federation rule now names the V10 kitdf8ff51. So a V6 run at0ec8b86fails in analyze, and the gate fails on it (not waivable). Use a kit variant without the exchange (0ec8b86withP0_FEDERATION_RULE_IDempty, and the ruleset pinned to it), or set the test rule back to0ec8b86in the Console.
- Note for the open V6 rounds, such as (f) run deletion: the V6 kit
- V2: a fork PR run prints its decoded OIDC claims (never the token).
- V3: check the intended federation rule against the WIF reference, record the final rule text in the spec, then test it on a test workspace.
- V5: one Messages API request with a mid-conversation system message, structured output and server-side fallback.
- V9: spend limit behavior on a test workspace with a very small limit.
- V13: dependency review on a fork PR with no checkout and SHA refs.
- V16: push epoch events on a fork PR.
- V18: fork head trees and blobs through the base repo's git API.
- V24: run list by head SHA from the gate job's token, and its delay.
- V4: a Dependabot PR mints an OIDC token in a ruleset-required run. A stub analyze job is enough.
Owner:
- Owner: create a scratch source repo in the org for the V tests, separate from claude-review-test (claude-review-p0-source).
- Owner: Anthropic Console: a dedicated workspace with a hard monthly spend limit and a service account. Create the production federation rule as in spec "Auth and cost": pattern mode, subject prefix
repo:OperationCode@10479364/*, scopeworkspace:developer, the review workspace only, and the placeholder SHA0000000000000000000000000000000000000000injob_workflow_ref. Read it back; if the Console rejects the placeholder, stop and resolve the order of rule ID and SHA first (spec V26 a). Its ID goes into the central code; phase 3 sets the candidate SHA. No API key. Done 2026-10-27: workspaceclaude-review, service accountclaude-review, rule R1claude-review-prod-r1created; the Console accepted the placeholder SHA (V26 a partial). The owner read R1 back at M1 and R2 at M2 on 2026-10-27 (spec P3-6). Still open: the service account and the workspace were not read back. - Owner: the test workspace (reused for evals, spec P2-1), and a federation rule for
workflow_dispatchruns inOperationCode/.githubwhose job uses the environmenteval: pattern mode, the same subject prefix, exact claimsrepository_owner_id,repository_id,event_nameandenvironment, scopeworkspace:developer, that workspace only (spec "Auth and cost"). Its IDs are inevals.py. The rule cannot nameeval.yml: any.githubworkflow that usesevalcan match, so the owner's approval and the workflow unit test (1c) hold it toeval.yml. Test it as in spec V27 before phase 2. - Owner: on
.github, create the GitHub environmenteval: the owner is a required reviewer for every run, and any branch may deploy to it. "Prevent self-review" is off temporarily (owner, 2026-10-26), so that the coordinator, which runs with the owner's token, can approve eval runs it started from reviewed branches; the owner turns it on again in phase 5. Admin bypass stays on. During phase 2 the coordinator's approval stands in for the owner's under the owner's delegation. Before approving a run, check the run that waits for approval: the workflow file and commit it runs, every job in it that useseval, and their effective permissions (no GitHub write). Also check that the PR does not changeeval.yml, any other workflow file, the eval code, the prompts, the fixtures or the federation setup in a harmful way, and thattest.ymlpassed. The approval is the control; the residual risk is an approver who approves without reading. Exception to the no-execute rule (K-4): evals of PRs to this repo may run that PR's code, only after an owner approves theevalenvironment run. Needed before phase 2. - Owner: a test workspace with a very small spend limit for V9.
- Owner: the test org ruleset for V6 that targets only claude-review-test.
- Owner: a Dependabot PR in claude-review-test for V4.
Exit criteria:
- V6 shows that the newest run on the head SHA (among runs at the pinned SHA, see spec P3-3) decides. If not, building stops. (The queued cell is still open.)
- V2 and V3 pass. If not, building stops.
- V4, V5, V9, V13, V16, V18 and V24 have results in the spec. A fallback in the spec covers each one that failed.
Python lives in review/, managed with uv. Unit tests make no network or API calls: cd review && uv run pytest tests/unit. Never check out, install, build or execute PR content.
1a may start in parallel with phase 0. It merges to main only after phase 0 exits. Any V result that changes 1a code is fixed before 1b starts. Each task comes with its unit tests.
-
review/project:pyproject.toml,uv.lockwith hashes,tests/unit/. -
schema/findings.schema.jsonandschema/envelope.schema.json, with good and bad fixtures. -
envelope.py: envelope schema, digest check, file count and size caps. -
snapshot.py: comparison pair, tree diff by path, blob SHA and mode, stale checks. -
prefilter.py: every row of the spec's prefilter table, and the coverage rules. -
deps.py: one parser per supported file family, with fixtures for each supported version. Registry lookups on fixed routes only, with byte caps and timeouts. -
collect.py: GitHub REST reads into the bundle envelope. Tests use recorded responses, no network. Depends on V18 (fork objects through the base repo). -
render.py: bundle to prompt text with nonce delimiters and redaction. Test that the rendered text cannot containuntrusted_pr_{nonce}. - Prompts:
system.md,review.md,security.md,supply-chain.md. A unit test fails if any prompt file loses its<grounding_rules>block. -
verdict.py: citation check, inline eligibility, gate reasons, override checks. The full gate matrix from the spec's "Testing" section. The push epoch includesreopened(V16, spec K-12). - Sanitizer and escaping for every untrusted string that the publisher posts.
Depends on: none for most tasks; V18 for collect.py, V16 for the push epoch in verdict.py.
-
analyze.py: one Messages API call per pass, no tools, the model set per pass in code (claude-sonnet-5-5on every pass, see spec P3-4), expliciteffortandmax_tokens, structured output with the stripped schema and a full schema check in code (V5), fallback, at most 2 retries, a request deadline, stale check before the calls. The spend limit error (HTTP 400invalid_request_error, "workspace API usage limits") is an outage with its own clear reason, not a request bug (V9). Always writes exactly three pass records, and exits 0 when it wrote them. The pass record has the optionalmetafield (reason, model, stop reason, schema digest, token usage). Federation goes through the SDK'sWorkloadIdentityCredentials; placeholder IDs make it refuse to call, and therelease-marked test (pytest -m release) fails on them; phase 3 set the production IDs. Tests use a fake client. -
publish.py: escaping, inlineCOMMENTreview, sticky summary, final check, exit code (0 only forpass). It never approves, requests changes, merges, closes, labels or edits the PR (spec decision K-1). A summary over the comment limit is cut in the spec's order, or fails closed with the full record in the gate job log. The summary shows token counts, no currency.
Depends on: V3, V5, V9 (analyze); V6, V16, V24 (publish).
-
.github/workflows/claude-review.yml:pull_request_targetonly,permissions: {}at the top, per-job scopes as in the spec, no jobif:exceptalways()on the gate job (the gate job's download steps carry the non-empty id guard, below), no concurrency group, nocontinue-on-error, every action pinned by full commit SHA, checkout ofOperationCode/.githubatjob.workflow_sha. - The analyze job is the only job with
id-token: write. The gate job has no Anthropic credential. The analyze job setsJOB_WORKFLOW_SHA: ${{ job.workflow_sha }}, passesGITHUB_TOKENandRUNNER_TEMP, and runspython -m oc_review.analyze --bundle-dir ... --bundle-digest ... --out-dir .... The gate job hascontents: read(spec D9),pull-requests: write,issues: writeandactions: read. - The dependency-review job exports the step outcome as the job output named
outcome(publish.pyreadsneeds.dependency-review.outputs.outcome, andDEPENDENCY_REVIEW_OUTCOMEif the workflow sets it; the two must agree). - Guard every artifact download in the gate job with an explicit non-empty id check (D10). An empty
artifact-idsmakesactions/download-artifactfetch every artifact of the run. When analyze failed, its ids are empty, andpublish.pymust find a missing directory and fail with theartifactreason while it still posts the summary. - A unit test parses the workflow: no
continue-on-error, the dependency-review job exports the stepoutcome, everyuses:is a full SHA. -
.github/workflows/test.yml: unit tests on PRs to this repo (pull_request, no path filter) and on pushes tomain. The job isunit-tests, withcontents: read, noid-token, no environment and no secrets, so it runs a PR's code with a read-only token (the no-execute rule covers the review workflow). From phase 3 the job has a second step,pytest tests/unit -m release, so the check name does not change. - Every workflow job starts with harden-runner and has
timeout-minutes. Actions are pinned to the newest release at least 2 days old (spec P5-5; it was 7 days), exceptsetup-uvv10.3.2 (spec K-15). Python is the runner image's 3.12, andsetup-uvhas an exact version and checksum, with its cache and the Astral mirror off. Artifact names carry the run attempt. -
.github/workflows/eval.yml: manual fixture evals. The job usesenvironment: evaland hascontents: readandid-token: writeonly. A workflow unit test reads every workflow file and fails if a workflow other thaneval.ymlusesenvironment: eval, or if a job that usesevalsets no explicit job-levelpermissionsor has anywritepermission other thanid-token: write. Job-levelpermissionsis required even if the workflow level sets it.write-allat either level fails the test, and so doread-alland a missing job-level block, because the job must statecontents: readandid-token: writeitself. -
review/src/oc_review/evals.py: the runner thateval.ymlcalls (python -m oc_review.evals --fixtures-dir ... --out-dir ...), with placeholder eval federation IDs and one smoke fixture. The phase 2 fixtures and IDs come later.analyzetakes an optional federation IDs parameter for it. - Prefilter run budget: the prefilter line diff has a budget of 40,000,000 steps per run next to its 2,000,000 per file (spec "Coverage limits").
- V10: harden-runner in audit mode for a full run, then block mode (
disable-telemetry: true) with the measured allowlist. Done 2026-10-27 (spec V10): block mode passed, but 2 of 24 block-mode jobs hung in "Complete job" (the symptoms match open harden-runner issue 708). Ruling K-14: every job runs in audit mode with its measured list and nodisable-sudo-and-containers; therelease-marked test holds the central lists to the V10 lists. Thetest.ymlandeval.ymllists are not measured (phase 5).
Depends on: V2, V4, V13, V18 (workflow); V10 needs a full run, so it runs here.
Owner:
- Owner: confirm the protection of
.githubmain: CODEOWNERS review, 1 approval, no force push, no deletion, org admin bypass through a pull request only. - Owner: on
.githubmain, turn on dismiss stale approvals on push, and require thetest.ymlcheck (the jobunit-tests, only after it has reported once). - Owner: org Actions setting, default
GITHUB_TOKENpermissions read-only.
Exit criteria:
- Unit tests pass in CI on
.github. - A full run in claude-review-test succeeds with egress in block mode (V10). Done 2026-10-27 with the test federation rule: a clean PR passed and a vulnerable one failed in block mode; 2 of 24 jobs hung; the K3 failed run passed on a re-run. The workflows ship in audit mode (spec K-14).
- Each change was reviewed through a PR with code owner approval.
The fixture eval calls the live API and costs money. The owner runs it on request only. It is the one exception to the no-execute rule: it may run a PR's code, only after an owner approves the eval environment run (K-4).
- V27, before the first eval (passed 2026-10-27, throwaway branch
v27-probe, never merged; Console deny reasons read by the owner on 2026-10-27:match_claim_absentfor N1 and the no-environment job of the rejected-approval run,match_claim_value_mismatchfor the no-environment job of the push run, N3 and N4; claim name and P1 scope not shown in the list view, so P1 scope not confirmed): test the eval federation rule (an approvedeval.ymlrun gets a token; the deny cases in the spec get none, including the other-repo case, spec P2-2; it ran as the substitute N4,pushin claude-review-test with no environment, 401, so repository isolation is not tested separately). - Real eval federation IDs in
evals.py, checked by a unit test against the production placeholders (spec P2-1). - Eval runner: expectation format v2 and the rendered summary per case (spec "Testing", P2-5).
- Fixture builder
review/tests/eval_fixtures.pyand the 13 cases of the spec's minimum set inreview/tests/fixtures, with expected outcomes. Unit tests: each bundle equals a rebuild, and each hard-block case fails the gate whatever Claude returns. - Move the smoke case to
review/tests/unit/fixtures/eval-smoke, out of the live run (spec P2-3). - Owner: run
eval.ymlon the PR branch and approve theevalenvironment, with the eval federation rule and the test workspace (reused for evals) from phase 0. Check the PR first (see phase 0). Record the results in the PR that changed the prompts or code. Done 2026-10-27: run 40445885087, commit 3a03ae8, run by the coordinator under the owner's delegation and approved in theevalenvironment; 13 of 13 cases matched with every pass ok, 207890 input and 20729 output tokens. Passing evals gate the merge of a prompt or model change, and a release pin moves only to a SHA whose tree has passed the evals (a merge SHA with the same tree as the tested candidate counts). No required check enforces the merge gate, so the owner holds the merge until the evals pass. - Fix prompts or code for each miss, then run the full set again on request. An expectation changes only with a written reason in the PR (spec P2-4). No misses in run 1, so no fix and no second run.
- Later phase (not required by the spec): keep the per-case
summary.mdthat the runner writes to--out-dir. Run 1 uploaded no artifact, so nobody can read the rendered summaries. Add anactions/upload-artifactstep toeval.yml(same pinned SHA asclaude-review.yml, attempt in the name, short retention,if: always()), extend the workflow unit test, and check the harden-runner allowlist. Needs a spec change first (spec "Testing", eval runner).
Depends on: phase 1; V27.
Exit criteria:
- V27 passes, with caveats: the "another environment" case was not run, and the repository check was not tested separately (N4 ran as
pushwith no environment); P1 scopeworkspace:developernot confirmed (the Console list view does not show it). - Every fixture in the minimum set gives its expected outcome: one full run with all 13 cases matched (spec P2-4; run 40445885087).
- The hard-block fixtures (bidi, git URL lockfile) block whatever Claude returns. The unit tests prove that both fail the gate whatever Claude returns, and run 1 confirms it.
Run the spec's workflow acceptance tests under a test org ruleset that requires the central workflow from OperationCode/.github at the candidate release SHA and targets only claude-review-test. Only one ruleset requires the workflow there at a time.
- Production IDs in
analyze.pyand the release tests in CI (the candidate S1). R1claude-review-prod-r1(fdrl_01N2ZktTiVKqUvvXwywBFfys) and the production service account are set;test.ymlrunspytest tests/unit -m release. - V26 a: the owner created R1 at the placeholder SHA and the Console accepted it (2026-10-27); R2
claude-review-p3-r2was created the same way. The owner read back R1 at M1590a387and R2 at M21cefe42before the M3 edit (spec P3-6). The service account and the workspace were not read back. - P3-2: an over-long string no longer fails a pass. Release eval run 40517202830 at S1 matched 12 of 13: one supply-chain quote went over 300 characters, which the API cannot enforce. Analyze now cuts every string over its schema
maxLength(summary, file, quote, title, explanation) to that limit and keeps the finding (spec P3-2). Fix round 1: a cut file path ends in "/", so it names no changed file; when the file, title or quote is at its limit, the inline comment key covers every shown field, so a cut never hides a distinct explanation. Fix round 2: a redaction can make two titles equal far below the limit, so the key of every finding covers every shown field, at any length. Fix round 3: each inline comment carries the D14 key and the full key, both of the redacted shown text; one run dedupes by full key, a later run skips a D14 key already on the PR and shows the finding in full in the summary when its full key is new. Fix round 4: an earlier comment counts only when its text checks out (the D14 skip needs the inline layout, path, side and title; "see the earlier comment" needs the exact body at the same line), and an unverified high or critical finding is never cut from the summary. A candidate with this change needs its own eval. - V26 b, before the acceptance tests: after the owner sets the production rule to the candidate SHA, a run at that SHA gets a token and a run at another SHA is denied. Run 40560632129 at S1 got a token (2026-10-27, 18:31Z); run 40561015054 at the probe SHA was denied, and the Console shows 6 rows
Failurematch_claim_value_mismatchon R1 at 18:33:49Z (owner). - V26 c, before V22 b: release steps 1 to 6 with a scratch rule R2 on the test workspace, R1 unchanged. Include the second exchange at the release SHA, and check that the workflows rule accepts a scratch ruleset pinned to a SHA that is only on a non-default branch of
.github. S29752ba7(PR 10 intop3-sim-base) and M21cefe42both got a token (runs 40585724589 and 40587964524); the rule accepted SHAs that are only on non-default branches. Partial: accepted at S2 and M2 (token inferred through R2); R2 Console rows were not read, no R2 read-back output is recorded, and step 5a was not run for R2/M2. Remaining: R2 read-backs at the placeholder, S2 and M2, and a denied exchange at M2 before the R2 edit. - Release eval at S1 (
3aa7ae9): run 40528717338, 13 of 13 matched. - M1 tree check: M1
590a387and S1 have the same tree, and the R1 exchange works at M1 (run 40585413003) after the denied run at M1 (run 40583290922). - Release simulation (R2, S2, M2): steps 1 to 6 of "Each release", never merged to main. Partial: steps ran and S2 and M2 got tokens; the R2 read-back at M2 is done. NOT RUN, stays open: the step 5a deny at M2 before the R2 edit.
- A fork PR from an account outside the org, including a draft that later becomes ready. The review starts with no approval click (V8 b). Pass 2026-10-28: PR 38, run 40621436811 (spec P3-6).
- V8 a: a fork PR whose own workflow has a job named
oc-claude-review. Pass 2026-10-28 (PR 39): from a first-time fork GitHub holds the fake run (action_required), so it cannot start first; after approval it passed and the box followed the central run. The fake-first case stays covered by the same-repo evidence (PR 36, run 40572853203 started first; the box stayed blocked). - V11: retarget from a non-default branch. The retarget starts no run and the box stays blocked until a reopen run passes (PR 29, run 40561903948); an old pass on the same head counts (PR 30).
- V12: repo-level Actions settings. With Actions disabled, and with a restrictive allowed-actions list, the required run still ran (runs 40562372022 and 40562688524). Settings restored.
- V22 a and b: pin behind main, then a pin move with PRs open.
job.workflow_shaequaled the pin in 33 of 33 attempts. After a forward pin move the old passes do not count; after a move back they count again (spec "Pinned SHA only"; PRs 28 and 37, 21:32 to 21:40Z). - V23: a same-path workflow file in the target repo. Pass 2026-10-28 (PR 40): the central run failed on the bidi hard block and was not replaced; the fork run stayed
action_requiredand never ran (the running case is covered by PR 35). Same-repo evidence: PR 35, run 40572849014. - V25: the pilot bypass. Pass 2026-10-28 for Maintain (PR 42) and Admin (PR 43): each merged a failed PR with the bypass. Write bypass NOT RUN (a plain merge was refused, which does not test the bypass; Write is not on the bypass list). GitHub records
bypasswith the user ID as actor (no role ID), and a direct push to main by Maintain is refused (GH013). RepositoryRole 2 is Maintain and 5 is Admin (built-in role IDs). Residual: the Write--adminattempt. The TR bypass list is test-only and goes back to empty after phase 3 (owner's choice pending). - The rest of the acceptance list in the spec's "Testing" section that needs no account X, except the four unit-test-only cases listed in the next item (approver removed, edited override, artifact removed, gate cancel after inline comments), including the outage process (spec "Phase 3 results"). Design change (spec P3-1): the invalid federation rule case ran live with two denied exchanges (runs 40561015054 and 40583290922); the exhausted budget case is covered by V9 (phase 0) and the unit tests, with no Console limit edit (ruling Q7).
- Acceptance cases that needed account X: V6 f as a Write user is done (residual CONFIRMED, PR 41). The web UI "Re-run all jobs" button on a ruleset-started run passed (run 40628870689, PR 43: new attempt on the same run ID, live state re-read, summary comment updated; merge box not observed because the PR was merged); the override cases ran with the REST re-run. Also not run, covered by unit tests: gate cancelled after inline comments with a refused summary write, approver role removed, edited override, artifact upload removed.
- Sonnet 5.5 on every pass (spec P3-4). Release evals 13 of 13 at M3 (run 40592861233); live run 40593729924 passed with
claude-sonnet-5-5(spec P3-6). Eval at 302882f: runs 40589761270 and 40590079545 each matched 13 of 13 with no failed pass; the Opus reference run 40528717338 also matched 13 of 13. Total tokens 0.97x and 0.98x of Opus, output tokens 0.71x and 0.76x, eval wall time 84 s and 82 s against 138 s. The switch takes effect at the next release: the production rule pins a SHA, so merging does not change production. - Comment redesign release M4
9e2795b(2026-10-28, spec P3-7): unit tests run 40710532524 success; release evals run 40713029193, 13 of 13 matched; R1 edited in place to M4 (no new rule); TR pin moved from M3 to M4 and read back; live runs 40713506554 (PR 28, pass layout) and 40713856249 (PR 40, fail layout). Production now runs M4.
Owner:
- Owner: set
job_workflow_refof the production federation rule to the candidate release SHA. Done 2026-10-27 (S1, then M1590a387; the run at M1 got a token; no read-back recorded). Later moved to M32c63f49, then to M49e2795bon 2026-10-28 (spec P3-7; the rule detail value was not read back, and the denied exchange before the edit was NOT RUN). The candidate's code must name that rule ID (spec "Auth and cost", "Each release"). - Owner: an account outside the org opens the fork PRs.
- Owner: test users with Write, Maintain and Admin on claude-review-test for V25. Done with account X
irvingpop2(2026-10-28). Remove its access and empty the TR bypass list after phase 3 (owner's choice pending).
Depends on: phases 1 and 2; V8, V11, V12, V22, V23, V25, V26.
Exit criteria:
- Every "blocking" V item passes, or the spec records a design change for it.
- No acceptance case leaves the merge box clear on a failed or missing run.
- Owner: record the pilot scope. The plan named only
back-end; the owner addedfront-end(135777271),operationcode-pybot(146210805, default branchmaster) andoperationcode_infra(107068150), withback-end(182584277). Four repos (spec P4-1). - Owner: before a repo joins the pilot, give it its own approval protection on the default branch: a repo ruleset or branch protection with at least 1 approval, code owner review and dismiss stale approvals on push. Done 2026-10-28: "Pilot approval protection" on
back-end(26057791),operationcode-pybot(26057793) andoperationcode_infra(26057794), with the Maintain, Admin and org admin bypass because pybot and infra have one active committer;front-end: the existing "Protect main" (25634150), onlyrequire_code_owner_reviewchanged to true (spec P4-3). - Owner: before a repo joins the pilot, check it for people or bots that push to the default branch directly (V25). Done: none in 12 months on
back-end,operationcode-pybotandoperationcode_infra;front-endhad a force push on 2026-10-28 that rewrote three merged PRs (cleanup of a malicious dependency payload), not new unreviewed code (spec P4-4). - Owner: accept that during the pilot a maintainer or repo admin can merge past any failed run, also on their own PR, with the approval that the baseline still requires (spec "Pilot and enforcement").
- Owner: confirm that the federation rule named in the release code has
job_workflow_refat the release SHA. Read back by the owner at M49e2795b(2026-10-29, spec P4-5). - Owner: every pilot repo sends the immutable OIDC subject (spec "Immutable subject", P4-6). Found 2026-10-29: repos made before the GitHub default sent
repo:OperationCode/<repo>:..., the rule rejected them (match_subject_prefix), and the first pilot runs failed closed as designed. Fix per repo:PUT repos/OperationCode/<repo>/actions/oidc/customization/subwithuse_defaulttrue anduse_immutable_subjecttrue. Before it, check the otherid-tokenusers in the repo and their relying-party trust:back-endandoperationcode-pybotuse a cloud OIDC trust policy that gained the immutable subjects (owner; old values kept). After it, all four test PRs passed (all four jobssuccess). - Owner: create the pilot ruleset. Only the rule "Require workflows to pass before merging" with the central workflow
repository_id, path and release SHA. Targets: the pilot repos by repository id (back-end,front-end,operationcode-pybot,operationcode_infra), default branch. Bypass inpull_requestmode: the Maintain and Admin repository roles, and org admins. Created disabled asclaude-review-pilot(26132750): four repo ids,~DEFAULT_BRANCH,repository_id1432342572, M49e2795b, bypassRepositoryRole2 and 5 plusOrganizationAdmin(spec P4-2). - Owner: read the pilot ruleset back through the org rules API. Check
repository_id, path,sha, noref, and the roleactor_idvalues from V25. Then enable it. Read back (do_not_enforce_on_createfalse, noref), then enabled 2026-10-29 (created and enabled the same morning). Rollback:PUTwithenforcementset todisabled. - Owner: require 2FA for all org members. Done in the web UI; all 7 members already had 2FA (spec P4-5).
- Owner: CODEOWNERS in each pilot repo for
.github/, manifests, lockfiles,pyproject.toml,Dockerfile. Added on each default branch (back-end: @irvingpop @kylemh;front-end: @kylemh @irvingpop;operationcode-pybotandoperationcode_infra: @irvingpop; spec P4-3). - Right after enabling the pilot ruleset, list the open PRs in the pilot repos again (do not reuse a list from earlier recon) and start a run on each (close and reopen, or a new push). A required workflow starts only on a new PR event, so a PR opened between the recon and the enable gets no review run: front-end PR 2027 (opened 2026-10-29T08:06Z) was missed this way. Done after the PR 2027 miss; the rule is in spec rollout step 5.
- Each week, record false positives, overrides, bypasses, failed runs by reason, egress blocks and cost per run. Keep cost numbers (any currency amount) in the owner's private notes and the Anthropic Console, never in this public repo. Public text may give ratios and timing only.
- Fix false positives in prompts or code. Each fix goes through phase 2 again, then ships as in "Each release" below.
- Release cf19216 (PR #21, spec K-18, K-19): npm registry responses capped at 128 MiB decoded, gzip requested with a bounded inflate, and visible failure reasons for a failed lookup. Found by the pilot: a false hard block on front-end PR 2027.
- Release 96f4966 (PR #22, spec K-20, K-21): the byte budget counts received bytes with no up-front reservation, the budget is 8 GiB, script text is cut to 1000 chars, and the local harness
review/tools/dep_repro.pyreproduces the dependency stage. Rule: a dependency-check change is reproduced and validated locally with the harness before release. - Pilot result, 2026-10-31: the first real PR (
OperationCode/front-endPR 2027, a large dependency update) was falsely blocked three times at earlier releases (the pnpm two-document lockfile parse, the lockfile diff size, then the npm cap and the byte-budget race). At 96f4966 it passes with 0 hard blocks and 0 findings, matching the local harness.operationcode_infraPR 191 passed with low, non-blocking findings. The test PRs were closed 2026-10-31. The pilot continues; the exit judgment is pending the owner. - Release item: "Claude of Duty" branding of the PR comment and check names (spec P4-8). Display text only; no change to markers, job ids, file names or the
test / unit-testscheck. Shipped in 88e055f (PR #23). - Keep the fixture eval results as an artifact:
eval.ymluploadsresults.jsonland each case'ssummary.mdaseval-results-a<N>(14 days,if: always()). Before this, a release eval's per-case results existed only in the job log (phase 2 run 40445885087 and phase 3 run 40528717338 were read from logs). Ships as in "Each release". - Plan publication before writing: bounded continuation comments, pointed to from the sticky summary, hold every summary-only finding (changed keys, unverified, outside the diff) when one comment cannot. Then no explanation is cut from the PR comments and summary capacity no longer fails a run whose computed result is
pass(spec "Publishing (code)", size limit residual; review r-p3-cut-final Important 1). Keep the fail-closed check until this path is complete. Ships as in "Each release". Plan (planned, not built): docs/continuation-comments-plan.md.
Depends on: phase 3.
Exit criteria:
- Owner: the owner judges that the false positive rate and the run failures are acceptable on real PRs, after a period the owner picks.
- Owner: accept in writing the residuals in the spec's "Which run counts": an earlier pass on the same SHA, and run deletion (V6 f confirmed it as a Write user).
- Owner: baseline ruleset 25787833 (spec P5-1):
deletion,non_fast_forward,required_signatures,pull_request(1 approval, dismiss stale approvals on push, Code Owners review, squash only);~ALL;OrganizationAdminpull_requestbypass only. Read back after each target change. - Supported repo inventory: docs/supported-repos.md.
- Owner: every repo to be added to the review ruleset first sends the immutable OIDC subject (spec "Immutable subject"). Check
GET repos/OperationCode/<repo>/actions/oidc/customization/sub, check the otherid-tokenusers in the repo and their relying-party trust, set it per repo and read it back before the repo is a target. Right after the repo becomes a target, run a test PR in that repo (all four jobssuccessat the pin); the repo is accepted only when it passes. - CODEOWNERS in each supported repo (pilot repos P4-3;
START_HEREP5-4). - Owner: turn "prevent self-review" back on for the
evalenvironment (it is off during phase 2), and decide whether admin bypass stays on. - Owner: Dependabot alerts with npm malware detection on active repos.
- Re-run the key acceptance tests on the current release: V6 a, V8 a, V22 b, V23 and the outage process (the invalid federation rule case), each case on its own terms (V6 a on a same-repo and a fork PR, with the timed-out and slow runs from a scratch-only build; V8 a from an account outside the org). A case not run leaves phase 5 open until the owner accepts the gap in the decision log.
- Scratch bypass matrix and switch rehearsal on claude-review-test: under the scratch test ruleset and the live baseline, check who can merge which PR kind with the empty, the pilot-shaped and the org-admin-only bypass lists, then return to the pilot-shaped list (the rollback of the phase 6 bypass switch). The baseline is never changed for this test.
- Revisit harden-runner block mode (spec K-14): switch to block when harden-runner issue 708 is fixed in a harden-runner release. Pin that release, measure the
test.ymlandeval.ymllists in audit mode, and re-test block mode. If no job hangs, setegress-policy: blockanddisable-telemetry: truein every job, and update the unit tests and the spec.
Depends on: phase 4; V6, V8, V22, V23.
Exit criteria:
- The written acceptance exists.
- The baseline read-back matches the P5-1 checklist, and the bypass matrix passed.
- The re-run tests pass.
- The owner's open security items are done or deferred, with the decision recorded.
- Owner: add each supported repo to the review ruleset 26132750 (immutable subject first, test PR right after). After each add, start a run on each open PR. After the last add, set its bypass list to org admins in
pull_requestmode (rehearsed on the scratch test ruleset first) and rename itclaude-review. - Keep the supported repo inventory current. A repo joins or leaves through a PR to this repo.
- Owner: Phase 6 also applies the org security configuration. Details are kept in the owner's records.
Exit criteria:
- Every supported repo is a target of the review ruleset and gets the baseline rules, and only org admins can bypass either one, through a pull request. The org security configuration is attached and read back (final acceptance test, checks 1-8).
Each release edits rule R1 in place, once, at the merge SHA, with the owner-run release tool (spec "Auth and cost", "Each release"; decision P4-9):
- Merge the release PR. The merge SHA is the release SHA.
- Validate at the release SHA:
test.yml(the push run on main) and the owner-approved fixture eval (eval.yml, aworkflow_dispatchrun on main) both conclude success. A pull_request run at the same head SHA does not count. - Owner:
make release-plan SHA=<release SHA>. Read the plan: every preconditionok, the R1job_workflow_refchange, and the pin change of each ruleset. - Owner:
make release SHA=<release SHA>. There is no prompt: it prints the plan and applies at once, so readmake release-planfirst. The tool edits R1 first, then moves the pin of every ruleset that requires the workflow (every ruleset inRULESETS; the drift check refuses a release if another org ruleset requires the workflow), and reads all three back. A required run started between the rule edit and the last pin move fails closed. A non-zero exit after a write prints which object is at which SHA, the records path and the recovery (the same command again, or the rollback command it prints): follow it before anything else. - Start a new run on each open PR (push, or close and reopen). Do not re-run old runs: a run at the old SHA fails auth. Do this after every pin move, also a move back: the box counts only runs at the pinned SHA (spec "Pinned SHA only").
- Check one run under the scratch test ruleset (claude-review-test) gets a token at the release SHA.
- Rollback:
make release SHA=<previous release SHA>, then new runs on each open PR. - After a change to the release tool or to
ant:make release-rule-test RULE=fdrl_01AfACzupW3U4Bx361ZUbsyR SHA=<sha>on the scratch rule R2 first. Done live on 2026-10-31 17:21Z (R2claude-review-p3-r2moved1cefe42to96f4966, read-back showed onlyjob_workflow_refchanged, logout and temp dir removed); repeat only after a later change to the tool or toant.