ci(distribution): disarm fleet automerge for the Phase 3d rollout window - #290
Merged
Merged
Conversation
`automerge` is opt-out policy-as-data: a target that omits the key keeps the armed default, so every one of the eight targets must carry it explicitly for the window to hold. Disarming restores the two-checkpoint shape while the claude review-lane components roll out — a standards merge proposes the sync PR, and a human merges it downstream. Without it, one merge on a standards PR distributes workflow content to all eight targets and self-merges under a base ruleset that requires zero approving reviews. This is a temporary window gate. It is reverted once the rollout completes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013pLW2dybov9xvTFtx48Ueb
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
kyle-sexton
added a commit
that referenced
this pull request
Jul 29, 2026
Resolves the one conflict in distribution/sync-manifest.yml, at the melodic-software/claude-code-plugins target where both parents insert after `- typos`: main's #290 adds `automerge: false`, this branch adds the `locally-owned` block for the two lane callers. Both survive, with `automerge: false` last to match the key order #290 uses on every target that already carries a `locally-owned` list. Purely additive over each parent — `git diff <parent> -- distribution/sync-manifest.yml` emits no deletion lines against either origin/main or af85ae0, so #290's comment above the `targets:` block is preserved along with every component list. Verified after resolution: - yq: 8 of 8 targets carry `automerge` as a `!!bool` false; the count of targets whose value is not boolean false is 0. - `sync-manifest.sh matrix` emits `automerge` as a JSON boolean for all 8 (a quoted string would be truthy in a GitHub expression). - Per-target managed/locally-owned lists are byte-identical to af85ae0 (95 entries), and the components block is unchanged. - `bash distribution/sync-manifest.sh validate` -> `Manifest valid: 34 components, 8 targets`, exit 0. - `bash distribution/sync-manifest.test.sh` -> 147 passed, 0 failed. - `npm run test:runner-policy` -> 242/242 against the merged manifest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton
added a commit
that referenced
this pull request
Jul 29, 2026
…ents (#286) ## Summary Adds two components — `claude-review-caller` and `claude-security-review-caller` — sourcing thin workflow callers for the ci-workflows reusable Claude review lanes at `.github/workflows/claude-review.yml` and `.github/workflows/claude-security-review.yml`, pinned at the **v0.9.1** release SHA (`c136b27f404dd32ce3873f39a6f3443891d1c16e`). This is Phase 3a of the ci-workflows claude-review-lanes plan (`docs/topics/claude-review-lanes/PLAN.md`). > **Sections below marked "REVISED" supersede the original text.** Four > commits landed after this body was first written; where they conflict, the > REVISED text is authoritative. ### REVISED — pin is v0.9.1, not v0.9.0 v0.9.0 (`cf666f67`) was tagged BEFORE `paths-file` merged. The security caller passes `paths-file`, which v0.9.0's reusable does not declare — Actions hard-fails a `workflow_call` that passes an undeclared input, and on the security lane that wedges a required check permanently. Verified by reading each reusable's `workflow_call` block at the pinned SHA (`git show <sha>:<path>`, not `main`, not the tag) and diffing the caller's `with:` / `secrets:` keys against it in both directions, plus every `needs.<job>.outputs.<name>` read against the reusable's declared outputs. Result at v0.9.1: 0 undeclared inputs, 0 undeclared secrets, 0 omitted required inputs, 0 undeclared output reads, across all four `uses:` including both `select-runner.yml` calls. Negative control at v0.9.0 reproduces the defect. ### REVISED — these components are PRIVATE-ONLY; the security caller is PARKED The callers resolve the runner through the governed `select-runner` indirection, and `runner-policy` admits that selector only for a private self-hosted consumer (`routingEnabled = visibility === "private" && selfHostedCi`). The `!routingEnabled` branch consults neither `exceptions` nor `localRoutingGrants`, so a PUBLIC target has no configuration escape. Auditing each component against a public consumer config using the shipped `policy.json` yields four `public-self-hosted-routing` findings; the same audit passes clean for a private self-hosted consumer. Consequences: - `claude-review-caller` is `managed` for the four PRIVATE targets that run the lane: dotfiles, github-iac, medley, provisioning. - `melodic-software/claude-code-plugins` is PUBLIC and is now `locally-owned` for both callers, keeping its hand-written hosted-only callers. Had the original targeting shipped, its own `runner-policy` lane — and with it `ci-status` — would have gone red, wedging the one repo whose ruleset requires `security-review / security-review`. - `claude-security-review-caller` therefore has **no managed target** and is recorded as PARKED, not scoped: both repos running a security lane today (claude-code-plugins, ci-workflows) are public. It is retained rather than deleted because its bytes are the reviewed shape for the one lane whose check can be a required context. It unparks when a private repo adopts the security lane, or when the runner indirection moves inside the ci-workflows reusable so one component serves both visibilities. Three tests in `components/runner-policy/runner-policy.test.mjs` hold this and were each proven non-vacuous by reintroducing the defect: a selector-routed caller may not be `managed` for a public target; every caller component must audit clean for a private self-hosted consumer; a selector-routed caller is expected to be rejected outright on a public one. ### REVISED — accepted loss: `synchronize` All four managed targets' live callers carry `synchronize`; the component drops it. That is the plan's deliberate cadence cut (review on open/ready/reopen; re-run the job for a fresh review), not drift — but it is the largest behavior change the component makes and is now recorded as an accepted loss for every target, alongside medley's `paths-ignore`. The security lane KEEPS `synchronize`: its check certifies execution against the latest head. ### REVISED — validation coverage gap closed `components/claude-lanes/` holds workflow bytes, but every workflow-shaped lane in this repo discovers files under `.github/workflows` — so actionlint, zizmor, concurrency-policy, and pin-comment-convention were all blind to these components, and `lint:runner-policy` scans the repo's own workflows, not `components/`. Nothing validated them. Three lanes now cover them in CI, each proven non-vacuous: - **pin-comment-convention** takes an explicit file list and scans `components/claude-lanes/*.yml` (a mismatched SHA claim fails it). - **zizmor** names both components alongside `.`; its `paths` input is whitespace-separated. Verified that a bare `.` never reached them, that they are audited now, and that the finding total, severity breakdown, and exit code are identical to baseline — so what the lane gates on is unchanged. - **actionlint**, through a new materialization contract test (`components/claude-lanes/claude-lanes.test.sh`, wired into the `actionlint` job). It runs the entrypoint a consumer runs rather than linting the component in place: `sync-manifest.sh apply` into a scratch checkout carrying the target's origin identity, then actionlint over the result. Every managed target is covered, plus each component's bytes standalone at their destination path so the PARKED security caller — which no target manages, so no target loop reaches it — is linted too. A control run with the suppression config removed still reports the `concurrency.queue` message, so the case cannot go vacuous unnoticed when rhysd/actionlint#654 ships upstream. **Scope of that coverage, stated precisely:** the test is hermetic and makes no network call, so it lints each target's materialized bytes under THIS repository's canonical actionlint config — not under the target's own. For `github-iac` and `medley`, which own `actionlint` locally and so receive no config from the manifest, the canonical config always substitutes. If either deleted its own queue suppression, this test would still pass. What the test owns is that the shipped bytes lint clean under a conforming config at the destination path the manifest maps. That the four live configs actually conform is gate item 1 below — a live check, re-verified independently, and it is what covers this gap. Still blind, deliberately: `concurrency-policy` fails both components on three rules whose values are load-bearing (lane-scoped group names; the security caller's deliberate `cancel-in-progress: false`). No target repo runs `concurrency-policy`, so nothing fails today; the conflict is documented at the component sources so a future adopter exempts rather than normalizes. Caller shape derives from the reusables' canonical-caller headers (the SSOT): - Job ids `review` / `security-review` — required-check name continuity (`security-review / security-review` on claude-code-plugins' ruleset). - Review triggers `[opened, ready_for_review, reopened]`; security triggers add `synchronize`; no workflow-level path filtering on the security caller (a non-triggering required check wedges forever). - Permissions per the canonical headers (`contents: read`, `pull-requests: write`, `id-token: write` on the lane job). - Governed `select-runner` indirection (selector job, `needs`, runner input) with the runner-policy recovery fallback `'ubuntu-24.04'` — never a self-hosted label, never `vars.CI_HOSTED_RUNNER`. - Security caller passes `paths-file: .github/claude-security-paths` (shipped in ci-workflows#282). - `CLAUDE_CODE_OAUTH_TOKEN` passed explicitly; never `secrets: inherit`. - Targets are the repos that run each lane **today** — SUPERSEDED by the private-only revision above; the shipped targeting is `claude-review-caller` in dotfiles, github-iac, medley, provisioning, with claude-code-plugins `locally-owned` for both callers and `claude-security-review-caller` parked. `.github` and ci-runner stay exempt (plan approval record item 1); knowledge-corpus and songwriting are commented follow-ups gated on the sync App access grant. `distribution/README.md`'s three workflow-caller-exclusion statements gain carve-outs pointing at a new authoritative section: hand-written lane callers empirically drifted (medley's missing `reopened` trigger, divergent skip-actors lists, pin skew v0.6.1 / e295107). AGENTS.md and governance-process.md were grepped for restatements of the exclusion — none exist. Cross-doc reconciliation self-review performed per `distribution/governance-process.md`: the three README statements plus the new section are the complete reconciliation surface, and no other normative doc was left contradicting the change. ### DO NOT MERGE — gate status (REVISED) 1. **standards#284 (Phase 3c0 actionlint suppression) — SATISFIED.** Merged 2026-07-27T14:24Z. Every consumer lints its own workflows with pinned actionlint 1.7.12, which rejects the review caller's `concurrency.queue` key (rhysd/actionlint#654); without the distributed suppression, each caller sync PR would fail its consumer's required `ci-status` check. Verified live: all four managed targets' `.github/actionlint.yaml` suppress the message for `claude-review.yml` (dotfiles and provisioning by glob, github-iac and medley by explicit path). 2. **standards-sync App `workflows: write` (Phase 3a0) — SATISFIED.** Verified live: `gh api orgs/melodic-software/installations` reports installation `144867070` (`melodic-standards-sync`) holding `{contents: write, issues: write, metadata: read, pull_requests: write, workflows: write}`. Writing `.github/workflows/` files in target repos needs that permission; without it every sync PR from this component would fail. 3. **#290 (`automerge: false` rollout window) — SATISFIED.** Merged 2026-07-29T13:05Z and merged into this branch, resolving the one conflict at the `claude-code-plugins` target. 4. **#289 (re-pin the sync engine at `ac223bb`) — OPEN.** #290's stated merge order is #290 → #289 → #286. The App grant above is inert until the engine re-pin lands, because `main` still pins `ci-workflows@0b45b9f`, which predates the `permission-workflows: write` mint. Beyond that ordering, the only failing check on this PR is `do-not-merge / do-not-merge` — the intentional label gate, which the label owner lifts. ### Concurrency decision record Shipped the 2e-documented shape from the reusables' headers: review caller = workflow-level per-PR cancel group plus a separate job-level `queue: max` repo-wide group; security caller = per-PR group with `cancel-in-progress: false` and **no** queue. github-iac's live caller deliberately omits caller-level concurrency, claiming (i) caller-level cancel reintroduces skipped-actor cancellation and (ii) a group-name collision with the reusable's job group. Inspection at v0.9.0: - **Collision claim: disproven.** The inner job group is `claude-review-<PR>-<headSHA>` (claude-review.yml:261 at v0.9.0); the caller groups are `claude-review-<PR>` (cancel) and `claude-review-<owner/repo>` (queue). No name equality, so the historical caller/inner deadlock — real when the inner group was `claude-review-<pr-number>` exactly; provisioning's caller comment documents the observed "deadlock was detected" error from that era — cannot recur. github-iac's and provisioning's comments describe a pre-v0.9.0 inner-group shape. - **Skipped-actor cancellation: real but bounded, accepted.** The caller workflow-level group does evaluate before any job `if`, so a skip-actor event on the same PR cancels an in-flight review. With no `synchronize` trigger the same-PR event surface is `opened` / `ready_for_review` / `reopened` — rare and human-driven. The reusable's 2e header documents this exact caller value as canonical and per-lane deliberate. The component sources record both rationales inline; the 3c smoke exercises this exact shape. ### Paths-file seeding disposition The manifest has **no seed-once mechanism** (schema v2: components are unconditional source-to-dest maps), and `.github/claude-security-paths` is repo-owned tuning that must NOT become managed bytes. Disposition: - Recorded the gap in the manifest comment and README section: a new adopter commits its starter list via a repo-local PR at adoption time. - **claude-code-plugins migration ordering: MOOT.** That repo is now `locally-owned` for both callers and receives no sync PR, so no migration ordering applies. (Its `.github/claude-security-paths` already exists — 733 bytes, live — and its hand-written caller keeps its inline `paths:` list.) - Proposed mechanism if seeding is wanted later: a `seed` file class in schema v3 — materialized only when absent at the target, never reconciled — which preserves repo ownership after first sync. ### Deviations / notes for the 3c smoke - `skip-actors` is not passed by the callers: the reusable's default at the pinned `c136b27` is already the normalized self-trigger-ban list (`dependabot[bot],claude[bot],melodic-ai[bot],melodic-standards-sync[bot]`, `claude-review.yml:150`); existing callers passed it only because the pre-v0.9.0 default was narrower. Ownership therefore moves from the caller to the reusable, and `conventions/review/ai-review-bot-composition.md` is reconciled to say so — it previously attributed the `melodic-standards-sync[bot]` exclusion to the caller's `skip-actors` input, wiring this component no longer has. - Runner fallback normalized to `'ubuntu-24.04'` (dotfiles and github-iac currently use `melodic-ubuntu-24.04-x64`, which violates the runner-policy recovery contract in `distribution/README.md` and would queue forever on a public repo). - Selector-failure surfacing (`class=runner` marker) is a comment, not a `TODO(#issue)`: no dedicated issue exists; the incident-aggregator acceptance test (ci-workflows#238) exercises it in Phase 4. - The 3c smoke must confirm the required-check shape when the security caller's selector job fails or skips (a caller-job skip is a new state for the `security-review / security-review` required context on claude-code-plugins; on that public repo the selector routes hosted-only, so the exposure is infra-failure only). - medley's `paths-ignore` tuning is an accepted loss (plan approval record item 10), noted at its manifest target entry. ## Test plan REVISED — as run at `a4d7ee6`: - `npm run test:runner-policy` — 242/242 pass, including four new gates (each proven non-vacuous by reintroducing its defect in a scratch copy): a selector-routed caller may not be `managed` for a public target; every caller component must audit clean for a private self-hosted consumer; a selector-routed caller is rejected outright on a public one; and every managed target of any caller must be one runner-policy admits (written as a property, so legitimate unparking via a private consumer passes). - `npm run lint:runner-policy`, `npm run lint:md`, `bash distribution/sync-manifest.sh validate` (`Manifest valid: 34 components, 8 targets`) all pass. - Scripted input-conformance proof, both directions, all four `uses:` read at the pinned SHA: 0 failures, 0 warnings. Negative control at v0.9.0 reproduces the `paths-file` defect. - Governance simulation with the shipped `policy.json`: private self-hosted consumer passes clean; public consumer produces four `public-self-hosted-routing` findings. Negative control against the pre-`fa2e7a5` policy produces eight findings, proving the new policy entries load-bearing. - Selector byte-identity claim confirmed by object hash: `c136b27` and `e77f0126` both resolve `select-runner.yml` to blob `6ba7d60c`. - zizmor 1.26.1 over both components: no findings; totals identical to baseline. Extended pin-comment-convention scan: exit 0. - End-to-end regression test against each managed target's REAL live state — its `.github/runner-policy.json` and its full live workflow set fetched via the API, then the synced caller dropped in: all four (dotfiles, github-iac, medley, provisioning) report `Runner policy passed.` both before and after, so the caller introduces no finding at any target. - CI on the tip: **42 of 43 checks SUCCESS**, including `ci-status`, `Runner policy`, `distribution`, `actionlint`, `zizmor`, `concurrency-policy`, and `pin-comment-convention`. The sole failure is `do-not-merge / do-not-merge`, which is the intentional label gate. REVISED — after merging `main` (#290) and closing the five review threads: - Conflict with #290 resolved at the `claude-code-plugins` target, the one place both parents insert after `- typos`. Purely additive over each parent: `git diff origin/main -- distribution/sync-manifest.yml | grep '^-'` and the same against `af85ae0` each emit only the `---` header, zero deletion lines, so #290's comment above `targets:` and every component list survive. - `yq`: 8 of 8 targets carry `automerge` as a `!!bool` `false`; the count of targets whose value is not boolean `false` is `0`. `sync-manifest.sh matrix` emits it as a JSON boolean for all 8 (a quoted string would be truthy). - Per-target `managed` / `locally-owned` lists byte-identical to `af85ae0` (95 entries); components block unchanged. - `bash distribution/sync-manifest.sh validate` → `Manifest valid: 34 components, 8 targets`, exit 0. - `bash distribution/sync-manifest.test.sh` → 147 passed, 0 failed. - `npm run test:runner-policy` → 242/242 against the merged manifest; `npm run lint:runner-policy` → `Runner policy passed.` - `bash harness/shell/run-tests.sh components/claude-lanes/claude-lanes.test.sh components/actionlint/actionlint.test.sh` → 2 files, 2 passed, 0 failed. - The lane-caller suite was mutation-tested rather than trusted for being green. Two independent ways it could have shrunk silently — the managed-list query returning nothing, and the per-component membership test ceasing to match — were each reintroduced in throwaway copies outside the worktree and both now surface as FAIL with a non-zero exit, where the second previously dropped four assertions and still exited 0. Per-target assertion counts are asserted, not assumed. - shellcheck (`--rcfile .shellcheckrc -x`), actionlint 1.7.12 over the repo, zizmor 1.26.1 over the repo plus both components (`No findings to report`), markdownlint, typos, and editorconfig-checker all clean on the changed set. Original pre-revision plan: - `bash distribution/sync-manifest.sh validate` reported `Manifest valid: 33 components, 8 targets`. - `yq eval -o=json distribution/sync-manifest.yml | node distribution/validate-sync-manifest.mjs` passes. - `sync-manifest.sh plan` over all five affected targets shows exactly the intended additions (claude-review-caller in all five; the security caller in claude-code-plugins only; automerge unchanged). - actionlint 1.7.12 over both callers materialized at their destination layout WITH ci-workflows' approved queue-suppression config exits 0; the negative control without the config reports exactly the one expected `concurrency.queue` syntax-check finding on the review caller. - markdownlint plus lefthook pre-commit gates (typos, editorconfig, gitleaks, markdownlint) green on commit. ## Related - Phase 3a of `melodic-software/ci-workflows` `docs/topics/claude-review-lanes/PLAN.md` (approval record items 1, 9, 10). - Gate 1: #284 (3c0 actionlint suppression). - Gate 2: Phase 3a0 sync-App `workflows: write` grant (org-owner action, lands via github-iac). - Canonical caller headers: ci-workflows `claude-review.yml` / `claude-security-review.yml` at v0.9.1 (`c136b27f404dd32ce3873f39a6f3443891d1c16e`). - ci-workflows#282 (`paths-file` input), ci-workflows#278 (empty selector output — motivates the hosted fallback), ci-workflows#238 (Phase 4 acceptance test covering selector-failure surfacing). No linked issue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 29, 2026
kyle-sexton
added a commit
that referenced
this pull request
Jul 30, 2026
) ## Summary Closes the claude-review-lanes Phase 3d rollout window by removing the 8 `automerge: false` keys and the window comment from `distribution/sync-manifest.yml` — the exact inverse of the window-opening commit (`69788e3`, standards#290). Removal, not `automerge: true`: the manifest documents absent-means-true as opt-out policy-as-data (`sync-manifest.sh:428`, `distribution/README.md:41`), and #290's own body pre-registered the post-restore check as `grep -c "automerge: false" == 0`. Preconditions all verified before branching: - Engine pin at `8202e03f` (arming gate `pull-request-number != ''`) on main. - Watchdog caller at the same `8202e03f` (standards#295) — the never-armed scan becomes load-bearing the moment this merges; its deadline (before restore) is satisfied. - Zero open `chore/standards-sync` PRs across all 8 targets (positive-control-checked). **Merge effect, deliberate:** merging is a push to main and fires a real sync run (benign — cardinality unchanged, engine at the fixed pin), all 8 targets enter the watchdog's scope, and sync PRs self-merge again once green. Combined with this repo's `babysit_loop_merge: c3-autonomous`, this removes the two-checkpoint shape #290 installed — the intended end state of closing the window. ## Test plan - `bash distribution/sync-manifest.sh validate` → `Manifest valid: 34 components, 8 targets`, exit 0 (stage the file first — validate reads the indexed object). - `bash distribution/sync-manifest.sh matrix` → all 8 targets `automerge=true` as JSON boolean (the behavioral proof). - `grep -c automerge distribution/sync-manifest.yml` → 0. - Independent fresh-context verifier: PASS ("byte-identical to `69788e3^` for automerge handling; the mechanical form matches the documented opt-out convention"), and it independently reached the removal-not-flip conclusion. ## Related No linked issue: this PR closes no GitHub issue. Related: standards#290 (opened the window), standards#295 (watchdog re-pin), ci-workflows `docs/topics/claude-review-lanes/PLAN.md` 3d pre-steps + Sanity Check (automerge-restore expectation). Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sets
automerge: falseon every one of the eight targets indistribution/sync-manifest.yml, plus one comment above thetargets:blockrecording that the window is temporary.
This is a temporary rollout-window gate. It MUST be reverted after Phase 3d
completes. Merging it is a prerequisite for #289 and #286.
automergeis opt-out policy-as-data —distribution/sync-manifest.sh:428-431treats an absent key as
true— so a target that omits the key keeps the armeddefault. Every target therefore has to carry the key explicitly for the window
to hold. No target opts out today.
Why the window is needed: the sync engine at
ci-workflows@ac223bbadds a stepthat arms squash auto-merge at PR creation, gated on
steps.cpr.outputs.pull-request-operation == 'created' && matrix.automerge(
standards-sync.yml:475). The same commit addspermission-workflows: writeto the per-target token mint, and this repository's
baseruleset carriesrequired_approving_review_count: 0. Composed, one merge here would writeworkflow content into all eight targets and self-merge with no human
checkpoint. Disarming
automergerestores the two-checkpoint shape: a mergehere proposes the sync PR, and a human merges it in each target.
Only the
automergekey is added. No target'smanagedorlocally-ownedcomponent list changes.
Test plan
bash distribution/sync-manifest.sh validate→Manifest valid: 32 components, 8 targets, exit 0 (both the bare CI form and the explicit--source-root . --manifest distribution/sync-manifest.ymlform).bash distribution/sync-manifest.test.sh→ engine suite green.bash distribution/sync-manifest.sh matrix→ all eight entries emit"automerge":falseas a JSON boolean (verified withjq '.automerge|type'==
boolean), sofromJsonyields a real boolean andmatrix.automergeevaluates falsy. A quoted
"false"string would have been truthy.yq '.targets[].automerge | tag'→!!boolfor all eight;sync-manifest.sh:433requires that tag.grep -c "automerge: false" distribution/sync-manifest.yml→8, matchingthe target count. The comment deliberately avoids the literal string so the
post-restore sanity check (
grep -c ... == 0) stays meaningful.Related
No linked issue — this is a pre-step of the claude-review-lanes rollout plan
(
ci-workflowsdocs/topics/claude-review-lanes/PLAN.md, Phase 3d pre-step i),tracked there rather than as a standalone issue.
Merge order: this PR first, then #289 (
ci: re-pin the standards-sync engine at ac223bb), then #286 (feat(distribution): add sync-managed claude review-lane caller components). Merging this PR is itself a push tomainand fires a realsync run; that run is safe because
mainstill pins the sync engine at0b45b9f, which has no arming step at all.