Repository navigation
Conversation
… own command (#348) lint-gate.sh --update-baseline took --approved-in-chat as the user's yes, and the agent could type that flag itself. A rise (lint) or an accepted regression (conformance-check) is now written only against a row in .claude/baseline-approvals.tsv: the sha256 of exactly the proposed content, written by the new bin/baseline-approve.sh, which the user runs in a terminal. Claude Code denies it and the file to the agent; Copilot must ask; it refuses without a TTY. Every instrument write is recorded in .claude/baseline-writes.tsv, and gate-check prints "Baselines (human-approved?)": FAIL for a baseline matching neither record (blocks Stages 5 and 6), MANUAL for one predating the lock. Bench (tests/wave2/bench-baseline-lock.sh, master vs this branch): unapproved moves landing 10/10 -> 2/10 per baseline (the 2 are direct file edits, all flagged); legitimate re-baselines 4/4 -> 4/4 at one human command each. Field evidence: none yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…AIL on CRLF or merges (#375 review) - Bypass: `baseline-lock.sh record <root> <kind> <sha> tighten` after a hand edit turned gate-check green. Every write now saves its content under .claude/baseline-history/, and verify replays the log: a record looser than the one before it without an approval is FAIL, whoever wrote it. Direct `record`, redirection into the logs and file-tool edits of them are denied in Claude Code; Copilot must ask. - False FAIL: content is compared with CR stripped (CRLF checkouts). A looser baseline or record that git HEAD holds (a merge) reads MANUAL, not FAIL. - The Bash deny names baseline-approve.sh only, so git add/commit/log -p on the approvals log work again. - Docs: the approve command runs in a separate terminal window; Claude Code's `!` prefix has no TTY and is refused. Fixtures: test-baseline-lock.sh gains F1 (forge), X1 (CRLF), X2 (HEAD blob), X3 (real two-branch merge); all four fail on d18f65f. Bench gains a forge attempt per baseline. Field evidence: none yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…ite log (#375 confirmation review) - H1: committed rows of .claude/baseline-writes.tsv are a required prefix, so removing or truncating them is FAIL, never "predates the lock". lint-gate and conformance-check refuse an unapproved write unless verify says PASS (MANUAL too). - H2: a loosening is MANUAL only when a git merge produced it (a looser row from a merge's other parent, or merge-made baseline content). A one-parent commit of it stays FAIL. gate-check blocks Stages 5-6 on MANUAL as well as FAIL. - M1/M2/L1: the replay restarts from the latest row the user approved, so `approve --current` clears a forged mid-log row, a merge MANUAL and a missing snapshot (rule 7). - M3: an approval covers only the state it saw: it must be newer than every write (microsecond timestamps), so returning to an old approved state after a tighter write is FAIL and lint-gate will not reuse it. - L2: a pty-faked TTY is listed as a residual in baseline-lock.sh. Fixtures: test-baseline-lock.sh 28/28 (11 new cases; 9 fail on ad7a9cf). Bench gains an rm-the-log attempt per baseline and now measures Stage 5 blocks. Field evidence: none yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
This was referenced Oct 14, 2026
Merged
Merged
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.
Closes #348.
Direct lane
What changed and why (one paragraph):
lint-gate.sh --update-baselinetook--approved-in-chatas proof that the user said yes. The agent whose script caused the rise could type that flag itself, so "re-baselining is the user's call" ran on trust. Now any baseline write that is not plain-safe waits for the user. That covers a lint rule rising, a conformance row going OK -> not OK, and re-creating a deleted baseline. It also covers writing over a baseline whose verdict is anything but PASS. The refusal saves the proposal and printsbin/baseline-approve.sh <kind>. The user runs that command in a separate terminal window, reads the diff and typesapprove. That appends the sha256 of exactly that content to.claude/baseline-approvals.tsv; the re-run writes that content and nothing else.Every instrument write is recorded together with its content (
.claude/baseline-writes.tsvplus.claude/baseline-history/).gate-check.shprintsBaselines (human-approved?)from a replay of those records against their content:Both FAIL and MANUAL block Stages 5 and 6, and the instruments refuse to write until the user approves. The user's
approve --currentis the remedy for every non-PASS state: the replay restarts from that approval (rule 7).--approved-in-chatis still accepted so old command lines parse, but it is ignored.Why this mechanism (candidate 1, enforced with candidate 2's tool):
baseline-lock.sh record, redirection into either log, and file-tool edits of the logs and snapshots.askrule) leaves no evidence.What it does NOT protect against, plainly:
The list lives in
project-bin/baseline-lock.sh.Scope: the baselines inventoried:
lint-gate.sh.claude/lint-baseline.jsonconformance-check.shdocs/conformance/baseline.tsv--update-baselinecan accept OK -> STALE regressionsmodel-stamp.sh write passis agent-callable; that forgery gap deserves its own issue.gate-check --ack-protocol --approved-in-chatMeasurement (output of
bash tests/wave2/bench-baseline-lock.sh <origin/master worktree> <this branch>at b042e95, not hand-typed):Of the unapproved attempts, 4 (lint) and 4 (conformance) edit the baseline file directly (1 each then forges a write record with
baseline-lock.sh record … tighten; 1 each first deletes the write log and re-runs the update): no instrument can refuse an edit; the gate-check row is what catches it.Replayed: 12 + 12 unapproved and 4 + 4 legitimate attempts (lint + conformance) per tree, synthetic input; not a field measurement.
Field evidence: none yet. Every number here comes from replayed attempts on synthetic input. A field run is still owed.
HEAD:./<path>, which resolves relative to the project root. Not exercised separately.CHANGELOG.mdline, crediting the external reviewer feedback; updated in place each round.Checks:
render-routing.sh --checkpasses. Baseline words are 75,028 before and 75,028 after, so the always-loaded cost is 0.check-scripts,check-portability, the leak guard,check-no-private-citationsandcheck-pr-disciplineare all clean.Review (MERGE-AFTER-FIX) — fixed in ad7a9cf
record … tighten: records are now checked against snapshots of their content. I also added deny rules for a directrecordand for writes into the logs. Fixture F1.*baseline-approve.sh*, sogit addandgit log -pon the logs work.!prefix has no TTY and is refused.Confirmation review (HOLD) — fixed in b042e95
approve --currentclears it. The fixture uses a forged row that differs from the state the user later approves.approve --current, or on a later mainline write that is not looser..claude/is not gitignored byinit-project.sh, so snapshots are committed.approve --currentre-saves the snapshot and re-anchors the replay. Fixture: deleted snapshots give FAIL, then PASS after the approval.baseline-lock.sh; no code change.approve --currentfrom the user before the next re-baseline (fixture G4).Fixture proof:
bash tests/wave2/test-baseline-lock.sh <tree>The M3 gate half reads FAIL on ad7a9cf too, but for an unrelated reason: on that head, M1's forged row never clears, so every later state in that project replays to FAIL.
Bench, ad7a9cf vs b042e95: "changed without approval and NOT blocked at Stage 5" goes from 1/12 to 0/12 for each baseline. The 1 is the rm-the-log attempt, which read as a non-blocking MANUAL before. Every other row is equal.
Fixtures re-run on b042e95, each with its own usage argument:
test-baseline-lock28/28test-lint-gate-accept23/23test-conformance-baseline-scope7/7test-install-claude-permissions79/79test-sec014-notice24/24test-lint-receipt-revert55/55test-exec-lock-watch84/84test-conformance-per-brd-ledger19/19test-review-conformance-per-brd6/6test-verify-stack-blocked6/6test-bug03-gates,test-wrong-verdicts20/20,test-source-ledger55/55,test-bug04-subscripts30/30,test-closeout34/34,test-stage-p14/14Impact
bin/baseline-approve.sh <kind>in a separate terminal and typesapprove(bench: 0.0 → 1.0 extra steps per legitimate re-baseline). A project whose baseline predates the lock needs oneapprove --currentfirst. New block: gate-check rowBaselines (human-approved?)blocks Stages 5 and 6 on FAIL and on MANUAL;lint-gate.sh/conformance-check.shrefuse unapproved writes.--approved-in-chatis now ignored. Review status: HOLD.bench-baseline-lock.sh); review notesbin/gate-check.sh,project-bin/exec.sh(message text only),bin/lib/install-manifest.sh,bin/install-claude-permissions.sh+bin/install-harness-permissions.sh(new deny rules),agents/gate-agent.md. Merges clean onto master 868b3cb (git merge-tree). Conflicts with #374 inbin/lib/install-manifest.sh;gate-check.shmerges clean with #273 and #346. Platform: approve refuses without a TTY; CR stripped before hashing; not run on macOS or Git Bash. Reads.claude/lint-baseline.json(producer:lint-gate.sh),docs/conformance/baseline.tsv(producer:conformance-check.sh),.claude/baseline-approvals.tsv(producer:baseline-approve.sh, run by the human only),.claude/baseline-writes.tsv+.claude/baseline-history/(producer:baseline-lock.shvia the two instruments), and git HEAD's committed log.git merge-treevs 868b3cb and pairwisebin/render-routing.sh --checkon master 868b3cb vs this head (b042e95) merged onto 868b3cb viagit merge-treein a temporary worktreeagents/gate-agent.mdchanges its lint re-baseline instruction; situationalskills/lint-that-actually-runs.mdalso changes (A/B recommended).agents/gate-agent.md;skill-routing.tsvload classexec.sh: message text only.🤖 Generated with Claude Code
https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw