feat(autonomy): add C3 auto-merge cell with evidence predicate - #1694
Conversation
Closes #1646. Operator direction of 2026-07-27 supersedes the 2026-07-25 "stay at c2-mechanical" decision recorded in #1388, via the path #1388 itself prescribed: amend the guardrail contract first, then flip the seam. - guardrails.md matrix: C3 merge policy becomes "auto-merge ELIGIBLE after per-class promotion trigger; ships human-gated" (mirrors the C2 cell). - work-classes.md promotion table: new C3 auto-merge evidence predicate. - loop-lane-prompts.md: rung narrative updated to the amended contract. C4/C5 human-merge floor untouched; demotion remains automatic fail-closed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 393475d69b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…auto-merge cell Codex review findings on the contract amendment: - P1: schema documented only C2 auto-merge and the checker rejected both merge_policy.C3 "auto" and promotion_state.C3-auto-merge, so guided setup could not validate or activate the new cell. C3 joins AUTO_MERGE_ELIGIBLE and PROMOTABLE_CELLS, gets the same unratified-promotion gate as C2, and a c3-auto-unratified fixture exercises the gate (374 checks pass). - P2: rung narrative claimed c3-autonomous while this repo's tracked seam is still c2-mechanical at this commit; reworded to tie the raise to the ratified seam flip (#1693) landing with this amendment, and updated the merge-lane copy-block rationale to the post-ratification state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated security 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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 902b87b004
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…lands Codex P1 on the amendment: babysit-loop's rung partition derives merge eligibility from tracked rung + work class only and never resolves the effective promotion state, so directing operators to drop the override over-promised fail-closed demotion. Retain the override, state the wiring gap, and gate dropping the flag on #1695 plus the evidence predicate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fae728bb91
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ontract amendment Codex flagged twice that the rung narrative described c3-autonomous while the tracked seam still read c2-mechanical in this commit. Root cause was splitting this repo's ratification across two PRs; fold the one-line babysit_loop_merge flip into the amendment so contract, seam, and narrative agree at every commit. Staged seam PR #1693 becomes superseded by this PR; the other watched repos keep their seam PRs. Also rewraps a paragraph so a "#1695" reference no longer starts a line (MD018). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b492df439
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…d decision
The top-level {{MERGE}} guidance still cited #1388's decided-against-raising
as this repository's answer; point it at the 2026-07-27 raise instead, with
the same #1695 + evidence-predicate gate on dropping the override.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 890624ce81
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…-merge cell Codex P1: the C3 auto-merge predicate is earned on the C2 track record, but evaluation filtered contrary evidence by exact cell id, leaving C3-auto-merge effective-promoted after a C2 demotion event. Contrary evidence now also matches a cell's predicate prerequisites (PROMOTION_DEPENDENCIES), the demotion note names the prerequisite cell, work-classes.md documents the cascade, and a promoted-c2c3 + C2-revert evidence fixture pair proves both cells demote (376 checks pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f3b8f4a67
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ound unpromoted Codex P1: the cascade caught contrary EVIDENCE against the prerequisite but not a prerequisite already BOUND unpromoted (post-demotion binding update), so C3-auto-merge stayed effective-promoted over an empty evidence stream. The resolver now lowers a promoted dependent cell whenever any prerequisite entry is missing or not state "promoted"; a promoted-c3-only + empty evidence fixture pair proves it (386 checks pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6931bef7ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ion to dependents Codex P1 (mixed-epoch): a C2 contrary event landing after C2's ratified_at but before C3's later ratified_at demoted C2 while being pre-epoch-ignored for C3, leaving the dependent effective-promoted without its prerequisite. The resolver is now two-pass: pass 1 resolves every cell's own effective state in its own epoch; pass 2 lowers any promoted dependent whose prerequisite is missing, bound unpromoted, or effective-unpromoted, naming the reason. This subsumes the earlier event-scan cascade. Fixture pairs cover direct cascade, bound-unpromoted prerequisite, and the mixed-epoch case (392 checks pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f3e25653d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ratified Codex P1: with C3 ratified Jul 1, a C2 revert Jul 10, and C2 alone re-ratified Jul 20, the revert became pre-epoch for C2 and the current-state-only dependency check revived C3 without any C3 re-earn. Prerequisite events are now also judged in the DEPENDENT's epoch during pass 1, so the demotion sticks until the dependent itself is re-ratified; pass-2 current-state propagation is unchanged. A promoted-c2reearned-c3 + revert evidence fixture pair asserts C2 promoted, C3 unpromoted (398 checks pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b90f2b03ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const VERIFICATION_STRENGTH = { "not-required": 0, advisory: 1, blocking: 2 }; | ||
|
|
||
| const PROMOTABLE_CELLS = new Set(["C2-auto-merge", "C3-ai-review-blocking"]); | ||
| const PROMOTABLE_CELLS = new Set(["C2-auto-merge", "C3-auto-merge", "C3-ai-review-blocking"]); |
There was a problem hiding this comment.
Bump the autonomy plugin version to ship this cell
Existing autonomy-plugin installations will not receive the new C3 schema, validator, resolver, or guardrail references when users refresh the marketplace because plugins/autonomy/.claude-plugin/plugin.json remains at 0.11.3; this repository explicitly documents the manifest version as the plugin cache key and sole delivery vehicle (docs/MIGRATION-PLAYBOOK.md:371-375). Bump the autonomy manifest version and add the corresponding changelog entry so this newly accepted cell is actually delivered.
Useful? React with 👍 / 👎.
## Summary Raises `babysit_loop_merge` from `c2-mechanical` to `c3-autonomous` in the team-tracked loop-lane config. Per the loop-lane convention, merge-rung raises are seam-only reviewed changes; merging this PR is the ratification, per the C3 auto-merge contract amendment (melodic-software/claude-code-plugins#1694) and operator direction of 2026-07-27. The C4-structural / C5-untrusted-provenance human-merge floor is unconditional and unaffected. ## Test plan Config-only change. Verified `c3-autonomous` is a valid rung name per the plugin's config-resolution reference (`human-only` < `c2-mechanical` < `c3-autonomous` < `full-autonomy`) and that only the one value line changed. ## Related - Contract amendment: melodic-software/claude-code-plugins#1694 - melodic-software/claude-code-plugins#1309 - Loop-lane convention: https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/loop-lane/README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4 No linked issue in this repository — the operator-ratified rung raise is tracked fleet-wide in melodic-software/claude-code-plugins#1309. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary Raises `babysit_loop_merge` from `c2-mechanical` to `c3-autonomous` in the team-tracked loop-lane config. Per the loop-lane convention, merge-rung raises are seam-only reviewed changes; merging this PR is the ratification, per the C3 auto-merge contract amendment (melodic-software/claude-code-plugins#1694) and operator direction of 2026-07-27. The C4-structural / C5-untrusted-provenance human-merge floor is unconditional and unaffected. ## Test plan Config-only change. Verified `c3-autonomous` is a valid rung name per the plugin's config-resolution reference (`human-only` < `c2-mechanical` < `c3-autonomous` < `full-autonomy`) and that only the one value line changed. ## Related - Contract amendment: melodic-software/claude-code-plugins#1694 - melodic-software/claude-code-plugins#1309 - Loop-lane convention: https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/loop-lane/README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4 No linked issue in this repository — the operator-ratified rung raise is tracked fleet-wide in melodic-software/claude-code-plugins#1309. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ite (#1690) ## Summary Implements #1650: a deterministic, off-machine escalation notification for loop lanes, composed entirely from documented first-party mechanisms. - **Escalation record write (loop-lane convention v4.0.0, escalation-contract change → major bump).** Every escalation an autonomous lane files (`work-loop`, `babysit-loop`) now also creates `.claude/lane-escalations/<UTC-stamp>-<item>-<lane>.json` with the Write tool, in the same step that posts the machine-marked escalation comment and **immediately before** it — one new file per NEWLY filed escalation (suppressed by the marker read the step already performs, so standing escalations never re-fire), `loop-lane/escalation-record@1` shape, summary restating only the already-public comment text. - **Write ordering is contract.** The record write and the marker post are not atomic. Record-first, a stop between them loses the tracker comment, which the next cycle re-files — one duplicate notification, recoverable. Marker-first loses the notification *permanently and silently*, because the standing marker suppresses the record on every later cycle. The convention states the ordering with that rationale; both skills carry it at the site; both skills' evals assert it. - **Ignoring the record directory is a lane-start preflight, not a consumer obligation.** Nothing delivers a tracked ignore rule into a consuming repo, so both lanes gain cycle-shape step 0: if `git check-ignore -q .claude/lane-escalations/` reports the path unignored, append it to the clone's untracked `$(git rev-parse --git-common-dir)/info/exclude`. No consumer change, no tracked file touched, a no-op where the repo's own `.gitignore` already carries the rule, and it repairs an existing consumer that upgrades without noticing. A tracked rule added through a repo's lane-enabling adoption change stays the durable form. - **Out-of-band notification seam (consuming-repo config with a documented default).** The consuming repo's tracked `.claude/settings.json` registers a `PostToolUse` hook — `matcher: "Write"`, `if: "Edit(/.claude/lane-escalations/**)"`, `type: "http"` — that POSTs the hook JSON to a repo-chosen endpoint, secret carried in a header via `allowedEnvVars`. Deterministic (no model judgment), no claude.ai subscription or Remote Control dependency. The seam documents its real egress (full `PostToolUse` payload including session metadata — consumer opt-in), its silent-failure mode with a wire-time verification step, and its degradation (no hook → tracker + local notify unchanged; record files are inert exhaust). `PushNotification` and `slack`-plugin outbound are named as optional model-discretionary layers, never the deterministic leg. - **The seam binds to the session's project, never to the repository a lane targets.** Stated as a requirement rather than a preference: a lane scoped to another repository POSTs to the launching project's endpoint, so running from the target's checkout is required whenever that repository's endpoint must hear. Writing the record into the target's tree was rejected and the reasoning recorded — the `if` rule anchors at its own settings source, so such a record matches no loaded rule and fires nothing, trading a wrong-endpoint notification for silence. - **Stale-claim fix.** `plugins/autonomy/hooks/lane-notify.sh` no longer claims "there is no remote/Slack/push transport … (none exists as a marketplace primitive yet)"; comment-only change, behavior untouched (contract test 10/10). - **Fan-out grounding.** `plugins/autonomy/reference/runner/escalation.md` binds the severity fan-out's channel leg (deterministic http hook) and personal-push leg (`PushNotification`, model-discretionary) to shipped first-party transports. - Version bumps: loop-lane convention 4.0.0; `work-items` 0.27.0; `source-control` 0.35.0; `autonomy` 0.11.5. This repo also gitignores `.claude/lane-escalations/` (it dogfoods the lanes). ## Open design question resolved (with citation) **Does the harness support project-scoped `type:"http"` hooks configured per consuming repo?** Yes. Per https://code.claude.com/docs/en/hooks (fetched 2026-07-26): hooks are configurable in project `.claude/settings.json` (and every other settings scope), and the `http` handler type is supported on all hook events except `SessionStart`/`Setup` — `PostToolUse` included. Header values interpolate env vars only for names listed in `allowedEnvVars`; the `url` field never interpolates, which is exactly why the seam is consuming-repo settings rather than plugin-shipped config. Supporting `if`-rule semantics (file rules in `Edit(...)` form covering the Write tool; `/`-anchored at the settings source) per https://code.claude.com/docs/en/permissions (fetched 2026-07-26). A `FileChanged` hook was avoided as the issue directs: its matcher takes literal filenames (no path globs) and firing on Claude-written files is undocumented. ## Verification - Fresh-context verifier (opus, rationale withheld) audited the diff against the issue's acceptance criteria, the rejected-concept list, and live official docs: initial round returned 6 findings (3 blocking — egress disclosure, silent-failure mode, duplicate suppression), all fixed; re-verification returned **PASS** (all findings resolved, every new harness claim doc-supported verbatim, markdownlint clean), plus two Low wording-only follow-ups, both applied. - Three review findings from `chatgpt-codex-connector` fixed on this branch (P1 marker-only partial write → write ordering; P2 ignore-rule delivery → lane-start preflight; P2 webhook/target-repo binding → requirement stated, target-aware path rejected with reasoning). Preflight mechanics verified empirically in this checkout: `git check-ignore -v` resolves through the worktree, and `--git-common-dir` resolves to the clone's shared `.git`, whose `info/exclude` already carries entries of the same shape. - Gates: markdownlint-cli2 0 issues on all changed markdown; shellcheck clean on `lane-notify.sh`; `lane-notify.test.sh` PASS=10 FAIL=0; all three `plugin.json` manifests parse. Closes #1650 ## Related - Refs #1619 — the `source-control` 0.33.2 pre-compute fix that landed on `main`; its version lineage is the source of the recurring changelog conflict resolved on this branch. - Refs #1694 — C3 auto-merge cell landed on `main` this cycle, an adjacent autonomy guardrails surface; no overlap with this PR's escalation contract. - Refs #1657 — the read-trust boundary and rung-partition changes that landed on `main` as `work-items` 0.26.0 / `source-control` 0.34.0; this branch composes above them (0.27.0 / 0.35.0), retaining both sides' changelog entries. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…uests (#1781) ## Why A review that lands **after** a merge has nowhere to go: - the ruleset's `required_review_thread_resolution` is a **merge-time predicate** that already passed; - the babysit lane works *open* PRs, and a merged PR leaves that queue; - nothing on a merged PR surfaces its open threads — GitHub shows the merge, not the findings. Six findings — one **P1** — posted **46 seconds after #1720 merged** and sat unread for a day. They surfaced only because a later session happened to audit that merge batch. Nothing was bypassed; the gate was satisfied *because the threads did not yet exist*. The morning brief is the right home: read-only, unattended, and already where attention signals land. ## What it does Compares each unresolved thread's **first-comment timestamp** against the PR's `mergedAt`, and reports only threads the gate could never have seen. A thread that predates the merge was visible to the gate — that is an ordinary unresolved thread, not this failure mode, and it stays out. - **One line per PR, at that PR's worst severity, with a finding count.** Several findings on one PR are one thing to go look at; repeating the title per thread buries every other PR. Collapsing on the *worst* severity means a P0 sitting beside advisory findings can never be softened. - **Severity survives to the operator** — a stranded P1 must not read like a P3. - **`--stranded-days`** (default 3) — wide enough to cover slow bot review *and* an operator-absent weekend. ## It fails loud, not clear A GraphQL error document is well-formed JSON that simply carries no `data`. The extraction would yield an empty list and render **"every merged PR in the window is clear"** — an all-clear asserted from an answer never received, which is the same fail-open shape this section exists to catch. This is not hypothetical: a rate-limit error did exactly that during development. An API error now says explicitly that it is *not* an all-clear, and prints the message. Covered by a regression case. ## This is a standing leak, not a one-off Its **first live run** against this repository immediately surfaced four more stranded findings on other merged PRs — including a **P1 on #1694** (merged `05:04:45Z`, finding posted `05:05:20Z`, 35 seconds later) recording that a shipped `autonomy` cell **never reached installations**. ## Verification - `morning-brief.test.sh`: **30 → 63 cases, 0 failures.** - The **negative** cases carry the weight — a pre-merge thread, an already-resolved post-merge thread, and a merge outside the window must all stay silent, or the section is noise rather than signal. Plus: collapse-does-not-soften-severity, highest-severity-first, window-widening, and the API-error case above. - The fixture mirrors the real #1720 shape, including the 46-second gap. - `shellcheck -x` on script and test — clean. One `SC2016` is declared, not blanket-suppressed: the `$owner`/`$name`/`$endCursor` in the GraphQL query are server-side variables bound by `-F` and **must** reach the server unexpanded. - `node scripts/validate-plugin-contracts.mjs` — 43 setup skills, 2153 files, pass. - `npx markdownlint-cli2` on both changed markdown files — 0 errors. ### Live run — posted in full in the comments below A live run on the current branch found **44 merged PRs carrying post-merge findings in a five-day window: 0 P0, 10 P1, 34 P2.** Among the P1s: **#1503**, a guardrail-bypass fix whose own review landed unread, and **#1322** with 5 findings. Read the **second** comment for the authoritative figures — the first was produced by the pre-review severity logic and reported a false P0, which review then caught. No truncation warning fired, so the read is complete. The five-day window filter was spot-checked against `mergedAt` (a PR numbered #969 in a 5-day window looks wrong until you check: it merged `2026-07-25`, 4.2 days before the run). This is a far larger leak than the six findings that exposed it. ## Related Closes #1777 Refs #1720 Refs #1759 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…h an atomic claim (#2065) ## What `plugins/autonomy/hooks/lane-stop-gate.sh` bound an arm record to its first presenting session with a read-then-write, and honored the record from the value read *before* that write. Two Stop invocations presenting the same fresh arm id both read it unclaimed, both wrote, and the last `mv -f` won — so **both honored the arm for that event**, while the loser, possibly the legitimate lane, was refused on every later stop and **ran ungated from then on**. `mv -f` made the rename atomic; the read → modify → write around it was not, and `GATE_ARM_JSON` was assigned the pre-claim JSON unconditionally, so the record was honored without ever re-reading what actually persisted. ## How - The claim is an **exclusive create** — `set -o noclobber` on a `>` redirection, i.e. `O_CREAT|O_EXCL` — of a `<record>.claim` sidecar holding the owning session id. Same primitive `statusline-tee.sh` already uses, and its recorded reason for avoiding `flock` (absent on macOS) applies here too. Both the `umask` and the option are scoped inside a subshell; verified empirically that neither leaks to the caller. - `GATE_ARM_JSON` is assigned **only past the ownership verdict**. An unowned record contributes no config at all, which is what stops a replaying session from being honored. - A claim file exists only because some process won that create, so an **empty** one is that winner caught between its create and its write — not an ownerless record. Reading it in that instant would hand one fresh arm to every concurrent presenter and reopen the race a few microseconds wide, so the owner read is retried over a bounded budget. - **Fail direction preserved in both directions.** An unwritable store leaves no claim, and a claim whose owner never lands exhausts the budget; both **honor** the arm — the gate stays ON. A legitimate lane silently losing its gate is the harm, never an extra gated stop. Refusing a durably ownerless claim would make the record permanently unclaimable, which is the original harm in a new shape. - **Compatibility.** A record claimed before the sidecar existed carries its owner in the record itself and has no sidecar; that field stays authoritative, so an upgrade cannot let a second session claim a record already bound to a live lane. Nothing writes it any more. - `lane-stop-gate-arm.sh` clears the sidecar **before** it (re)writes a record, so a re-armed id starts unclaimed rather than staying bound to a dead session. The clear precedes the write deliberately: a crash between the two then leaves the old record with no claim (an extra gated stop) instead of a fresh record beside a stale claim, which would refuse the new lane on every stop until the record aged out. The record it writes is built fresh by `jq -n` and never carries an owner, so no owner can be orphaned. The gate's TTL sweep drops record and sidecar together. - The claim path carries **the record path's own `[[ -f ]]` asymmetry**. It is a second predictable name in the same store, and without a type guard a planted FIFO took the write with no reader and hung the entire Stop event — which the harness resolves by allowing the stop, i.e. an ungated lane on every attempt. Anything at that path this hook did not write now decides nothing and the arm is honored. - One spelling of the sidecar path (`gate_arm_claim_path` in `lane-stop-gate-lib.sh`) so the gate and the arm helper cannot diverge. ## Verification `bash plugins/autonomy/hooks/lane-stop-gate.test.sh` — **83 pass, 0 fail**, including six new cases: | Case | Asserts | | --- | --- | | 41 | a pre-claimed record is refused for a non-owner and honored for its persisted owner | | 42 | re-arming clears the previous lane's claim, and the relaunched lane can claim it | | 43 | a record claimed before the sidecar existed stays bound to its legacy owner | | 44 | six presenters racing one fresh arm id — exactly one is honored, and the persisted claim names that session | | 45 | an ownerless claim still honors the arm (fail direction: gate ON) | | 46 | a non-regular file at the claim path neither hangs the hook nor loses the gate | Every claim-ownership case pins the **exit code** as well as the decision; a stdout-only assertion would pass a hook that started exiting 127 in silence, which is the regression shape this effort has already hit once. Nothing here was reasoned about where it could be measured: - An **independent fresh-context verifier**, given the finding and the branch but not the author's rationale and told to refute, ran the *new* test file against `origin/main`'s hooks: **76 pass / 6 fail**, among them `6 of 6 concurrent presenters were honored for one fresh arm id`. The race reproduces on main and the new cases are genuine regression tests, not decoration. It also ran 150 × 6 function-level and 25 × 6 end-to-end concurrency trials against the fix: zero multi-winner, zero mismatch. - The **empty-claim window** was demonstrated, not inferred: with a pause injected between the exclusive create and the owner write, six of six concurrent presenters were honored. With the bounded re-read, that same injected pause yields one of six across repeated runs, and a pause deliberately exceeding the budget over-gates (four of four honored) rather than leaving any lane ungated. - The **FIFO hang** was demonstrated the same way: with the type guard removed from a scratch copy the hook never returns; with it, the hook completes and the gate stays on. Also green from the worktree root: changelog parity (`--check`, `--check-order`, `--check-bump` vs `origin/main`), shell-portability lint, `check-silent-skips.sh`, `check-changed-skills.sh origin/main`, `check-contract-clause-coverage.py`, `validate-plugins.sh`, ShellCheck, shfmt, markdownlint. ## Known and deliberate - Exactly one session ends up with the arm, so a **leaked arm id still strands the loser** — that is the first-presenter-wins contract, not the race, and it is unchanged by this PR. What changes is that the binding is now decided once, atomically, instead of by whichever writer happened to land last. - A **mid-lane downgrade** to a version that predates the sidecar would ignore a live claim, since 0.13.1 records the owner only in the sidecar. The upgrade direction is handled and pinned by case 43. ## Related - Review finding `PRRT_kwDOTCGFQM6WACHN` (#1865), P1, verdict REAL. - `PRRT_kwDOTCGFQM6T8HlP` (#1694) is already fixed and deliberately **not** folded in: its one open sub-ask (a missing C3 changelog line) harms no install and is parked for the operator. No linked issue --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…abysit-loop partition (#2566) Closes #1695 Minimal in-repo peel implementing the frontier-consensus ruling: trusted promotion-evidence resolution contract on the rung partition with fail-closed behavior when evidence is unavailable, untrusted, partial, or forgeable. Does not enable autonomous merge on unqualified evidence. Full three-arm resolver deferred; `--merge human-only` launch pin stays until the seam qualifies. ## Summary - **Contract:** `skills/babysit-loop/reference/promotion-evidence-resolution.md` — promotable cells (`C2-auto-merge`, `C3-auto-merge`), trusted seam requirement, fail-closed table, partition interaction - **Partition:** cycle-shape step 3 resolves effective promotion state before work-class eligibility; unavailable seam → every cell effective-unpromoted → no C2/C3 autonomous merge - **Config:** `config-resolution.md` notes the gate under merge dimension - **Gotcha:** SKILL.md documents that tracked rung alone is not merge permission - **Version:** `source-control` 0.53.24 ## Tests - Eval 10: promotion-evidence fail-closed at `c3-autonomous` tracked rung - Evals 2, 6–8 updated for promotion gate semantics - `test_skill_contract.py::test_babysit_loop_promotion_evidence_contract_is_wired` ## Related - #1694 — C3 auto-merge cell (Codex review origin) - #1682 — Finding 1 gotcha sequencing (tier-flooring vs invoke babysit-prs); no gotcha text added here per minimal peel scope - Frontier consensus ruling on #1695 (IMPLEMENT_NOW fail-closed contract; defer three-arm resolver) --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Summary
Adds the C3 auto-merge path #1646 identified as resolution shape 1:
plugins/autonomy/reference/guardrails.mdmatrix: C3 merge-policy cell changes fromhuman mergetoauto-merge ELIGIBLE after per-class promotion trigger; ships human-gated— mirroring the C2 cell wording exactly; all other cells unchanged.plugins/autonomy/reference/guardrails/work-classes.mdpromotion table: newC3auto-merge evidence predicate (≥ 20 autonomous C2 merges over ≥ 14 days with 0 demotion events, plus ≥ 10 autonomous C3 completions with 100% deterministic-gate pass, 0 human-reverted merges, 0 human-confirmed missed-blocking AI-review findings).prompts/loops/loop-lane-prompts.md: rung narrative updated — the "no C3 auto-merge promotion cell" and "stay at c2-mechanical (decision: raise babysit_loop_merge to c3-autonomous, or leave it — the guardrail matrix has no C3 auto-merge cell #1388)" claims are stale after this amendment; the existing "full-autonomy adds nothing over c3-autonomous" analysis is kept, still true.Predicate thresholds are suggested defaults; the org may bind lower. Ratifying seam flips ahead of the suggested evidence is an operator choice the contract records; automatic fail-closed demotion (any post-merge gate failure, human revert, or verification divergence) still applies.
C4-structural / C5-untrusted-provenance never promote — unchanged.
Operator direction of 2026-07-27 supersedes #1388's "stay" decision, via the path #1388 itself prescribed: amend the guardrail contract first, then flip the seam.
Closes #1646.
Test plan
Related
🤖 Generated with Claude Code
https://claude.ai/code/session_01PfjSBcWkzNFCydZQYsfUc4