Skip to content

ci(distribution): disarm fleet automerge for the Phase 3d rollout window - #290

Merged
kyle-sexton merged 1 commit into
mainfrom
ci/disarm-fleet-automerge-rollout-window
Jul 29, 2026
Merged

ci(distribution): disarm fleet automerge for the Phase 3d rollout window#290
kyle-sexton merged 1 commit into
mainfrom
ci/disarm-fleet-automerge-rollout-window

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

Sets automerge: false on every one of the eight targets in
distribution/sync-manifest.yml, plus one comment above the targets: block
recording 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.

automerge is opt-out policy-as-data — distribution/sync-manifest.sh:428-431
treats an absent key as true — so a target that omits the key keeps the armed
default. 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@ac223bb adds a step
that 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 adds permission-workflows: write
to the per-target token mint, and this repository's base ruleset carries
required_approving_review_count: 0. Composed, one merge here would write
workflow content into all eight targets and self-merge with no human
checkpoint. Disarming automerge restores the two-checkpoint shape: a merge
here proposes the sync PR, and a human merges it in each target.

Only the automerge key is added. No target's managed or locally-owned
component list changes.

Test plan

  • bash distribution/sync-manifest.sh validateManifest valid: 32 components, 8 targets, exit 0 (both the bare CI form and the explicit
    --source-root . --manifest distribution/sync-manifest.yml form).
  • bash distribution/sync-manifest.test.sh → engine suite green.
  • bash distribution/sync-manifest.sh matrix → all eight entries emit
    "automerge":false as a JSON boolean (verified with jq '.automerge|type'
    == boolean), so fromJson yields a real boolean and matrix.automerge
    evaluates falsy. A quoted "false" string would have been truthy.
  • yq '.targets[].automerge | tag'!!bool for all eight;
    sync-manifest.sh:433 requires that tag.
  • grep -c "automerge: false" distribution/sync-manifest.yml8, matching
    the 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-workflows docs/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 to main and fires a real
sync run; that run is safe because main still pins the sync engine at
0b45b9f, which has no arming step at all.

`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

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@github-actions

Copy link
Copy Markdown

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
kyle-sexton merged commit 69788e3 into main Jul 29, 2026
43 checks passed
@kyle-sexton
kyle-sexton deleted the ci/disarm-fleet-automerge-rollout-window branch July 29, 2026 13:05
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant