Repository navigation
review: the repo's merge process in docs/process.md, locked, outside the decisions index [PROTECTED: owner merges] - #920
Conversation
…-21) The owner's final merge rule for this repo's own PRs was not written down in the design. A new short section 20 states it: the orchestrator merges every PR after an independent final check, the combined check and green required CI; the critical set also needs a second adversarial reviewer; the orchestrator runs the batch-merge procedure itself; reviewers and builders never merge; what still goes to the owner; the weekly summary of critical merges; and what the [PROTECTED: owner merges] title marker now means. Test (fails without): none, docs only. No test pins docs/design.md's sections. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: CHANGES NEEDED (round 1/2)Final check of head 1. Does §20 say what the owner said, and nothing else?
The batch-merge procedure compared with the batch-merge script (measured: I read the whole script):
2. The markerRequired change 1. §20 says the marker "means the PR is in the critical set." But the marker isn't applied by critical-set membership. It goes on every PR that touches a protected path, and protected paths include Measured with 3. Names, brand, paths
4. Contradictions elsewhere in docs/design.mdI grepped for "owner merges", "PROTECTED", "self-modif", "L3", "owner approv", "this repo", "engine repo", "critical", "adversarial", "strict" and "admin" (measured). Every hit describes how the harness merges a project's PRs: §8 review levels, the merge policy, batching's "Owner approval" bullet at line 417, and line 351's "not strict by default" recommendation for projects. §20's first paragraph scopes itself away from all of them. None contradicts §20 (inferred). Outside design.md, so not part of this check: 5. CI at
|
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Second adversarial review: CHANGES NEEDED (head c04ccfa)The question I tested: could a future agent that reads design.md as policy use §20 to justify a merge it shouldn't make? Yes. There are three routes, and two claims in the PR body are false. I compared every line with the owner's own words for the rule. Each claim below is labelled. Attacks
False claims in the PR body (principle 5)
Required changes (each one fails closed, and none loosens anything)
Suggested wording for items 1 and 2:
For the owner (a genuinely new trade-off): do you mean "Only bring me" to replace rule 10, the owner's OK for removing or weakening a check? Until you say so, item 6 keeps rule 10, which is the status quo. Refusals: none. |
… set Review round 1 of #920. The title marker goes on every PR that touches a protected path (a dependency bump included), so it can't mean the critical set: it now says the marker no longer means the owner merges, and the second adversarial review applies only to the critical set, which the orchestrator decides from the changed files. Also restores "physically" in the owner's words, and states the tree check as the batch-merge procedure does it: after each merge against that step's tested tree, at the end against the whole combined tree. Strict comes back on through an exit trap, on a normal or an error exit, not when the process is killed outright. Test (fails without): none, docs only. No test pins docs/design.md's sections. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 2, head
Verified at b3f1be3, after a rebuild (all measured):
|
…tside the decisions index Owner decisions on #920's adversarial review (2026-10-21). docs/design.md is indexed by heading as recorded decisions for the planner and plan-review agents (src/plan-decisions.ts DECISION_FILES), so a section there on how this repo's own PRs are merged could be cited to let the engine merge more. The rule now lives in docs/process.md, which the index never reads; design.md keeps only a pointer in its untitled intro, which the index drops. docs/process.md is locked like the design rules: its own self-modification part in src/review.ts ("the engine repo's merge process") and an engine-guards entry in review.yaml. This only adds a lock. The page states the rule fail-closed, as the review asked: both reviews on the current head, posted naming it, and redone on any push; who counts as independent; a conditional PASS is not a pass; green means every required check reported success on that exact head or tree; the critical set widened to the owner's seven areas plus every self-modification lock and the release-signing and engine-guards categories, decided by changed files, never the title marker, and when unsure it's in; changes to who may sign a release go to the owner; a batch stops on any mismatch or failure; the strict toggle is the orchestrator's only ruleset change; the owner list is for merges, and the owner's OK for removing or weakening a check stays; and the harness's merge policy, review levels and holds are unchanged. Test (fails without): test/plan-decisions.test.ts (process.md exists and no index entry carries the merge process), test/engine-guard-locks.test.ts and test/merge-policy.test.ts (docs/process.md is self-modification, L3, never auto-merged, under engine-guards), and the adversarial case merge-process-docs-only. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 3, head
Verified (measured):
|
Evaluator verdict: PASS (head
|
| Item | In docs/process.md | Result |
|---|---|---|
| Owner: merge every PR after the independent final check, the combined check and required CI | "Who merges", all three, each on the current head | Met (measured, by reading) |
| Owner: critical set needs a second, independent adversarial reviewer | "The critical set", first bullet | Met (measured) |
| Owner: you run the batch-merge scripts | "The batch merge" | Met (measured) |
| Owner: only bring me release signing, auto_merge per project, direction changes, steps that physically need my hands or GitHub admin rights | "For merges, the owner is brought only", with "physically" kept | Met (measured) |
| Owner: weekly summary of critical merges | Last bullet | Met (measured) |
Owner: widen the critical set to every self-modification lock plus review.yaml release-signing and engine-guards |
The 7 areas (word for word as round 1) plus both additions, "this page included" | Met (measured) |
| Owner: rule 10 stays | "This removes no other owner approval…" | Met (measured) |
| Owner: a separate process doc, not design.md | design.md §20 removed; a single pointer sentence in the untitled intro | Met (measured) |
| Adversarial 1: second review pinned to the head; a push needs both reviews again; who counts as independent; a conditional PASS is not a pass; each PASS is posted naming the head | All five present | Met (measured) |
| Adversarial 2: critical set widened; changed files decide, never the marker; when unsure it's in | Present | Met (measured) |
Adversarial 3: changes to who may sign a release go to the owner (.github/release-signers, scripts/verify-release-tag*) |
Present, and in the owner list | Met (measured) |
| Adversarial 4: green = every required check reported success on that exact head or tree; the same for the combined tree | Present. It also counts "skipped" as not green, which is stricter than the review asked. It matches the script (reqmissing needs conclusion == success) and blocks nothing today: no required check is skipped at this head |
Met, fail-closed (measured) |
| Adversarial 5: the batch stops on a mismatch, failed step or non-green check, and the rest wait for a new combined check; the strict toggle is the only ruleset change | Present | Met (measured) |
| Adversarial 6: "for merges"; rule-10 sentence; the harness is unchanged and can't be cited | Present (intro and owner section) | Met (measured) |
| Adversarial 7: PR body's three false claims corrected | "Corrections to earlier claims" | Met (measured) |
| Round 2, fix 1: the marker isn't tied to the critical set | Present. It needs the second review only if its changed files put it in the critical set | Met (measured) |
| Round 2, fix 2: "physically" | Restored | Met (measured) |
| Round 2, fix 3: the per-step tree vs the final combined tree | Present | Met (measured) |
| Round 2 optional: exit trap, not SIGKILL | Present | Met (measured) |
Loosened or added beyond these? No (measured, by reading the whole page). The only additions are stricter: "skipped" in the green rule, and "Reviewers and builders never merge", which round 1 accepted.
The batch procedure compared with the batch-merge script (measured: read the script in full):
- The trap is set before strict is turned off (lines 88-89), and
dieexits through it. - Every merge uses
--match-head-commit(line 113). - The per-step tree is checked at lines 118-119 and the final tree at lines 124-128.
- Every failure path calls
die. - There is no
--adminand no force push. The ruleset PUT keepsbypass_actors.
All of this matches the page.
One note, not a change to this PR (inferred): the script's preflight can resume a stopped batch from the merged prefix using the same combined check. The page says the remaining PRs wait for a new combined check, which is stricter. Whoever runs the script should rebuild the combined check after a stop, not rerun it.
2. Decisions index (src/plan-decisions.ts)
DECISION_FILESandDECISION_DIRSdon't namedocs/process.mdor anydocs/folder it could fall under. The pointer is at design.md line 15, under the level-1 title.sectionsOfdrops that section when deeper headings exist (measured, by reading).- At the head, the new test passes (
ok 165), along with the rest of plan-decisions, engine-guard-locks, merge-policy and adversarial: 165 tests, 153 pass, 0 fail, 12 todo (measured). - Fail-without (measured): I wrote the new test verbatim as a new file in a fresh base worktree at
41f6dd42(test/fw920-plan-decisions.test.ts). It fails with'docs/process.md is a real file'(expected true, actual false). On main the existence check fails first, so I also ran the test's index assertions against round 1's design.md (c04ccfa8, with §20) in a temp checkout. They fail ondocs/design.md § 20. How the engine repo itself is merged (owner decision, 2026-10-21)and pass on the head's design.md (measured). So the assertions do catch the leak the second review found.
3. The lock
- Only additions:
src/review.ts+3 -0 (one newSELF_MODIFICATION_PARTSentry, "the engine repo's merge process",docs/process.md), and.worklane/review.yaml+1 -0 (anengine-guardsentry). No existing lock, glob or assertion is changed or removed (measured:git diff origin/main...HEAD). The only other edited line in a test is thepresentlist, which gainsdocs/process.md. - engine-guard-locks and merge-policy: pass at the head (above). review, not-yet-files-locks, docs-guardrails and brand also pass: 35 tests, 0 fail (measured).
- Managed app:
docs/process.mdstays L0['docs'], withselfModificationPart(…, 'managed-app') === null. The PR's managed-app test checks this, and so does my fail-without file, which passes on main as well: no regression (measured). - Fail-without of the lock assertions (measured, new file
test/fw920-locks.test.tsat base):- the self-modification test fails: expected
"the engine repo's merge process", actualnull; - the engine-guards test fails: expected
'L3', actual'L0'.
- the self-modification test fails: expected
4. Adversarial case merge-process-docs-only
- On main (measured): the case, written as a new case file
test/adversarial/cases/fw920.jsonin the base worktree, givesnot ok 25 … merge-process-docs-only … waitswithexpected: false, actual: true. That means it merged with the evaluator fooled. - At the head:
ok 59(measured).
5. Other checks
- Changelog:
changelog.d/920-lock-merge-process-doc.mdsits under "Guardrail changes: what each one does", like the 16 other fragments. Its claims match what the tests measure (measured). - Typecheck:
npm run typecheckis clean (measured, through npmw.sh). - Denylist: "denylist: 1397 files clean" (measured).
- Brand: the brand test passes. Nothing in
src/hardcodes the product name; the diff adds only the lock entry (measured). - Size: measured with
npm pack --dry-run --jsonon base and head builds.- tarball: 1,470,176 → 1,470,274 (+98 B);
- unpacked: 4,926,629 → 4,926,952 (+323 B);
- 491 entries in both.
- Both are far under the budget (2,050,000 / 6,400,000). The body's "+200 B" was inferred; the measured unpacked delta is +323 B. That is a small correction, and no change is required.
git merge-tree --write-tree(measured), all exit 0 with no conflicts:- with origin/main →
631c58ed; - with Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886
e79b6cc7→d1a2a5db; - with Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910
7b00aa3c→deedcc7c. - Whether they work together is for the combined check (inferred).
- with origin/main →
- CI at
8cb562bb(measured, not rerun; check-runs on the exact SHA): all 8 required checks arecompleted/success. So are desktop, scope, installed-engine-config and the 8 test shards. The real-systemd, bootstrap and sign-in-probe jobs are skipped; none of them is on the required list. - Commits: all 4 are signed off, with no attribution trailers (measured:
git log). - Rule A: the tests use only
mkdtemptemp dirs and in-memory config; nothing can touch real state (inferred, by reading). - Protection: marked protected (title marker,
**Risk class:** protected,needs-owner), with real "What this can never do" and "Compromised component" sections. I checked the claims there against the code and tests: they hold (measured for the lock and the index, inferred for "no other reader": a grep ofsrc/finds only the lock glob). - Adversarial pass section: present in the body.
- Closes: there is no "Closes #N"; this records an owner decision with no issue (inferred). Not required.
For the merge (not a builder change)
By the rule this PR records, and already by the existing critical-set areas (it changes src/review.ts locks), this PR is in the critical set. The only adversarial review so far was on c04ccfa8. A second adversarial PASS naming 8cb562bb is needed before it merges (inferred from docs/process.md and the review history).
Refusals: none.
Second adversarial review: CHANGES NEEDED (head 8cb562b)Head checked: What holds (measured)
Attacks
Claims in the PR body
Required changes
Recommended (not blocking)
Refusals: none. |
…ock its test Second adversarial review of #920. A decisions file or folder entry that is a symlink to docs/process.md (docs/decisions.md -> process.md, or docs/decisions/0001-merge.md -> ../process.md) was indexed with the page's full text, as an L0 docs change, and the only backstop, test/plan-decisions.test.ts, was itself L0. Now recordEntries skips any record that is the checkout's docs/process.md by identity (device and inode, through symlinks), which also covers a hard link; a checkout without that file indexes as before. The test is locked under "the tests of the harness controls" and in review.yaml engine-guards, so removing it is L3. docs/process.md closes the stale-base route: every merge goes through the batch procedure (a single PR is a batch of one); strict up-to-date is confirmed on before any merge and after every batch, and nothing merges until it is; a combined check counts only if built on current main, and a stopped batch's remaining PRs need a new one. Also: the signer paths are examples of every place a release's signature is checked; the owner's explicit OK, with an exception or skip counted as a weakening; green is on the exact head or combined commit, and an unreadable or empty required list means nothing is green. Test (fails without): test/plan-decisions.test.ts "regression: a decisions file or folder entry that is docs/process.md under another name ...", and the two lock tests in test/engine-guard-locks.test.ts for test/plan-decisions.test.ts. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 4, head
Verified (measured):
|
Second adversarial review (round 5): CHANGES NEEDED (head cfc69f0)Head checked: What holds (measured)
Attacks
Claims that are false or overstated (principle 5)
Required changes
Recommended (not blocking)
Refusals: none. |
Evaluator verdict: CHANGES NEEDED (delta check, round 4, head cfc69f0)Head checked: 1. The test lock (measured)
2.
|
| # | Fixture | Previous head | This head |
|---|---|---|---|
| A1 | docs/decisions/0001-merge.md -> ../process.md |
indexed | skipped |
| A2 | docs/decisions.md -> process.md |
3 sections indexed | skipped |
| A3 | hard link decisions.md to docs/process.md |
n/a | skipped |
| A4 | chain docs/adr/0001-x.md -> ../hop.md -> process.md |
n/a | skipped |
| A5 | directory link decisions -> docs |
n/a | decisions/process.md skipped; only decisions/design.md indexed |
| A5b | two hops through another folder | n/a | skipped |
| A0 | the real checkout | n/a | no leak; same refs as the previous head |
A copy under another name is caught only by the locked test, and only when it contains the test's words (measured):
- A full copy in
docs/decisions/0001-merge.mdis indexed; the code doesn't catch it. The test that catches it istest/plan-decisions.test.ts"the engine repo's own merge process is not a recorded decision: docs/process.md is never indexed, and no indexed section of design.md carries it". It scans the repo's index forprocess\.md|engine repo itself is merged|critical set|orchestrator. - A paraphrased copy, with those four phrases swapped out, is indexed as
docs/decisions/0001-merge.md, and the test's pattern doesn't match it. This agrees with attack 1 of issuecomment-6307750538.
A symlink that leaves the checkout is NOT skipped (measured). This agrees with attack 3 of issuecomment-6307750538.
- A planning worktree sits under
<clone>/.claude/worktrees/<name>(theworktree.rootdefault, src/config/schema.ts:631).decisionIndexgets that worktree path (src/coordinator/decisions.ts:41). - I built a fixture: a clone with
docs/process.md, and a worktree inside it at.claude/worktrees/plan-1with its own copy of the page. The committed link isdocs/decisions.md -> ../../../../docs/process.md. - The head indexes
docs/decisions.md § Who merges,§ The critical set,§ The batch mergeand§ What goes to the owner. The link's target is a different inode from the worktree's own page, soidentitydoesn't match. - The same tree in a CI-shaped checkout gives only
docs/design.md § 8. Roles, because the link dangles there. So the locked test can't see this route either.
A checkout without process.md indexes as before (measured):
- With no process.md, a fixture with decisions files, a folder entry and symlinks to other files gives a deep-equal index on the head and on the previous head.
- With process.md present and no links, the index is also deep-equal.
- A dangling folder entry throws ENOENT on both builds. That isn't new.
A stat error fails open (measured; principle 1):
identity()returns null on any error, andisProcessDocthen treats every record as not the page.- Fixture A8b:
docs/not searchable (mode 0600), plus a rootdecisions.mdhard-linked todocs/process.md. The head indexesdecisions.md § Who merges,§ The critical setand§ The batch merge. - A record's own stat error is harmless today. For a decisions file,
present()is false, so it's skipped. For a folder entry,readFileSyncthrows, as on the previous head. - Reachability: git can't record directory modes or hard links, so a PR alone can't build A8b (inferred). It is still the unsure case, and it should skip.
3. process.md wording (inferred, read against the owner's words quoted in issuecomment-6307233001)
| Point | Text at head | Result |
|---|---|---|
| Every merge goes through the procedure | ":37 Every merge goes through it: a single PR is a batch of one." | Met |
| Strict is confirmed on before any merge and after every batch | ":39 before any merge, and after every batch, the orchestrator reads the ruleset and confirms strict up-to-date is on. If the exit trap didn't turn it back on, nothing merges until it is on again" | Met. One wording nit, below |
| A combined check counts only on current main | ":11 built on main as it is now … A combined check built on an older main doesn't count" | Met |
| A stopped batch needs a new combined check | ":43 The PRs left need a new combined check, built on main as it is then, before any of them merges" | Met |
| Explicit OK; an exception is a weakening | ":54 the owner's explicit OK first. Adding an exception, allowlist entry or skip to one is a weakening." | Met |
| An empty or unreadable required list means nothing is green | ":13 If the required list can't be read or is empty, nothing is green." | Met |
| Nothing loosened against the owner's words | The owner's list keeps "physically" and is scoped "for merges". The signer rule widens to every place a signature is checked. The rest only adds steps. | Met: tightenings only |
- Nit (not blocking): taken literally, ":39 before any merge … confirms strict is on" clashes with ":40 strict up-to-date is turned off for the batch". The merges inside a batch run with strict off. A literal reader would refuse to merge, which fails closed, so this isn't a loosening. Suggested wording: "before a batch starts (a single PR included), and after every batch".
- I didn't re-judge the rename gap that issuecomment-6307750538 raises (its change 2).
4. Fail-without, tests, checks, size, merges, CI
Fail-without (measured; method (b), new files only):
- I wrote two NEW files into a fresh worktree at the previous head
8cb562bb:test/fw920-plan.test.ts, the new regression test verbatim;test/fw920-locks.test.ts, the head's engine-guard-locks verbatim.
node --import ./dist/test/isolate.js --test dist/test/fw920-plan.test.js dist/test/fw920-locks.test.jsgave 4 tests, 1 pass, 3 fail, each for the right reason:- the lock regression: message
test/plan-decisions.test.ts(inferred from the line: theselfModificationPartassertion); - the review.yaml test:
test/plan-decisions.test.ts: only docs/tests, 2 lines/'L0' !== 'L3'; - the index regression:
decisions.md § Who merges | decisions.md § The critical set | docs/decisions.md § Who merges | docs/decisions.md § The critical set | docs/design.md § 8. Roles | docs/decisions/0001-merge.md | docs/decisions/0002-csv.md.
- the lock regression: message
- On the head, the same tests pass inside the run below.
Named tests (measured): the 11 files ran on the head, with a scratch HOME, scratch state dirs and host-tool stand-ins (engine-guard-locks, merge-policy, plan-decisions, adversarial, review, brand, docs-guardrails, changelog, changelog-fragments, changelog-fragments-selfcheck, not-yet-files-locks). Result: 240 tests, 228 pass, 0 fail, 12 todo. This matches the builder's figures.
Typecheck and denylist (measured, both run through npmw.sh):
run typecheck: no errors;run check:denylist: "denylist: 1397 files clean".
Size (measured: npm pack --dry-run --json --ignore-scripts, the same way on all three builds, without the web UI built):
| Build | Tarball (bytes) | Unpacked (bytes) |
|---|---|---|
| origin/main | 1,470,176 | 4,926,629 |
| previous head | 1,470,274 | 4,926,952 |
| head | 1,470,597 | 4,928,020 |
- Against main, the head adds +421 to the tarball and +1,391 unpacked; 491 entries on both.
- That is within
PACKAGE_BUDGET(2,050,000 / 6,400,000). - The PR body's "about +1,200 … Not measured" can now cite these numbers.
git merge-tree --write-tree (measured, each one clean):
- with origin/main:
a4323005; - with Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886
3b23fa41:65e442a7; - with Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910
4a232bdb:63cd47c5.
Commits (measured): all signed off, with no Co-Authored-By or other attribution trailer. The PR body has no attribution line.
CI at cfc69f0 (measured, gh pr checks, not rerun):
- passed: agentshield, dco, denylist, desktop, installed-engine-config, new-deps, push-gate, scan x2, scope,
test (ubuntu-latest, 24), and shards 1/4 (24), 2/4 (22, 24), 3/4 (22, 24) and 4/4 (24); - pending:
test shard 1/4 (ubuntu-latest, 22)andtest shard 4/4 (ubuntu-latest, 22).test (ubuntu-latest, 22)hasn't reported yet. - Not green yet under process.md:13.
Protection
- The PR is marked protected (title marker, risk class) and is in the critical set by its changed files (measured above: L3 self-modification and engine-guards).
- Rule A: the new test writes only under
tmpdir(). Nothing touches real state (inferred, from reading the test).
Required changes
-
Fail closed in
recordEntries, in code (principle 1; measured above). Index a record only when both of these hold:- its real path (
realpathSync) is inside the checkout's real path; docs/process.md's identity could be read, or the page is absent (ENOENT).
On any other stat or realpath error, skip the record. Add fail-without tests for:
- the out-of-checkout fixture: a worktree nested in a clone,
docs/decisions.md -> ../../../../docs/process.md; - the unsure case: for example,
docs/process.mdas a symlink loop (ELOOP) beside adecisions.md, which needs no chmod.
This drops a managed app's record that links outside its checkout. That's a tightening, so say it in the PR body.
- its real path (
-
Close the copy class by design. Code identity can't catch a partial or paraphrased copy (measured, A6b). The by-design fix is the round-5 review's change 1, which I concur with (inferred): lock every
DECISION_FILESandDECISION_DIRSpath, andsrc/plan-decisions.ts, as self-modification andengine-guardsin the engine repo. Then any PR that adds text to the planners' index is L3 and in the critical set, whatever the text says. -
Correct the overstated claims (principle 5):
- process.md:3, "never includes it, even through a symlink or hard link";
- the src/plan-decisions.ts:87 comment, "A copy of its text is held by test/plan-decisions.test.ts". Only a copy containing the test's four phrases is held, and only inside the checkout;
- the changelog fragment's "never includes it";
- the PR body's "The symlink route is closed by design";
- the PR body's "What this can never do: the only code change adds a self-modification glob". Round 4 also changes
recordEntries.
Recommended (not blocking)
- process.md:39: "before a batch starts (a single PR included)", per the nit above.
- PR body, Package size: cite the measured +421 / +1,391 bytes.
Refusals: none.
… index fails closed The index code could not recognise a copy of docs/process.md under another name: a partial or reworded copy in a new docs/decisions/*.md was indexed and cited as a recorded decision, at L0 (round 5 adversarial review, attack 1). A record linking out of a planning worktree nested in its clone reached the clone's copy (attack 3), and an unreadable docs/process.md made every record count as "not the page" (round 4 delta check). - Every decision record path (DECISION_FILES, DECISION_DIRS, each folder itself and everything under it) and src/plan-decisions.ts are self-modification in the engine's repo, and under engine-guards in review.yaml. The lock reads the index's own lists, which now live in src/review.ts (src/plan-decisions.ts re-exports them; importing it from src/review.ts would pull it and src/events/redact.ts into the guard's pinned import tree). A managed app's records keep their categories (engineSourceGlob). - recordEntries indexes a record only when its real path is a record path inside the checkout, it is not the page, and both could be told; when the page exists but can't be read, no record is indexed. - docs/process.md: what keeps the page out of the index, said as it is; both paths of a rename count, and the engine's own computeLevel decides; the combined check's base must still be main's tip at the first merge; the newest run of each required check; "before a batch starts". Test (fails without): test/plan-decisions.test.ts (a nested worktree's link out, a link to a non-record file, an ELOOP page, an unsearchable docs/ with a hard link), test/engine-guard-locks.test.ts (the records and the index code are L3 self-modification and engine-guards, read from the index's lists), test/merge-policy.test.ts, and two locked-paths.json cases. 11 of 12 fail at cfc69f0; the one that passes is the managed-app no-change check. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 5: head 41343a7This answers issuecomment-6307750538 (second adversarial review) and issuecomment-6307787151 (round-4 delta check). The details are in the body's "Round 5" section.
|
Evaluator verdict: CHANGES NEEDED (delta final check, round 5, head 41343a7)Head checked:
Two blocking items:
Everything else asked for in round 4 holds. 1. Decision records and src/plan-decisions.ts are L3 self-modification and engine-guardsLock compare, origin/main against the head (measured). I compared 1423 paths: every tracked file, plus 24 record paths, nested paths and case variants. Each was checked under this repo's review.yaml, the example's and a bare one, in absent,
The adversarial review's routes, re-run with
The delta's routes, re-run with
2. The deviation: one list, and the GUARD_TREE reasoning
3. recordEntries
Managed-app effect: a tightening only, with one exception (measured). Over the 36 fixtures in eval-920-r4/fixtures, origin/main against the head, the head indexes nothing that main didn't. The one exception is A8d, a dangling folder entry:
No text becomes citable that isn't a real record at a record path, so this isn't a guard loosening (inferred). It does make the PR body's "index fewer records, never more" untrue for that case. See required change 3. The one changed assertion for the weakening gate is in
4. The 13 new NOT_YET_FILES entriesThe entries are:
5. process.md wording (inferred, read against the owner's words quoted in issuecomment-6307233001)
Line 23 detail:
6. Fail-without, re-measuredMethod (b), measured. I wrote NEW files into a fresh worktree at
Result: 12 tests, 11 fail, 1 pass.
Each failure is for the right reason:
7. Other checksNamed tests on the head (measured). I ran 12 files: plan-decisions, engine-guard-locks, not-yet-files-locks, merge-policy, review, adversarial, brand, changelog, changelog-fragments, changelog-fragments-selfcheck, docs-guardrails and plan-mode.
Typecheck, denylist and size (measured, through npmw.sh):
Commits and protection (measured):
CI at 41343a7 (measured,
Required changes
Recommended (not blocking)
Refusals: none. |
…t is only added to by code docs/design.md is a decision record and the design rules' file, so round 5 listed it in two parts. selfModificationPart names the first, while test/coordinator.test.ts walks every part's globs and expects that part's name: CI shard 1/4 failed on Node 22 and 24. Each glob now stays in the first part that lists it (onePartPerGlob), so a path has one name. The lock is unchanged: the records part is still built from the index's lists, and docs/design.md is still locked, by the design rules' part. docs/process.md: the owner's subjects define the critical set. The engine's computeLevel can only add a PR to it, never take one out, and an unsure or unreadable result means critical (round-5 delta check). Test (fails without): test/engine-guard-locks.test.ts "each self-modification glob is in exactly one part": 1 of 1 fails at 41343a7 (docs/design.md in two parts). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 6: head 1d11ea3This answers issuecomment-6308270567. The details are in the body's "Round 6" and "What remains" sections.
|
Second adversarial review: CHANGES NEEDED (head 1d11ea3)Head checked: The route (A1): a decision record that no PR addsThe lock holds git changes to the record paths.
Attack table
Required changes
Recommendations (not blocking)
What this can never do / Compromised component, line by line
Refusals: none. |
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: PASS (delta final check, round 6, head 1d11ea3)Head checked:
1.
|
| Base → head | Paths | Looser | Other changes | lockedCategories lost (absent / engine / managed-app) | Added |
|---|---|---|---|---|---|
merge base 41f6dd42 → 1d11ea38 |
1423 | 0 | 108 (all tighter) | 0 / 0 / 0 | 16 / 16 / 0 |
origin/main d2a549f4 → 1d11ea38 |
1436 | 0 | 108 | 0 / 0 / 0 | 16 / 16 / 0 |
previous head 41343a76 → 1d11ea38 |
1423 | 0 | 0 | 0 / 0 / 0 | 0 / 0 / 0 |
- Round 6 changes no observable answer (measured, last row).
docs/design.mdwas already named "the release procedure and the design rules" at 41343a7, because the first part wins. The CI failure came only from the coordinator test walking each part's globs.
Fail-without (measured, method (b)):
- I wrote the new engine-guard-locks test verbatim as a NEW file,
test/fw920r6.test.ts, into a fresh worktree at41343a76. - Result: 1 test, 1 fail,
a glob in two parts, with+ ['docs/design.md'] - []. This matches the builder.
The weakening hold (measured). I ran weakeningOf on 41f6dd42..1d11ea38, using eval-920-r6/hold886.mjs:
| Hold code | Tip | Result at 1d11ea3 | Result at 41343a7 |
|---|---|---|---|
main d2a549f4 |
none | SELF_MODIFICATION_PARTS: the line that declares it is removed or changed, plus guard-lines = [ and ];. The holds from the 41343a7 column remain |
NOT_YET_FILES declaration changed, the superset selfModificationPart assertion line, and engineSourceGlob guard-lines. No SELF_MODIFICATION_PARTS hold |
#886 c1f13341 |
c1f13341 |
Same as main's row, plus #886's table-used on engineSourceGlob |
Same as main's row, plus #886's table-used on engineSourceGlob (ENGINE_TREE and ENGINE_ROOT_FILES). No SELF_MODIFICATION_PARTS hold |
- So the table is flagged already, by main's hold. Main registers
SELF_MODIFICATION_PARTScentrally as never-shrink. With Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 merged, the flag is the same. - This is expected and correct (inferred).
- The hold reads a changed declaration line as a possible decoy.
- A function wrapped around a never-shrink list can remove entries, and the line reader can't see that. Waiting is the fail-closed answer.
- This PR already waits for several reasons: it is critical by its files, L3 self-modification, and it grows NOT_YET_FILES. So the flag adds no new wait.
- After this lands, Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886's reader still reads the table (measured,
eval-920-r6/table.mjs).tableLinesreads 105 lines, from the= onePartPerGlob([line to]);.- With Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886's
// check-table: never-shrinkmark placed above the new declaration,registrationsOfstill registers the table as never-shrink.
- The PR body says this flag was "inferred". It is now measured.
2. process.md:23 adds to the critical set and never narrows it (inferred, read against the owner's rule)
| Owner's rule | Line 23 and the list below it |
|---|---|
| The seven areas | Listed as "subjects". A PR is in when its changed files touch any of them |
| All locked self-modification paths | Listed (SELF_MODIFICATION_PARTS). computeLevel putting a file under self-modification also puts the PR in |
review.yaml release-signing and engine-guards |
Listed, process.md included. The same two categories from computeLevel also put the PR in |
| "Changed files decide" | Line 34: "The changed files decide, never the title marker". Both paths of a rename or copy count |
| "Unsure means critical" | Line 23: if it's unsure whether a file is under a subject, or the code's result can't be run or read, the PR is in. Line 34 says it again |
- "The code can only add a PR to the set, never take one out" closes round 5's reading.
- The three examples (
scripts/lib/signing.mjs,src/update-drain.ts,src/updater-setup.ts) are named as in the set. The code alone doesn't classify them that way. - No sentence makes the code's lists stand in for the owner's subjects. Nothing narrows the set.
3. CI at 1d11ea3 (measured: gh pr checks 920, and gh run view for every run)
- Every run's headSha is
1d11ea38, at attempt 1, so nothing was rerun. - All required jobs pass. That includes:
test shard 1/4on Node 22 (job 119045901958, 8m41s) and Node 24 (job 119045901964, 9m58s);- shards 2/4 to 4/4 on both Node versions;
test (ubuntu-latest, 22/24);- dco, denylist, push-gate, new-deps, scope, scan and agentshield.
- The real-systemd and bootstrap jobs were skipped, as on other PRs.
- Nothing is pending.
4. Other checks
-
Named tests on the head (measured, through run.sh): engine-guard-locks, merge-policy, not-yet-files-locks, plan-decisions, review and adversarial gave 198 tests, 186 pass, 0 fail, 12 todo.
- None of these files sets its own PATH (grep).
- coordinator.test.ts was not run locally. CI shard 1/4 ran it and it passed.
-
Typecheck, denylist and size (measured, through npmw.sh):
- typecheck is clean;
- the denylist reports "1397 files clean";
pack --dry-runagainst the merge base: tarball 1,470,176 → 1,472,145 (+1,969), unpacked 4,926,629 → 4,932,851 (+6,222). This matches the body.
-
Weakening gate, every changed or removed existing test line in the whole PR (measured,
git diff 41f6dd42 1d11ea38 -- test/):Removed or changed line Judgment Imports in engine-guard-locks and plan-decisions Each replacement is a superset engine-guard-locks present3 entries added, none removed The selfModificationPart(...) === nullpath list5 entries added in front, every old entry kept The NOT_YET_FILES declaration It grows by 13 entries, each paired with a lock. Disclosed in the body as a loosening (principle 9), as in round 5 - Round 6's only test-line change is the import line, which adds
SELF_MODIFICATION_PARTS. - Nothing is loosened beyond the disclosed NOT_YET_FILES entries.
- Round 6's only test-line change is the import line, which adds
-
Body claims (inferred, checked against the code):
- "
onePartPerGlobdrops only a glob's repeat listing, never its only listing": true (lock comparison above). - The corrected A8d claim in "What this can never do": accurate.
- A10 is listed under "What remains".
- "
-
Commit (measured):
1d11ea38is signed off and has no attribution trailer. The body has no attribution line.
5. Main moved, and how this PR merges with other open PRs (measured: git merge-tree --write-tree)
| Merged with | Result |
|---|---|
origin/main d2a549f4 (batch 3: #887, #916, #894, #929; the body still says 41f6dd42) |
clean, tree aad5e631 |
#910 aae9db3e |
clean, tree 09cec8f2 |
#886 c1f13341 |
conflict in 2 files: test/merge-policy.test.ts (the NOT_YET_FILES declaration, as before) and src/review.ts (new this round) |
- No main merge is needed for conflicts.
- Batch 3 shares only
docs/design.mdwith this PR, and that file auto-merges. - Batch 3's
test/review.test.tschange addssrc/guardrails/access.tsandsrc/hook-deadline.tsto GUARD_TREE. This PR doesn't change which modulessrc/review.tsimports (inferred). - The combined check covers the merged result.
- Batch 3 shares only
- The src/review.ts conflict with Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886:
- Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 adds
// check-table: never-shrinkaboveexport const SELF_MODIFICATION_PARTS ... = [, and this round changed that line to= onePartPerGlob([. - To resolve it, keep the mark directly above the new declaration line. The table then still registers and reads (section 1, measured).
- Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 adds
Recommended (not blocking)
- Update the body's "Merge-order note":
- Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 is now
c1f13341, and the conflict is in bothtest/merge-policy.test.tsandsrc/review.ts. Keep each mark above its new declaration line. - origin/main is now
d2a549f4. - The weakening-hold side effect is now measured. Main's hold already flags it, as "the line that declares it is removed or changed".
- Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 is now
- A10, the case-variant follow-up from round 5: open the issue.
No required changes.
Refusals: none.
…read from git The lock holds git changes to the decision record paths, but recordEntries read the working tree: a file a setup step, an install script or a pooled worktree left at a record path, untracked, was indexed and citable with no PR adding it (round-6 adversarial review, A1). - recordEntries reads the records git tracks at the plan's base commit (HEAD when none is given): git ls-tree at that commit, regular files (100644/100755) at a record path only, the text from the blob (cat-file --batch). An untracked file, an uncommitted edit, a committed symlink or a submodule is no record. Replace refs are not read; a rev that isn't HEAD or a full commit id, a checkout that isn't its repository's top, or any git failure indexes no record. The working-tree checks stay as a second check. - The plan step passes its base commit, so a setup step that commits in the worktree adds nothing either. - src/plan-decisions.ts is a guard file for the weakening hold (an addition). - docs/process.md: what keeps the page out of the index, said as it now is; the orchestrator merges every PR but the signing ones that go to the owner; the reviewer who clears a conditional PASS is as independent as the review; the worktree setup inputs are named under setup scripts. Test (fails without): test/plan-decisions.test.ts (untracked, staged, edited and linked records; read at the base commit, past a later commit and a replace ref; an untracked record alone) and test/engine-guard-locks.test.ts (a removed line in src/plan-decisions.ts raises the weakening hold): 5 of 5 fail at 1d11ea3. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 7: head 6edea7bThis answers issuecomment-6308710896 and folds in the non-blocking items of issuecomment-6308732220. The details are in the body's "Round 7" section.
|
… instructions From the round-11 second review: - planner and plan-reviewer runs load the agent's project instructions and config from their checkout, which reaches every planner as instructions and isn't named by the critical set; it is now an open route (#973); - the list's lead now covers text presented to planners as a recorded decision or as instructions, and says the task text and the checkout are data planners read by design; - #956 names the real answer surfaces: an owner on any surface, a writer on the dashboard, in a chat thread or in a comment. The CLI answers as the owner and the engine's user can run it, which is #969; - process.md:4 says an unreadable page leaves no record indexed while decisions already made stay citable, as the changelog already says. Test (fails without): none, wording only. The named docs, changelog and guard tests pass (120/120). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict (delta check, round 11): PASS on content at
|
| Item | Status | Evidence |
|---|---|---|
| 1. #956 covers the answer; "once an owner or a listed writer answers it"; note on #956 | Fixed | process.md:9 now says "indexed with its question and its answer", that the answer can be agent-written and is "neither capped nor redacted", and "once it is answered (by an owner, or by a writer through the CLI or a comment)", pointing to #969 (measured: diff). The code matches: madeEntries caps and redacts the question only; the answer goes in as is (src/plan-decisions.ts:300, measured by reading). #956 has a comment widening it to the option text and saying an answer-only fix leaves that open (measured: gh issue view 956) |
| 2. Event-log route (A2) | Fixed | process.md:10, #969 (open: "decisions index: trust only answers recorded through the answer path"). It names the engine's user, agents and setup steps when no agent user is configured. No file name or steps (measured: grep, section 4) |
| 3. Checkout-copy route (A3) | Fixed | process.md:11, #970 (open: "plan brief: give planners the committed record text, not the checkout copy"). process.md:4's "add no record" is scoped "(to the index; what planners read in the checkout is an open route, below)" (measured: diff) |
| 4. Body: setup-step "What holds", Planner line, "What remains" | Fixed | The Planner line names #969 and #970 and points to the list. The setup-step "What holds" is scoped to the index and points to #970, and "What doesn't hold" names #962 and #969. "What remains" adds #969 and #970, extends #956 to the answer, and calls its lines highlights of the list (measured: reading the body) |
I re-ran the second review's probe (adv-920-r10/probe.mjs, re-pointed at this head, fixtures and in-memory events only). All three routes still reproduce, as expected for listed open routes (measured):
- A1: 1 entry,
decision q1, 781-character title carrying the option text, in the planner's brief and citable; - A2: an answer appended with a non-owner
by, no role and no audit entry is indexed (MARKER-FORGED-QUESTION -> MARKER-FORGED-ANSWER); - A3: the index holds
MARKER-COMMITTED-TEXTwhile the checkout holds the edit, and the index header still says "the files are in this checkout".
Round-10 delta check (issuecomment-6310344340):
| Item | Status | Evidence |
|---|---|---|
1. src/merge-policy-weakening.ts:211 comment |
Fixed | Now: "records committed at the base, never docs/process.md; the routes it doesn't close are listed under "Open routes into the decisions index" there." (measured: diff) |
| 2. process.md:9, "once the owner answers it" | Fixed | See item 1 above. Re-running the delta's C1 probe with the answer's role set to writer: 1 entry, carrying the page's text, citable; asked-only gives 0 entries (measured). So "answered (by an owner, or by a writer …)" is accurate |
| Optional: process.md:13 pointer | Done | "(a copy of its text: see the open routes above)" (measured: diff) |
2. The diff changes no code logic (check 1)
git diff --stat d13857d9 4b50017c(the two non-merge commits): the changelog fragment, docs/process.md,src/merge-policy-weakening.ts(+2 −1) andsrc/plan-decisions.ts(+2 −2) (measured).src/plan-decisions.ts: one hunk inside thePROCESS_DOCJSDoc block (* That holds except through the open routes …). Both removed lines were added by this PR in round 10 (measured: the diff ofd13857d9's own round). Comment only (measured, by reading the hunk).src/merge-policy-weakening.ts: one hunk; it replaces this PR's own//comment line with two//lines. Against origin/main the file's diff is@@ -208,6 +208,9 @@, three added lines (two comments and'src/plan-decisions.ts',) and no removed line (measured:git diff origin/main f672543c -- src/merge-policy-weakening.ts). Additions only, so still within the registry permission; it stays a critical digest.- Tests: no test file changed in the range, so the weakening gate has no test lines to judge, and there is no fail-without to re-measure (measured: diffstat).
- Commits: all three are signed off, with no Co-Authored-By or "Generated with" trailer (measured:
git log).
3. The merge re-derives exactly (check 2)
git merge-tree --write-tree 4b50017c 9f9533aa gives cc4ff684c071dd8e3eeeda6ab7875d329132a044, and f672543c^{tree} is the same id. The merge's parents are 4b50017c and 9f9533aa, and 9f9533aa is origin/main now (measured).
4. The one-list rule and the #707 rule (checks 3 and 5)
The list (process.md:7-11) names #962, #956 (question and answer), #969 and #970 (measured, by reading). Its lead now also covers "routes around the index to the text planners read beside it".
Nothing outside it claims completeness (measured: grep of the added lines of git diff -U0 origin/main f672543c -- docs changelog.d src, and of the body, for only through|no other|nothing else|reads only|only committed|can't reach|can't cite|one list|any route|open route|#962|#956|#969|#970, every hit read):
- process.md:3, :5 and the critical-set setup-step sentence each carry the open-routes qualifier; the setup-step sentence now names plan brief: give planners the committed record text, not the checkout copy #970, and decisions index: pin the base it reads to the forge's branch #962 and decisions index: trust only answers recorded through the answer path #969 as the engine's user.
- The fragment's guardrail bullet and the
PROCESS_DOCdocstring give decisions index: pin the base it reads to the forge's branch #962 (and Decisions index cites agent- or reporter-written question text as an owner decision #956) as examples ("such as", "for example") and point to the list. Thedecisions.tscomment names decisions index: pin the base it reads to the forge's branch #962 and points to the list. - In the body, the Intent, "Loosen a merge rule", "Make the harness merge more", the Planner line, the builder line and "other instances" all carry the pointer. Every "(decisions index: pin the base it reads to the forge's branch #962, Decisions index cites agent- or reporter-written question text as an owner decision #956)" next to a pointer is gone.
- Optional, not required: the fragment's Upgrading bullet still leads with "reads only committed records". It is about which records, and the same bullet says "Plans can still cite the decisions already made", so it doesn't read as a claim about routes (inferred). The body's round-7 heading "the index reads only committed records" is history, in the same sense.
#707: I grepped process.md, the fragment, the body and the PR's added src/ lines for event-log file names (jsonl, ndjson, events/, .log), config keys and commands (insteadOf, remote.origin, alternates, git config, pushurl, .git/config, commondir, set-url, FETCH_HEAD, WORKLANE_). The only hit is the source file src/events/redact.ts in the body's round-5 import note, which isn't an event-log name (measured). #969 says only "the instance's event log" and what writing it allows. No steps (inferred, by reading).
5. process.md against the owner's merge rule (check 4)
No merge-rule line changed in this round. The diff touches only :3-13 (the two checks and the list) and the setup-step sentence of the critical-set rule (measured: diff). Reading the page at the head (inferred, by reading):
- Who merges: the orchestrator merges after an independent final check, the combined check and green required CI on the current head.
- Critical PRs: they also need a second, independent adversarial review on the same head, and any push needs both reviews again.
- What goes to the owner: only release signing and changes to who may sign,
auto_mergeon, direction changes, and steps that need the owner's hands or GitHub admin rights. - The critical set: widened (the code can only add a PR; when unsure, the PR is in), and the changed files decide, never the title marker.
6. Other checks (measured, at the head, with a temp HOME, TMPDIR, state dir and git config, and stand-ins first on PATH)
npm run typecheck: clean.npm run check:denylist: "denylist: 1416 files clean".- Named tests: plan-decisions, docs-guardrails, changelog, changelog-fragments, changelog-fragments-selfcheck, engine-guard-locks, merge-policy, review and not-yet-files-locks: 144 tests, 144 pass, 0 fail. None sets its own PATH (grep).
- Size: not re-measured. docs/ and changelog.d/ aren't in the package (
filesin package.json, measured); docs/process.md grew 9350 → 10450 bytes (measured:git cat-file -s), but isn't shipped. The only shipped change is a few comment bytes indist/src, and CI's package-files test covers the budget (inferred).
7. Merge-tree (git merge-tree --write-tree, measured)
- origin/main
9f9533aa: clean. - Clean with the briefed heads of Adversarial suite: attack cases for doubt, the off switches, the stop, weakening, evals and outside-plan (#707) [PROTECTED: owner merges] #957, Bench: bench pull pulls each task's image by pinned digest and puts its base tree, from the image, in the held-out store (#748 slice 4) [PROTECTED: owner merges] #947, Adversarial suite: attack cases for the agent guards, push guard, push gate, commit scan and redaction (#707) [PROTECTED: owner merges] #932, Redaction: one shared list of token shapes, prefixed dashboard keys, no own redactors (#889) [PROTECTED: owner merges] #921, Receipts: signed, chained merge receipts anyone can verify offline (#717 slice 1) [PROTECTED: owner merges] #918, Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910, setup: an Anthropic API key is a first-class agent login (add-project, credentials.sh api-key, doctor) [PROTECTED: owner merges] #904, Setup: the machine helper's sudoers rule follows the engine, rewritten on its own only when it narrows; --replace-sudoers for the rest (#898) [PROTECTED: owner merges] #902, Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #893 and Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746.
- Conflicts:
src/review.ts: git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944d29ae46e, git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #9276aa0853b, Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903abcd79e4, Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #89968873520;src/review.tsandtest/merge-policy.test.ts: Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886dac9dcd8;- git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 and git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 also conflict in
src/push-check.ts, and Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903 insrc/coordinator/base.ts, but git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944, git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 and Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903 conflict with origin/main alone too (measured), so those extra files are theirs to refresh.
- Heads that moved since the brief: git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 is now
541c6424, Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899c63db3b3, Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #8935f5413afand Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #88675368b22. At the live heads: Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #893 is clean; git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 and Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899 conflict insrc/review.tsonly; Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 insrc/review.tsandtest/merge-policy.test.ts. All four are clean against origin/main alone (measured). - These are the same adjacent-addition conflicts as round 10 (inferred, from the files). The combined check must cover them.
8. CI at the head (I reran nothing)
CI never finished at f672543c, so it isn't green there (measured). Run 39561190109 (headSha f672543c) was still in progress when the branch moved. denylist, desktop and agentshield passed. dco, push-gate and three Node 24 test shards were cancelled, and the other shards had no result. installed-engine-config, new-deps, scan and scope passed in their own runs.
The PR head moved while I was checking: it is now 062beb8f ("docs: add #973 to the open routes and scope the list to decisions and instructions"). That commit sits on f672543c and changes only docs/process.md (+4 −3) (measured: git diff --stat f672543c 062beb8f). I didn't review it: it's outside this check's brief.
Required changes
None to f672543c.
Before merge (for the orchestrator):
- a delta check of
062beb8fagainstf672543c; - the second adversarial review's PASS, naming the head that is current then;
- green required CI at that head;
- the combined check, built on
9f9533aaor later, covering thesrc/review.tsconflicts in section 7.
Refusals: none.
Second adversarial review, round 12: CHANGES NEEDED (head
|
| # | Attack | Result | Label |
|---|---|---|---|
| A1 | Role prompt assembly for the planner and the plan reviewer | Both get only the engine's built-in prompt. planExtra() is exactly defaultRolePrompt('planner'). planRun passes no system prompt, and the reviewer gets defaultRolePrompt('plan-reviewer'), so no project role file reaches them. The goal planner does take the project's role file, but that file is L3 (agent-instructions), the goal planner gets no index, and its output is task text. Closed. |
measured (probe on planExtra, and computeLevel), the rest inferred (reading src/coordinator/decisions.ts planRun/planReview, src/coordinator.ts splitGoal) |
| A2 | Everything else in the planner's brief | planStep builds the brief from the task text plus four extras: the built-in prompt, decisionsMade(index), the owner's revise note and the send-back note. Learnings, corrections and research don't reach it. |
inferred (reading planStep) |
| A3 | The revise note ("The owner asked for a revised plan: …") | planStep reads the last plan.decided for the issue straight from the event log and puts its note into the planner's brief as the owner's words. On every legitimate surface only an owner can give the note (plan decisions are owner-only). Nothing checks who wrote the logged event, though, so whatever can write the log can hand the planner instructions in the owner's name. Unlisted. #969's entry names only "a citable decision", and #969's acceptance (a forged answered event not indexed) would let #969 leave the list while this stays open. |
inferred (reading planStep and Base.events); log writability as measured for #969 |
| A4 | Console messages to a live planner or plan-reviewer run | Every coordinator run, planner and plan reviewer included, goes through the wrapper that hands its input to the console (src/coordinator/base.ts, the onControl in d.runner.run). The console acts on any live run, with no role filter, and delivers a message as the run's next user turn. The rule lets owners and writers message a run. It also takes the request's own word for who sent it: in a scratch state dir, a request from a non-owner name that claims to be the owner was accepted, one claiming to be a writer was accepted, and one claiming neither was refused. So a writer on the dashboard, and whatever can write the instance's state (the engine's user, as with #969), can put instructions into a planner or plan-reviewer run while it plans. Unlisted. |
trust step measured (scratch probe calling takeRequests and mayRequest); delivery to planner runs inferred (reading src/coordinator/base.ts, consoleRequests in src/coordinator.ts, turnEnded in src/runner.ts) |
| A5 | The send-back note | The plan reviewer's free-text reason (up to 500 characters, tied to a real index entry) goes to the planner as "Your previous plan was sent back: …". The reviewer writes it, and it reads only the task text, the checkout and the listed routes. So it adds no new source, but it can carry data planners read by design to the planner framed as review feedback. | inferred (reading readPlanReview, sendBackNote) |
| A6 | Environment passed to planner runs | Committed project env drops every CLAUDE_* and ANTHROPIC_* name: in a probe, only the harmless name survived. Auto memory is forced off in the agent env. A project's settings file may only add hooks and permission rules, or the run is refused. Closed. Project hooks that add context fall under #973's "config", and every .claude/ path is L3 in this repo. |
measured (probes on safeProjectEnv and computeLevel); hooks inferred (reading projectSettingsRefusal) |
| A7 | MCP servers and plugins offered to planner runs | --strict-mcp-config is set. A server reaches a run only through the broker, per role and approved by root, and the agent sees the approved snapshot of its instructions and tool descriptions, never the live one. Plugins come from root's per-role allowlist and are hash-checked before every run. These are owner inputs, not a route around the reviews. |
inferred (reading src/mcp/broker.ts, src/mcp/snapshot.ts, src/plugins/run.ts) |
| A8 | The engine's own hooks in planner runs | The session-start hook returns nothing. Post-tool context comes only after an edit or write tool, and those are denied to planners. Closed. | measured (PLAN_DISALLOWED); hook output inferred (reading src/hook.ts) |
| A9 | Instruction files the run's --setting-sources project might still load: user-level memory in the run user's Claude config dir, and a top-level local memory file |
In this repo the local memory file at the top is L0 (docs only), while every other project instruction path probed is L3. Whether a planner run loads either file is not measured. If one does, the local file makes #973's "a change to it is L3" false, and user-level memory would be a route across runs for anything running as that user. | L0 measured (computeLevel probe); loading is a guess (no real agent CLI run, per the brief) |
| A10 | Each sentence about what the index or the page can or can't do: process.md, the changelog fragment, the PROCESS_DOC docstring, the GUARD_FILES comment, the decisions.ts comment |
They match the code. The code is unchanged since c0df669a. decisionIndex is made decisions plus committed records, so #973's "can't be cited" holds. :4's "indexes no record … (decisions already made stay citable)" matches the changelog and decisionIndex. |
inferred (reading), with the code identity measured |
| A11 | #973's "L3, but no subject of the critical set names it" | Holds: the CLAUDE.md files and every .claude/ path probed are L3, with no self-modification part. The exception is A9's local memory file. |
measured (computeLevel and selfModificationPart probe) |
| A12 | process.md merge-rule lines against the owner's rule | They match. The orchestrator merges after an independent final check, the combined check and green required CI. A critical PR also needs a second independent adversarial review on the same head, and any push resets both. Only signing, auto_merge on, direction changes and hands or admin steps go to the owner. The critical set is widened, the changed files decide, and when unsure a PR is in. |
inferred (reading) |
| A13 | #707: exploit steps in public text | None in process.md, the changelog fragment or the round-12 body section. Each entry says only what a route allows. | inferred (reading) |
3. Required changes
-
A4: add console messages to live planner and plan-reviewer runs to "Open routes", as their own entry, with a new issue. Say, at the level of the other entries and with no file names or steps (Public adversarial suite in CI: the merge gate refuses every hostile PR case (prompt and delimiter injection, rename and copy tricks, poisoned .claude settings and hooks, credential smuggling), with the existing guard tests folded in #707), that:
- owners and writers may message a live run, and the message reaches it as instructions;
- the coordinator takes the request's own word for who sent it, so whatever can write the instance's state can send one in the owner's name.
A by-design fix for the issue could be to give planner and plan-reviewer runs no console input, and to have the coordinator check the sender's role itself.
-
A3: widen decisions index: trust only answers recorded through the answer path #969's entry (and decisions index: trust only answers recorded through the answer path #969's acceptance), or add a separate entry. The same log writes can give the planner an owner's revise note, which it reads as the owner's instructions. decisions index: trust only answers recorded through the answer path #969's fix as specified would not close that.
Neither is a regression, and neither needs an owner decision: both are list entries under principle 5, with fixes in their issues.
4. Recommendations (not blocking)
- A5: one sentence noting that the reviewer's send-back reason reaches the planner as feedback, and that its sources are the task text, the checkout and the routes listed.
- A9: a probe like the plugins' P2, to show whether planner runs load the run user's own memory file and a top-level local memory file. If they do, add them to planner runs: load no project-level agent instructions from the checkout #973, and lock the local file with the other agent instructions.
- A7: optionally, one clause in the lead saying that root's per-role plugin and MCP approvals are owner inputs, as the task text is data.
- Outside the lead's scope: the same log writes can mark a logged plan approved, and
planStepthen hands that plan to the worker without a planner run or plan review. That belongs in decisions index: trust only answers recorded through the answer path #969's issue.
Refusals
None.
) Four reviews in a row found routes the list left out. The latest two are messages into a live planner run, where the sender's role is taken on trust, and the owner's revise note, read from the event log without a check of its author. So the list's lead now says these are the known open routes, found by review and not proven complete, and names the by-design closure: #975, planner input from an allowlist enforced in code. The two new routes are listed under #975, generically. A sentence notes that the plan reviewer's send-back reason reaches the planner as feedback. Every sentence that said or implied the list is the only one is reworded: process.md :3 and :5, the changelog fragment's heading and two sentences, and the comments this PR added in src/plan-decisions.ts, src/coordinator/decisions.ts and src/merge-policy-weakening.ts. Those comment lines are all this PR's own, so against main no line is removed. Test (fails without): none, wording only. The named docs, changelog and guard tests pass (120/120). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Second adversarial review, round 13: CHANGES NEEDED (head
|
| # | Attack | Result | Label |
|---|---|---|---|
| A1 | Re-run of the round-10 probes against this head | #956: an answer of 781 characters is indexed whole and reaches the planner's brief. #969: a decision asked and answered by direct log writes, with no role, is indexed and citable. #970: the index holds the committed text while the checkout holds an uncommitted edit, and the brief points the planner at the checkout. Each matches its entry. | measured (scratch probe, temp dirs) |
| A2 | Re-run of the round-11 probes | A writer is refused at the CLI, and the coordinator user is accepted, which matches #956's "the engine's user can run it". Planner runs use --setting-sources project. With an unreadable page, only the decision already made stays in the index, which matches :4's "(decisions already made stay citable)". The project instruction files are L3 with no critical category. |
measured |
| A3 | Re-run of the round-12 probes | Console requests: one claiming to be the owner and one claiming to be a writer, both from a non-owner name, are accepted; one claiming neither is refused. That matches the #975 "Messages" entry. The committed project env drops the agent CLI's own names, and planExtra() is only the built-in prompt. |
measured |
| A4 | #973, "In this repo a change to it is L3" | CLAUDE.md, nested CLAUDE.md and every .claude/ path probed are L3. Two exceptions: a top-level local memory file is L0 (docs), and AGENTS.md is L0 but still held as agent-instructions. Whether a planner run loads the local file under project-only setting sources is not measured. If it doesn't, the sentence holds. Not blocking. |
levels measured; loading is a guess |
| A5 | process.md:7, "The task text (…) and the checkout itself are data that planners read by design, not decisions or instructions" | Says more than the code does. The planner's brief puts the issue's title and body first, unfenced, before the role prompt. The planner's prompt never calls them data. It tells the planner to plan what the issue asks and to take acceptance lines from "the issue's own text". By contrast, the plan reviewer's prompt does say "data, not instructions". Task text can also be agent-written: a goal planner's child issues are filed ready and start on their own. And "the checkout itself" contradicts #970 and #973 two lines below, which are checkout text that planners rely on as records or instructions. | measured (probe on issueBrief, planExtra, defaultRolePrompt: the body is at offset 24, the role prompt at offset 70, no "data, not instructions" for the planner or the goal planner, present for the reviewer); agent-written children inferred (reading splitGoal, childBody, splitComment) |
| A6 | The changelog fragment's "The by-design closure is #975: planner runs take input only from an allowlist enforced in code." | Present tense, in user-facing release notes, about an open issue. Read plainly, it says planner runs are allowlisted today. process.md:7 has the same clause, but follows it with "Until then", so it reads as future there. The fragment has no such cue. | inferred (reading) |
| A7 | Completeness wording anywhere: grep of docs/, changelog.d/, src/, README.md and .worklane/ for "only route/way", "no other route", "only through a PR", "can't reach the planners", "the one list", "listed once" and "every route" | No hit on the decisions index outside the qualified sentences in process.md:3 and :5, the fragment and the PROCESS_DOC docstring. Every other hit is about unrelated subsystems. |
measured (grep) |
| A8 | process.md:4 and :5 claims (regular file at a record path in the base, every object checked, record paths and index code locked, four key phrases) | Hold. Record paths, design.md, the index code and its test are L3 with self-modification and engine-guards. The test's pattern has four phrases (test/plan-decisions.test.ts:155). |
measured (levels probe, tests); reading for the phrase count |
| A9 | Merge-rule lines against the owner's rule | They match: | inferred (reading) |
| the orchestrator merges after the independent final check, the combined check and green required CI (:22–26); | |||
| a critical PR also needs a second independent adversarial review on the same head, and any push resets both (:32–33); | |||
only signing, auto_merge on, direction changes and hands or admin steps go to the owner (:62–66); |
|||
| the critical set is widened and the changed files decide (:34, :44). | |||
| :67 keeps the owner's standing rule on removing checks, which is not a merge route. | |||
| A10 | #707: exploit steps in public text | None. Each entry says what a route allows, with no file names, keys or commands. | inferred (reading) |
| A11 | process.md:16, the plan reviewer's "known inputs" | It leaves out the plan under review, but the planner writes the plan from the same inputs, and "known" hedges it. Holds. | inferred |
| A12 | design.md:461 (main's text, not changed here): the index has "the sections of the decision records in the checkout" | Stale since this PR: the index now reads the base commit. It claims less than the code does, not more. Optional fix. | inferred (reading) |
3. Required changes
-
process.md:7 (A5): correct the task-text sentence. Suggested wording:
"The task text (the issue's title, body and done-when list) is the planner's assignment: it tells the planner what to plan, and the code doesn't mark it as data. The checkout's code is data that planners read by design. Neither is a decision record."
Then either add agent-written task text to the list under planner runs: take input only from an allowlist enforced in code #975 (item N1 below), or say plainly that the list doesn't track the task text. Don't call it "not instructions".
-
Changelog fragment (A6): mark planner runs: take input only from an allowlist enforced in code #975 as future. For example: "The by-design closure is planner runs: take input only from an allowlist enforced in code #975 (open): planner runs will take input only from an allowlist enforced in code." Optionally do the same in process.md:7.
Neither is a regression or a new trade-off: both are principle-5 corrections. The re-check after the fix can be wording-only.
4. New routes for #975 (not blockers, stated generically)
- N1. Agent-written task text. Some issues an agent writes are filed ready and start without an owner step, for example the child issues of a goal split. The next planner reads that text as its assignment. The goal planner's own inputs include the checkout routes (plan brief: give planners the committed record text, not the checkout copy #970, planner runs: load no project-level agent instructions from the checkout #973), so text can pass from one planner run to another as a task.
- N2. Text that project instructions or config pull in by reference. The referencing file is L3, but the referenced file keeps its own level, which can be L0 (a docs file probed is L0). After one L3 change adds the reference, later changes to the referenced text reach every planner at the lower level. No such reference exists in this repo today.
- Measured: a docs file's level, and that the repo's
CLAUDE.mdhas no reference today. - Inferred: the reference behaviour, from the agent CLI's docs.
- Measured: a docs file's level, and that the repo's
- N3 (carried from round 12, still unlisted). Instruction files that live outside the checkout. One is the run user's own user-level memory. Another is a top-level local memory file in the checkout, which is L0 (A4). Whether planner runs load either is not measured (guess).
Refusals
None.
From the round-13 second review: - docs/process.md said the task text and the checkout are "data, not decisions or instructions". The issue text is the planner's assignment, first and unfenced in its brief, so the sentence now says that, and that only the checkout's code is data read by design. Neither is a decision record; - three more known routes go under #975, generically: task text an agent wrote, text that project instructions or config pull in by reference, and instruction files outside the locked set that planner runs may load (a guess); - the changelog fragment and process.md mark #975 as open ("will take input only from an allowlist"); - docs/design.md's plan-citation rule said the index takes the records "in the checkout"; it now says "committed at the plan's base", as the code does. Test (fails without): none, wording only. The named docs, changelog and guard tests pass (120/120). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict (delta check, rounds 12–14): CHANGES NEEDED, PR body only, at
|
| Where | Result | Label |
|---|---|---|
| docs/, changelog.d/, src/, README.md, .worklane/: grep for "975", "only route/way", "no other route/way", "only through a PR", "can't reach the planners", "the one list", "listed once", "every route", "proven complete" | No completeness claim about the decisions index. Every hit on it is qualified: process.md:3, :5 and :7, the fragment's :9–:11, the PROCESS_DOC JSDoc and the GUARD_FILES comment. #975 appears as "(open)" with "will" (process.md:7, fragment :10), or as a pointer. The other hits are about unrelated subsystems (dashboard routes, push paths and the like) |
measured (grep) |
| PR body: the same grep, plus "allowlist" and "by-design closure" | One present-tense hit. Round 13's section, the second bullet under "The list says what it is", reads: "It names the by-design closure: #975, planner and plan-reviewer runs take input only from an allowlist enforced in code." Read plainly, it says runs are allowlisted today, the same problem as round 13's A6. The other hits are fine: the Intent says "(open: … will come …)", "What remains" says "#975 (open)", and the Planner line says "The by-design closure is #975", which has no tense | measured (grep of gh pr view --json body) |
| Round 14's adversarial-pass row "Present-tense #975 anywhere?" | It is labelled "measured (grep)" and says the body is clean. The hit above makes that false (principle 5) | measured |
3. Each route is stated truly and generically (check 3, #707)
| Check | Result | Label |
|---|---|---|
process.md:7, task text. I re-ran round 13's brief-probe.mjs, pointed at this head |
The planner's brief has the body at offset 24 and the role prompt at 70. No "data, not instructions" for the planner or the goal planner; the reviewer's prompt has it. The new sentence ("the planner's assignment … the code doesn't mark it as data") matches | measured |
| process.md:13 (#975, messages). I re-ran the round-12/13 console probe, pointed at this head, in a scratch state dir | Claims owner: ACCEPTED. Claims writer: ACCEPTED. Claims nothing: refused. Matches "takes the request's own word" | measured |
| process.md:15, task text an agent wrote | splitGoal files every child with the ready label, with no owner step. Matches |
inferred (reading src/coordinator.ts:724-775) |
| process.md:16–17 (by reference, instruction files outside the locked set) | Stated as the round-13 review found them. :17 carries its own "a guess, not measured" | inferred |
| process.md:9 (#956 surfaces) and :4 ("indexes no record … decisions already made stay citable") | Unchanged since round 13's measured re-run. They match | inferred (unchanged text) |
| #707 | The new entries name no file, setting, key or command. They say only what each route allows | inferred (reading) |
| Issues named | #956, #962, #969, #970, #973 and #975 are all open, so "(open)" and "will" are right | measured (gh issue list) |
4. Merge-rule lines (check 4)
process.md:23–71 are untouched in this range: the only hunk is @@ -1,14 +1,22 @@ (measured). They still match the owner's rule (inferred, by reading):
- who merges: the orchestrator merges after the independent final check, the combined check and green required CI (:25–28);
- critical PRs: they also need a second independent adversarial review on the same current head, and any push needs both reviews again (:35–36);
- the owner: only signing,
auto_mergeon, direction changes and hands or admin steps go to them (:65–69); - the critical set: it is widened, and the changed files decide, never the marker; when unsure, a PR is in (:37–47).
5. Tests, typecheck, denylist, CI (check 5)
- Tests: plan-decisions, docs-guardrails, changelog, changelog-fragments, changelog-fragments-selfcheck, engine-guard-locks and merge-policy ran at this head: 120 tests, 120 pass, 0 fail. They ran with a temp HOME, TMPDIR and state dir, and with
ghandnpmstand-ins (exit 1) first on PATH (measured). - Typecheck: exit 0 (measured).
- Denylist: "1416 files clean" (measured).
- Fail-without: there is none to re-measure, since this range is wording only and changes no test (measured: the diff).
- CI at
36eafd03: all 8 required checks pass: agentshield, dco, denylist, new-deps, push-gate, scan, test (ubuntu-latest, 22) and test (ubuntu-latest, 24). Nothing is pending. The optional real-systemd and bootstrap jobs are skipping, as before (measured:gh pr checks 920after the run finished; the required list was read from the main branch rules). Nothing was rerun.
6. Merge-tree (read-only git merge-tree --write-tree)
- Clean with origin/main, guard: the commit scan covers every commit form and checkout the guard allows [PROTECTED: owner merges] #979, tool guard: protected-path checks decide on the file a write really reaches; redact secrets in keys [PROTECTED: owner merges] #976, Adversarial suite: attack cases for doubt, the off switches, the stop, weakening, evals and outside-plan (#707) [PROTECTED: owner merges] #957, Bench: bench pull pulls each task's image by pinned digest and puts its base tree, from the image, in the held-out store (#748 slice 4) [PROTECTED: owner merges] #947, Adversarial suite: attack cases for the agent guards, push guard, push gate, commit scan and redaction (#707) [PROTECTED: owner merges] #932, Redaction: one shared list of token shapes, prefixed dashboard keys, no own redactors (#889) [PROTECTED: owner merges] #921, Receipts: signed, chained merge receipts anyone can verify offline (#717 slice 1) [PROTECTED: owner merges] #918, Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910, setup: an Anthropic API key is a first-class agent login (add-project, credentials.sh api-key, doctor) [PROTECTED: owner merges] #904, Setup: the machine helper's sudoers rule follows the engine, rewritten on its own only when it narrows; --replace-sudoers for the rest (#898) [PROTECTED: owner merges] #902, Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #893, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794 and Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746 (measured).
- Conflicts:
- in
src/review.tswith DO NOT MERGE: combined CI for batch 5 (#904, #886) #977 (alsotest/merge-policy.test.ts), git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944, git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927, Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903, Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899 and Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 (alsotest/merge-policy.test.ts); - neither file changed in this range, so these conflicts don't come from rounds 12–14 (inferred). They are a merge-order question for the orchestrator: whichever PR lands second merges main in.
- in
Required changes
- PR body, round 13's section ("The list says what it is", second bullet): mark planner runs: take input only from an allowlist enforced in code #975 as future, for example: "It names the by-design closure: planner runs: take input only from an allowlist enforced in code #975 (open): planner and plan-reviewer runs will take input only from an allowlist enforced in code."
- PR body, round 14's adversarial-pass row "Present-tense planner runs: take input only from an allowlist enforced in code #975 anywhere?": correct it to say this hit was missed and is now fixed, and keep the "measured" label only for a re-run grep.
This is a body edit only: no push, and both reviews stay valid. Neither change is a regression or an owner trade-off: both are principle-5 corrections.
Refusals
None.
Resolves the conflicts with #886's in-file check-table marks in src/review.ts and test/merge-policy.test.ts. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict (delta check, round 15): PASS at
|
| Check | Result | Label |
|---|---|---|
| Re-derive the merge | git merge-tree --write-tree 36eafd03 7b0a96a0 conflicts in exactly two files, src/review.ts and test/merge-policy.test.ts, and auto-merges the rest (docs/design.md, src/merge-policy-weakening.ts and others). The derived tree differs from the head's tree only in those two conflict hunks (git diff --stat <derived tree> 14cdf561: 2 files, +2 −10, all of it marker removal). No hand edits outside the conflicts |
measured |
src/review.ts |
#886's // check-table: never-shrink sits directly above this PR's export const SELF_MODIFICATION_PARTS … = onePartPerGlob([. The [ … ] form from main is dropped, and the rows are untouched |
measured (diff) |
test/merge-policy.test.ts |
#886's // check-table: never-grow sits directly above the multi-line NOT_YET_FILES. It holds main's 7 entries (experiments.yaml, deploy/**, deploy, compose.*, docker-compose.*, dockerfile*, Containerfile*) plus this PR's 13 (5 files and 8 folder or folder-glob entries), 20 in all |
measured (read) |
| Diff against main | git diff --stat 7b0a96a0 14cdf561: the same 12 files as before, and nothing from main leaks in |
measured |
Registry comparison (the builder's registry.mjs, run here against 7b0a96a0 and 36eafd03) |
The merged tree has 150 registrations and main has 149. 0 are lost against main, and the one extra is guard src/plan-decisions.ts (this PR's). 0 are lost against 36eafd03. SELF_MODIFICATION_PARTS registers as never-shrink behind onePartPerGlob([, and NOT_YET_FILES registers as never-grow |
measured |
| #886's registry tests plus the tables' own tests | merge-policy, merge-policy-chars/-decoy/-giveup/-paren/-raw/-reader/-tip, policy-only-registration, plan-decisions and docs-guardrails: 116 tests, 116 pass. engine-guard-locks and review: 28/28. changelog, changelog-fragments, changelog-fragments-selfcheck and not-yet-files-locks: 41/41. All were run on the head with a temp HOME, TMPDIR, state and held-out dir. Each test's spawns were read first, and they only run git in temp repos and the hook entry with that env | measured |
| The marks still protect the tables after the merge (my probe) | weakeningReasons with the head as base, run on in-memory variants (no repo file edited). Dropping the "main watch" row from SELF_MODIFICATION_PARTS gives "removes or changes an entry in a check's rules or a guard's list". Adding one entry to NOT_YET_FILES gives "adds or changes an entry in an allowlist, exception or good-fixture list". So tableSpan reads both tables in their new form |
measured (scratch future.mjs) |
2. The weakening hold
The builder's hold.mjs, run here over 7b0a96a0→head, gives four reasons (measured):
- "can't read a change to check it" for the declaring lines of
SELF_MODIFICATION_PARTSandNOT_YET_FILES. This is fail-closed: both declarations changed form. - "removes or changes a line with an assertion" in
test/engine-guard-locks.test.ts. That line is the negative-path list, and the head keeps all 16 of main's paths and adds 5 (docs/decisions-old.md,docs/adrs/0001-x.md,src/decisions.md,notes/decisions.md,src/plan-decisions-view.ts). The other removed lines are imports widened in place andpresentwith entries added. All of this tightens. - "changes a check's table outside its own list":
engineSourceGlob's use ofENGINE_TREEandENGINE_ROOT_FILES. - "removes or changes a line of a guard or lint":
src/plan-decisions.ts.
Each is disclosed in the body:
- Round 15's merge section names all four.
- Principle 9: "Loosening disclosure (principle 9)" names the exempted check ("self-modification: every glob names real engine files (none dead)") and why its "matches no file" finding is not a fault for these 13 globs (locks placed before the first record, each paired with its lock in not-yet-files-locks, which passes).
engineSourceGlob: A12 and the "Engine repo only" item. It only lets the new decision-record locks stand aside in a managed app, where main never locked those paths, so it reopens nothing a managed app had closed.plan-decisions: "Loosen a merge rule" in What this can never do.
None is an undisclosed loosening (inferred from the diff and the body; all four were judged in earlier rounds and the merge changed none of them).
3. The body (read through the raw API, curl …/pulls/920, 0 raw control characters)
| Required change from issuecomment-6311712976 | Result | Label |
|---|---|---|
| 1. Round 13's #975 line | It now reads "#975 (open): planner and plan-reviewer runs will take input only from an allowlist enforced in code", with a correction note | measured |
| 2. Round 14's row "Present-tense #975 anywhere?" | Corrected: "this row said the result was clean, but Round 13's section … still said 'take input only from …'", labelled "measured (grep), incomplete" | measured |
| Re-grep of the whole body for "975", "allowlist", "take input", "takes input" and "by-design closure" | No present-tense hit left. Every #975 mention is "(open)" with "will", a pointer, or "the by-design closure is #975" | measured |
| Repo text | process.md:7 and the changelog fragment both say "#975 (open): … will take input only from …". gh issue view 975 shows it OPEN |
measured |
| Attribution | No Co-Authored-By or "Generated with" line in the body. The merge commit is signed off with no other trailer | measured |
4. Other checks
- Build, typecheck, denylist: build and
npm run typecheckexit 0. The denylist reports "1432 files clean" (measured). The body's 1436 most likely counted 4 untracked files in the builder's worktree, since the script usesls-files -co(inferred). CI's denylist job passes. - Merge-tree against the open PRs: I ran
git merge-treeof the head against every open PR head (measured), then against main to separate out the conflicts main already causes.- Clean: guard: the commit scan covers every commit form and checkout the guard allows [PROTECTED: owner merges] #979, tool guard: protected-path checks decide on the file a write really reaches; redact secrets in keys [PROTECTED: owner merges] #976, Adversarial suite: attack cases for doubt, the off switches, the stop, weakening, evals and outside-plan (#707) [PROTECTED: owner merges] #957, Receipts: signed, chained merge receipts anyone can verify offline (#717 slice 1) [PROTECTED: owner merges] #918, Setup: the machine helper's sudoers rule follows the engine, rewritten on its own only when it narrows; --replace-sudoers for the rest (#898) [PROTECTED: owner merges] #902, Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #893, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794, Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746.
- Conflicts this PR adds, all in
src/review.tsand all keep-both:- git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 and git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 add
test/git-runner.test.tswhere this PR addstest/plan-decisions.test.tsin the same part. - Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899 adds
HARNESS_CONFIG_GLOBSnext to this PR's decision-record JSDoc. - Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903 adds
src/vacuity*.tsto the merge-policy row. Keep both the row and this PR's mark plusonePartPerGlob([line.
- git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 and git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 add
- Conflicts already against main (Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886):
src/merge-policy-weakening.tswith git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944, Adversarial suite: attack cases for the agent guards, push guard, push gate, commit scan and redaction (#707) [PROTECTED: owner merges] #932, Redaction: one shared list of token shapes, prefixed dashboard keys, no own redactors (#889) [PROTECTED: owner merges] #921 and Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903;src/coordinator/merge-flow.tswith Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903;src/review.tswith Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910 (ODD_PATH_CATEGORIES).
- CI at
14cdf561(run 39571448742 on this sha, nothing rerun): every required check passes: agentshield, dco, denylist, new-deps, push-gate, scan (2), test (ubuntu-latest, 22) and test (ubuntu-latest, 24). Overall it is 20 pass, 7 skipping (the optional real-systemd, bootstrap and sign-in probe jobs, as in earlier rounds) and nothing pending (measured:gh pr checks 920 --requiredafter the run finished)
Required changes
None.
For the orchestrator, not the builder: both reviews reset with this push, and the combined check must include 7b0a96a0.
Second adversarial review, round 15: PASS (head
|
| # | Attack | Result | Label |
|---|---|---|---|
| A1 | Did the merge drop a #886 mark? | Main has 77 marks (tables plus lint tests) over every tracked JS/TS file. Every one is in the head, with the same table and kind. | measured (scratch script, registrationsOf at both revs) |
| A2 | Did it drop a central registration? | The 52 CHECK_TABLES entries at 36eafd03 (and at c0df669a) are all still registered at the head, by a central entry or by a mark in their file. All 22 LINT_TESTS entries are still registered. GUARD_FILES loses nothing from main or from 36eafd03, and still holds src/plan-decisions.ts. |
measured |
| A3 | Did it drop a lock line? | Every SELF_MODIFICATION_PARTS line at 36eafd03 and c0df669a is in the head. From main, only the close ]; is missing, because the head closes with ]); (the wrapper, disclosed). NOT_YET_FILES keeps main's 7 entries and has this PR's 13. |
measured (tableLines at each rev) |
| A4 | Later PR: an add-only line inside the marked SELF_MODIFICATION_PARTS that empties its value |
Held as "table-shrunk". The added line becomes the reader's close, so the real close drops out of the span. Main behaves the same way. | measured (in-memory mutant, weakeningReasons) |
| A5 | Later PR: remove a part / add a NOT_YET_FILES entry / grow NOT_YET_FILES from outside its lines |
All held ("table-shrunk" plus "guard-lines"; "table-grown"; "table-used"). | measured |
| A6 | Later PR: an add-only change outside the tables' lines, in the helper that builds the lock list or in the test that reads NOT_YET_FILES |
Not held. Main has the same class: an add-only change in a function that reads the lock list is not held there either (measured on 7b0a96a0). This is the hold's documented scope ("other pure additions pass"), not something this PR or its merge opened. The helper existed at c0df669a. Both files are self-modification (L3, critical), so such a PR still needs both reviews, and the test that pins the approved part list would fail in CI (inferred). Not a blocker. It could be a follow-up on the hold's design. |
measured (hold), inferred (CI) |
| A7 | Shrinking NOT_YET_FILES without the hold |
Not held, but this tightens: the entry's glob falls under the dead-glob check. The lock itself stays in SELF_MODIFICATION_PARTS. |
measured (hold), inferred (effect) |
| A8 | The body's claim that the hold flags the PR's own diff against main | Reproduced: the declaring lines of SELF_MODIFICATION_PARTS and NOT_YET_FILES are changed (fail-closed), plus engine-guard-locks, engineSourceGlob and src/plan-decisions.ts. All are disclosed. |
measured |
| A9 | Re-run of the round-13 probes for the listed routes' wording | #956: a 781-character answer is indexed whole and reaches the brief. #969: a log-written decision with no role is indexed. #970: the committed text is indexed while the checkout holds an edit. Messages: owner and writer claims from a non-owner name are accepted, and a bare request is refused. The planner brief puts task text first and doesn't call it data (the plan reviewer's does). Levels: CLAUDE.md and .claude/ paths are L3, CLAUDE.local.md and AGENTS.md are L0. Each matches process.md:8–17 as worded, including :17's "a guess, not measured". |
measured (scratch probes, temp HOME, state and repos) |
| A10 | Are the routes stated as open really open? | #975, #962, #956, #969, #970, #973, #953 and #917 are all OPEN. | measured (gh issue view) |
| A11 | Over-claims: grep the PR's files for "only way/route", "every route", "can't reach", "listed once" and "one list" | The only live hits are negated ("That PR is not the only way in", "Neither closes every route", "don't close every route"), plus the code comment that the index and the lock "read one list", which is true: src/plan-decisions.ts:13 imports DECISION_FILES and DECISION_DIRS from src/review.ts. |
measured (grep), inferred (reading) |
| A12 | Merge-rule lines against the owner's rule | process.md:25–28 (final check, combined check, green required CI), :35–36 (a second independent review on the current head, and any push resets both), :37 and :47 (the set is widened, the changed files decide, and when unsure the PR is in), and :65–69 (only signing, auto_merge on, direction, hands or admin). All match. :70 keeps the standing check-removal OK, which is not a merge route. |
inferred, by reading |
| A13 | Exploit steps in public text (#707) | None new. The routes are stated generically, with no paths, settings or commands. | inferred, by reading |
| A14 | Tests on the merged head | plan-decisions, engine-guard-locks, merge-policy and not-yet-files-locks: 82 tests, 82 pass, 0 fail, with a temp HOME, TMPDIR and state dir. | measured |
3. Required changes
None.
Nit (non-blocking, body only): round 15's registry counts (73 / 149 / 150) leave out the 5 template-literal GUARD_FILES entries (${BRAND.configDir}/…). Counting every entry gives 78 / 154 / 155. The "0 lost" result is the same either way (measured).
For the orchestrator (information): origin/main has moved to 40ef312a (#947, bench scripts and tests only). It merges cleanly with this head (git merge-tree exit 0) and touches none of this PR's files (measured). The merge rule's "main's tip must still be the combined check's base" applies.
Refusals: none.
main brought #947 and #899. One conflict, in src/review.ts: the HARNESS_CONFIG block from #899 (its JSDoc, HARNESS_CONFIG and the never-shrink HARNESS_CONFIG_GLOBS) and this PR's decision-record block (DECISION_FILES, DECISION_DIRS, DECISION_RECORD_GLOBS and onePartPerGlob) were both added after hidesSubmodules. Both are kept whole: this PR's block first, then the one from #899, then SELF_MODIFICATION as before. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: PASS (round-16 delta final check, head
|
| What | main b3176d3e |
prev head 14cdf561 |
merged b36a9525 |
Lost |
|---|---|---|---|---|
Lines over all tracked files (merge base 7b0a96a0; a line may go only where the other side removed it) |
1458 files | 0 | ||
SELF_MODIFICATION_PARTS entries |
128 | 134 | 134 | 0 / 0 |
NOT_YET_FILES entries |
7 | 20 | 20 | 0 / 0 |
GUARD_FILES (quoted + template) |
77 | 78 | 78 | 0 / 0 |
LOCKED_CATEGORIES string entries |
4 | 4 | 4 | 0 ([HARNESS_CONFIG]: HARNESS_CONFIG_GLOBS present, read) |
HARNESS_CONFIG_GLOBS |
3 | n/a | 3 | 0 |
#886 marks (registrationsOf, every JS/TS file) |
78 | 77 | 78 (union 78, 0 extra) | 0 |
#899's added lines in src/review.ts (git diff 40ef312a b3176d3e) |
18 | 18 present | 0 |
Marks still hold (measured, r15's future.mjs rerun on this head). With in-memory variants fed to weakeningReasons:
- dropping the "main watch" row of
SELF_MODIFICATION_PARTSgives "removes or changes an entry in a check's rules or a guard's list"; - adding one entry to
NOT_YET_FILESgives "adds or changes an entry in an allowlist, exception or good-fixture list".
3. #899's harness-config lock holds (measured)
- Probe.
levels.mjsrunscomputeLevelat the head for 3 configs: this repo's review.yaml; a hostile one withharness-configandengine-guardsemptied and dropped from every level; and one with every category emptied and every level'swhencleared. It runs in both scopes,engineandmanaged-app..worklane/tests.yaml,.worklane/review.yaml,.worklane/decisions.md,.worklane/decisions/x.md,CLAUDE.mdand.claude/settings.jsonare L3 withharness-configin all 6 combinations.docs/process.md,docs/design.mdandsrc/plan-decisions.tsare L3 withself-modificationin the engine scope under all 3 configs.- Expected-L3 misses: 0.
- Tests. 14 named test files gave 147 tests, 147 pass, 0 fail: harness-config-lock, engine-guard-locks, plan-decisions, review, merge-policy plus the 7 merge-policy-* files, not-yet-files-locks and docs-guardrails. The run used the npmw.sh build and a temp HOME and state dir. I read each file's imports and spawns first: none sets its own PATH or calls a host tool.
- Not run here.
separate-users.testruns a realsudo -n, so I left it to CI. All 8 test shards passed at this head (below).
4. Body count fix (measured: own count.mjs, GUARD_FILES quoted + template entries plus marks)
| Rev | GUARD_FILES | Marks | Sum | Body says |
|---|---|---|---|---|
36eafd03 |
78 (73 + 5) | 0 | 78 | 78 |
7b0a96a0 |
77 (72 + 5) | 77 | 154 | 154 |
14cdf561 |
78 | 77 | 155 | 155 |
b3176d3e |
77 | 78 | 155 | 155 |
b36a9525 |
78 | 78 | 156 | 156 |
The 78/154/155 correction in the round-15 section and the round-16 counts (155/155/156) are both right. The body was read through the raw API and has 0 raw control characters and no attribution line (measured).
5. Usual checks
- Typecheck and denylist (measured):
npm run typecheckexits 0. Denylist: "denylist: 1453 files clean". - Commit (measured): the merge commit is signed off and has no other trailers.
- Merge-tree with current main
cec056a5(tool guard: protected-path checks decide on the file a write really reaches; redact secrets in keys [PROTECTED: owner merges] #976, Setup: the machine helper's sudoers rule follows the engine, rewritten on its own only when it narrows; --replace-sudoers for the rest (#898) [PROTECTED: owner merges] #902) (measured): clean, tree2de26db4. main's delta sinceb3176d3etouches none of this PR's 12 files.- It adds
src/guardrails/reach.tsandsrc/os/process-links.tstoGUARD_TREEintest/review.test.ts. This PR adds no import to the hook's import tree:src/plan-decisions.tsgains imports, and the head'sGUARD_TREEtest passes (inferred: no interaction). CI on the combined tree will confirm. - main's marks at
cec056a5number 81 (measured).
- It adds
- Merge-tree with open PR heads (measured):
- Clean: test: tests of scripts/ shell scripts never reach the machine's sudo or other host tools (#991) [PROTECTED: owner merges] #997, guard: the commit scan covers every commit form and checkout the guard allows [PROTECTED: owner merges] #979, Adversarial suite: attack cases for doubt, the off switches, the stop, weakening, evals and outside-plan (#707) [PROTECTED: owner merges] #957, Adversarial suite: attack cases for the agent guards, push guard, push gate, commit scan and redaction (#707) [PROTECTED: owner merges] #932, Redaction: one shared list of token shapes, prefixed dashboard keys, no own redactors (#889) [PROTECTED: owner merges] #921, Receipts: signed, chained merge receipts anyone can verify offline (#717 slice 1) [PROTECTED: owner merges] #918, Review levels: a path that looks like a locked or protected one (UTS #39 skeleton) always waits (#763) [PROTECTED: owner merges] #910, Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox and Read rules, not only the hook (#890) [PROTECTED: owner merges] #893, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794, Remove the desktop app, its source, Tauri lockfile and docs; keep the desktop check one more release as a no-op (#744) [PROTECTED: owner merges] #746.
- Conflicts in
src/review.ts: git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944, git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 and Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903. All three already conflicted with the previous head14cdf561, so none is new this round. Each is an adjacent-addition hunk resolvable by keeping both sides:- git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 and git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 add
test/git-runner.test.tsbeside this PR'stest/plan-decisions.test.tsin the engine-guards list. - Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903 changes the first
SELF_MODIFICATION_PARTSrow (addssrc/vacuity.tsandsrc/vacuity-read.ts) on the line next to this PR'sonePartPerGlob([opener. Whoever merges second must keep theonePartPerGlob([form and Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903's widened row (inferred). - git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 and git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 also conflict with main itself, in
test/review.test.ts. That is their own merge, not this PR's.
- git: an instance's git runs nothing its checkout's config names, and .git/ is the coordinator's (#926) [PROTECTED: owner merges] #944 and git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 add
- CI at
b36a9525(measured): runs 39631817128 and 39631817127 are at headb36a9525, completed, success.- Passed: all 8 test shards (Node 22 and 24), agentshield, dco, denylist, desktop, installed-engine-config, new-deps, push-gate, scan (x2), scope and the test summaries.
- Skipped by design: the real-systemd, bootstrap and sign-in jobs.
- Pending or failing: none. Nothing was rerun.
Required changes
None.
Notes for the merge
- This head resets both reviews. The second adversarial review and the combined check must be built on
cec056a5or later (inferred from the merge rule). - The Merge: hold a PR whose own tests run none of the app code it changes [PROTECTED: owner merges] #903 hunk note above applies to whichever PR merges second (inferred).
Second adversarial review, round 16: PASS (head
|
| # | Attack | Result | Label |
|---|---|---|---|
| B1 | Level of every decision record path (DECISION_FILES, each DECISION_DIRS folder, a file in it and one in a subfolder), docs/process.md, docs/design.md, src/plan-decisions.ts, test/plan-decisions.test.ts, and #899's paths (.worklane/tests.yaml, review.yaml, any .worklane/*, .claude/settings.json, .claude/**, CLAUDE.md). Scopes: engine, managed-app and undefined. Each under this repo's review.yaml and under hostile ones: empty L1–L3 lists with L0 max_lines 100000; harness-config, self-modification, engine and engine-guards redefined to [] with docs: ['**']; a __proto__ category; and the same empty categories passed as extraCategories |
0 rows below L3 where L3 is expected. In the engine scope and the undefined scope, every path is L3 and waits (whyHighRisk is non-empty). Records wait on self-modification, and the two .worklane/ records on harness-config too. In the managed-app scope, #899's paths and the two .worklane/ records stay L3 through harness-config. The engine-only paths stand aside there, as designed and disclosed |
measured (levels.mjs, computeLevel + whyHighRisk) |
| B2 | A later PR shrinks HARNESS_CONFIG_GLOBS: it drops a glob (H1), drops the mark and shrinks in one change (H2), slices it in LOCKED_CATEGORIES (H3), or takes it out of ALWAYS_L3 (H4) |
All held: "can't read", "registration gone", "table entry removed" and "guard lines" | measured (weakeningReasons on in-memory mutants of the head's src/review.ts) |
| B3 | A later PR shrinks the records' lock: it drops a DECISION_FILES or DECISION_DIRS entry (H5, H6), cuts DECISION_RECORD_GLOBS to the files (H7), widens onePartPerGlob's skip line (H8), or slices the part's globs (H9) |
All held. src/review.ts is a GUARD_FILES entry, so any removed or changed line in it waits |
measured |
| B4 | Combination of onePartPerGlob and HARNESS_CONFIG_GLOBS: the harness list is fed through onePartPerGlob after the self-modification parts (H11). Also: engineSourceGlob widened to the config folder, which would make harness-config stand aside in a managed app (H12). Also: computeLevel's never-fewer union loses its locked side (H10) |
All held | measured |
| B5 | Unbind a mark: an add-only line between // check-table: never-shrink and HARNESS_CONFIG_GLOBS (H15), or SELF_MODIFICATION_PARTS (H16) |
Both held ("removes or changes the mark") | measured |
| B6 | Add-only narrowing (#992 class, listed and not reported): a skip line added in onePartPerGlob (H13); HARNESS_CONFIG_GLOBS.length = 0 added outside the table (H14) |
H13 is not held, which is #992 as known. H14 is held ("table used outside its list") | measured |
| B7 | Did the merge lose a lock line, mark or registration? | Each marked table in src/review.ts at either parent has all its lines in the head. Exceptions: ALWAYS_L3's old line (main changed it), and the SELF_MODIFICATION_PARTS open and close (the wrapper, disclosed). #886 marks over every tracked JS/TS file: 14cdf561 77, b3176d3e 78, head 78, 0 lost. GUARD_FILES quoted and template entries: main 77, 14cdf561 78, head 78, 0 lost, src/plan-decisions.ts and src/review.ts present. So there are 156 registrations, as the body says |
measured (keep.mjs, counts.mjs) |
| B8 | Merge-resolution slip: did the restored /** comment out code, or detach a declaration from its mark? |
No. At runtime LOCKED_CATEGORIES has 3 keys and harness-config holds its 3 globs (B1). tableSpan finds HARNESS_CONFIG_GLOBS (one line, 355), SELF_MODIFICATION_PARTS (368–479), LOCKED_CATEGORIES (482–486) and ALWAYS_L3, each under its mark |
measured |
| B9 | Can the index read docs/process.md or #899's harness config (.worklane/tests.yaml, .claude/**, CLAUDE.md) through a record path? I tried committed links and working-tree swaps. Committed links: .worklane/decisions.md to tests.yaml, the .worklane/decisions folder to ../.claude, docs/adr/0002-x.md to ../../CLAUDE.md, and docs/decisions.md to process.md. Swaps after the commit: the same file and folder, and docs/adr/0001-ok.md to ../process.md |
No text leaked in any case. Committed links aren't regular files, so they aren't indexed. A swap leaves out the record whose real path isn't a record path | measured (index.mjs, scratch repos) |
| B10 | Re-run of the round-13 and round-15 probes that still apply | Same results as round 15. #956: a 781-character answer is indexed whole and reaches the brief. #969: a log-written answer with no role is indexed. #970: the committed text is indexed while the checkout differs. Console: owner and writer claims are accepted, and a bare request is refused. The planner brief doesn't call the task text data. Levels: CLAUDE.md and .claude/** are L3 (now with harness-config); CLAUDE.local.md and AGENTS.md are L0. All of these match the open routes as process.md words them |
measured |
| B11 | Tests on the merged head | plan-decisions, engine-guard-locks, harness-config-lock, review, merge-policy, not-yet-files-locks and docs-guardrails: 116 tests, 116 pass, 0 fail. The adversarial suite: 111 tests, 99 pass, 0 fail, 0 skipped (the other 12 are its todo entries). For lock loss masked by harness-config: engine-guard-locks asserts categories['self-modification'] equals the path for every record path under every config. So a .worklane/ record that lost its self-modification glob would fail there, even though it stays L3 |
measured (tests); inferred (masking, by reading test/engine-guard-locks.test.ts) |
| B12 | Main has moved since b3176d3e |
origin/main is cec056a5 (#902). git merge-tree --write-tree against the head is clean (exit 0, no conflicts). git diff --stat b3176d3e origin/main is empty for src/review.ts, src/plan-decisions.ts, src/merge-policy-weakening.ts, docs/process.md, test/merge-policy.test.ts and test/engine-guard-locks.test.ts |
measured |
3. Required changes
None.
Nits (optional, wording only):
- The body's "
LOCKED_CATEGORIESandHARNESS_CONFIG_GLOBS… 4 and 3 entries".LOCKED_CATEGORIEShas 3 entries at main and in the head (5 table lines), and 2 at14cdf561. "0 lost" stands (measured). changelog.d/920-lock-merge-process-doc.md's "A managed app's decision records keep the categories its ownreview.yamlgives them". Since Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899, a managed app's.worklane/decisions.mdand.worklane/decisions/**are always L3harness-config, whatever its review.yaml says (B1, measured). This understates the protection. It doesn't overstate it, and the body's round-16 row already says so. A half-sentence would make it exact.
For the owner (information, a scope choice, not a defect of this PR): process.md's critical set names self-modification, engine-guards and release-signing from computeLevel. It doesn't name #899's new code lock harness-config. So in this repo, a PR that touches only .worklane/tests.yaml, .worklane/deploy.yaml, .claude/** or CLAUDE.md is L3 by code but gets one review, not two (measured for CLAUDE.md and .claude/**: "critical by code: none"). process.md:12 already discloses the project-instructions part of this (#973). The merge didn't open it, and main had no written critical set. Adding harness-config to the set would cost more reviews. That makes it a scope decision for the owner.
Refusals: none.
src/review.ts: the engine-guards lock list takes the union, test/git-runner.test.ts and #920's test/plan-decisions.test.ts, each with its comment. src/plan-decisions.ts (#920, locked as the planners' decisions index): its two raw spawnSync('git', ...) calls, the object read (cat-file --batch) and committedRecords' rev-parse helper, run through gitSpawnSync, which the runner scan requires. Their own --no-replace-objects, the object-id checks and the hash re-check stay as they were; the runner adds grafts and the commit-graph off. No OUTSIDE_THE_RUNNER entry, and no plan-decisions test changed. Measured on this merge before the routing: git-runner's "every git call ... goes through the runner" fails with "src/plan-decisions.ts: starts git 2 time(s) (not on the list)". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> #920 merged on main; no conflicts.
One conflict, src/review.ts SELF_MODIFICATION_PARTS. It keeps #920's `onePartPerGlob([` and this branch's merge-policy row, which includes src/vacuity.ts and src/vacuity-read.ts. On the merged tree, measured: dependencies reinstalled, typecheck and build clean, 99/99 on review, merge-policy, engine-guard-locks, not-yet-files-locks and merge-policy-vacuity. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
No conflicts (git merge-tree --write-tree origin/main HEAD exit 0). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…ith TypeScript, and pin yaml Both reviews of 87cf216 found that round 18's claim, that everything the backstop runs is held, was false. - Four src/ modules run in its process through test/isolate.ts and the weakening module: src/brand.ts, src/os/system-paths.ts, src/os/own-env.ts and src/os/child-process.ts. A changed line in any of them got no hold. One such line, patching what fs returns, turned the backstop green over a red desktop twin. They are now in GUARD_FILES, which is an addition only. A new test walks the built import closure of test/isolate.ts and the backstop with TypeScript's import reader. It fails on any local module whose changed line isn't held, and on a closure module that loads another by name at run time. - yaml could be swapped through the lockfile, or shadowed by a dist/node_modules/yaml that Node finds first. The backstop now pins each package its closure loads (yaml, and typescript, its new import reader) to the lockfile's integrity, held as a line of this lint test. It also checks that the package resolves under the repo's own node_modules (a plain directory, not a link), that no node_modules sits between a built module and the repo top, and that the package isn't the repo itself. - The import-list test read only column-0 imports with a space after `import`. It now reads imports with ts.preProcessFile. typescript is added to MAY_IMPORT with it. The test also fails on the text that loads a built-in by name. IMPORT_FORM_FIXTURES holds the forms the regexes missed: indented, a tab, a comment first, no space (side effect and named), export* with no space, and a comment inside import(). Also merges origin/main 1cee9bd (#920, #921), with no conflicts. Test (fails without): test/desktop-removed.test.ts. A new file in a worktree at 87cf216 turns each fix red there: the 8 import forms pass that head's scan; the 4 modules get [] from its hold; its closure walk finds them unheld; and with a dist/node_modules/yaml shadow and a red twin added as new files, its backstop passes all its tests. At this head the same shadow turns the pin test red. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Intent: Record the owner's merge rule for this repo's own PRs (owner decisions, 2026-10-21) in a separate process page,
docs/process.md, instead of docs/design.md. The engine indexes design.md as recorded decisions for its planner agents, so the rule must not live there. The new page is locked like the design rules and kept out of that index. Since round 5, every path the index reads, and the index code itself, is locked too, so a PR that brings a copy of the rule into a committed record needs both reviews. That is not the only way to the planners. docs/process.md lists the known open routes, for example project instructions loaded into planner runs (#973). The list is not proven complete, and the by-design closure is #975 (open: planner input will come only from an allowlist enforced in code). The page states the rule fail-closed, as the adversarial reviews asked.Risk class: protected. It changes
src/review.ts(engine, and the self-modification locks),src/plan-decisions.ts(engine, now locked),.worklane/review.yaml(engine) and locked tests (engine-guards). By its changed files it is in the critical set: review levels and locks, plusengine-guardspaths (inferred from docs/process.md's own list). It needs the second adversarial review.Round 16 (main moved to
b3176d3e: #947 and #899; one body count fix)Head
b36a9525: a merge of origin/mainb3176d3einto14cdf561, signed off with no other trailers (measured:git log -1;npm run check:dco -- 14cdf561 HEADprinted "DCO: all commits signed off"). This push resets both reviews, and the combined check must be built onb3176d3eor later.Main merge (#899)
git merge-tree --write-tree --name-only 14cdf561 b3176d3eshowed one conflicted file,src/review.ts, with one hunk (measured). Everything else, including all of #947, merged cleanly.14cdf561)src/review.ts, afterhidesSubmodules(the only hunk)DECISION_FILES,DECISION_DIRS,DECISION_RECORD_GLOBSandonePartPerGlobexport const HARNESS_CONFIG = 'harness-config',// check-table: never-shrinkandconst HARNESS_CONFIG_GLOBS = [...]/**opener restored), thenSELF_MODIFICATIONas before. The resolution used Edit only. It removed the three conflict markers and added one blank line and one/**lineThe rest of #899's change to
src/review.tsmerged without conflict:[HARNESS_CONFIG]: HARNESS_CONFIG_GLOBSinLOCKED_CATEGORIES,ALWAYS_L3 = [SELF_MODIFICATION, HARNESS_CONFIG],DEFAULT_CATEGORIES'[HARNESS_CONFIG]key andcomputeLevel's never-fewer harness-config globs.Nothing lost (measured, by a scratch script that reads main, the head and the merged tree, and edits no repo file):
git diff origin/main HEAD -- src/review.tsshows only this PR's lines.git diff 14cdf561 HEAD -- src/review.tsshows only Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899's lines, the same asgit diff 40ef312a b3176d3e -- src/review.ts.src/review.ts(its lock lines) are in the merged file. 0 are lost.14cdf561is in the merged tree, unless the other side deliberately removed it from the merge base7b0a96a0. 0 lines or files are lost.SELF_MODIFICATION_PARTSentries: main 128, head 134, merged 134. 0 lost from either side.NOT_YET_FILESentries: main 7, head 20, merged 20. 0 lost.GUARD_FILESentries (quoted and template literals): main 77, head 78, merged 78. 0 lost.LOCKED_CATEGORIESandHARNESS_CONFIG_GLOBSare kept whole: 4 and 3 entries, 0 lost.registrationsOfover every tracked JS/TS file): main 78, head 77, union 78, merged 78. 0 lost. main's one extra mark is Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899'sHARNESS_CONFIG_GLOBS.GUARD_FILESplus marks): mainb3176d3e155, head14cdf561155, merged 156. The one extra is this PR'sguard src/plan-decisions.ts.git diff --stat origin/main HEADstill lists only this PR's 12 files.Body fix: round 15's registry counts left out the 5 template-literal
GUARD_FILESentries. 73/149/150 are now 78/154/155 in the round 15 section below (measured: the same scratch count at36eafd03,7b0a96a0and14cdf561: 78, 154 and 155).Verified on the merged tree (measured, with a temp HOME, TMPDIR and state dir; build through npmw.sh):
git diff --stat 40ef312a b3176d3elists, gave 240 tests, 236 pass, 0 fail, 4 skipped (node exit 0). The 15 files are harness-config-lock, project-command-home and its 8 siblings, project-command-isolation, the 3 project-command-seatbelt-* files and coordinator. The bubblewrap sandbox tests ran here and did not skip. The 4 skips are coordinator browser tests ("no Chrome or Chromium on this machine").separate-users.test, which Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899 also changed. At load it runs a realsudo -n -u nobody true, and agents may not run sudo here. CI runs it.git show --stat b3176d3e^2is Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899's branch merging main, so its stat lists what main brought into Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899 (the setup: an Anthropic API key is a first-class agent login (add-project, credentials.sh api-key, doctor) [PROTECTED: owner merges] #904 and Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 tests), not Sandbox: the agent's home is read-only to a project command, so it can't plant config for a later agent-user process; harness-config review locked (#798) [PROTECTED: owner merges] #899's own tests. I used the range from40ef312atob3176d3einstead. main's Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 registry tests (merge-policy-*) are in the 183 above.Adversarial pass (round 16)
SELF_MODIFICATION_PARTS,NOT_YET_FILES,GUARD_FILES,LOCKED_CATEGORIESandHARNESS_CONFIG_GLOBS.worklane/tests.yaml,.claude/settings.jsonandCLAUDE.mdstay L3 withharness-config(scratchcomputeLevelrun).worklane/decisions.mdand.worklane/decisions/**?self-modification(part "the planners' decision records") andharness-config. In a managed app's scope,self-modificationstands aside as before (engineSourceGlob), and #899'sharness-configkeeps them L3. That is #899's tightening, not a loosening by this PRdocs/process.md,docs/design.md, the decision records andsrc/plan-decisions.tskeep their parts after the merge?14cdf561("the engine repo's merge process", "the release procedure and the design rules", "the planners' decision records", "the planners' decisions index")Round 15 (round-14 delta check issuecomment-6311712976: PASS on the repo; merge main, two body fixes)
Head
14cdf561: a merge of origin/main7b0a96a0into36eafd03, signed off with no other trailers (measured:git log -1). main brought #904 and #886. #886 moved check registration from central lists to in-file marks. This push resets both reviews, and the combined check must be built on7b0a96a0or later.The merge (measured):
git merge-tree --write-tree --name-only 36eafd03 7b0a96a0showed conflicts in two files. Both are resolved by keeping both sides:src/review.ts: Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886's// check-table: never-shrinkmark now sits directly above this PR'sSELF_MODIFICATION_PARTS … = onePartPerGlob([line.test/merge-policy.test.ts: Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886's// check-table: never-growmark now sits directly above this PR's multi-lineNOT_YET_FILES. The list keeps main's 7 entries and this PR's 13.src/merge-policy-weakening.tsanddocs/design.md.git diff --stat 7b0a96a0 HEADstill lists only this PR's 12 files.This PR's registrations, re-expressed (measured):
src/plan-decisions.tsinGUARD_FILES(git diff 9f9533aa 36eafd03 -- src/merge-policy-weakening.ts: 3 added lines, its comment and that entry). It added noCHECK_TABLESorLINT_TESTSentry, so there is nothing to convert to a mark.GUARD_FILESas a central list, and the entry is still there after the merge.SELF_MODIFICATION_PARTS(never-shrink) andNOT_YET_FILES(never-grow).Registry comparison (measured): a scratch script runs
registrationsOfover every markable tracked file and readsGUARD_FILESat each rev.GUARD_FILESentries).7b0a96a0has 154 (corrected from 149), and 0 are lost in the merged tree. The one extra isguard src/plan-decisions.ts, this PR's.36eafd03has 78 (corrected from 73; itsGUARD_FILESentries, 73 quoted and 5 template literals; its other registrations were central lists the script doesn't read), and 0 are lost.REGISTERED_BEFORE_MARKScomparison test passes on the merged tree, so the 75 pre-mark registrations all still register.How
NOT_YET_FILESmeets #886's never-grow mark:weakeningReasonsover the diff from7b0a96a0to the merged tree reports it as the declaring line changed, which is fail-closed (measured).SELF_MODIFICATION_PARTS's declaring line,engineSourceGlob's use ofENGINE_TREEandENGINE_ROOT_FILES, the added entries in a negative-path list in engine-guard-locks, and the replaced lines ofsrc/plan-decisions.ts. All are disclosed in earlier rounds (round 6, round 7 and A12).NOT_YET_FILESgrowth is disclosed under principle 9 below.Body fixes:
Verified on the merged tree (measured, with a temp HOME, TMPDIR and state dir):
scripts/push-gate-hook.mjs. Reinstalling it is a host setup step, so I left it for the orchestrator.Adversarial pass (round 15)
tableSpanstill readSELF_MODIFICATION_PARTSbehindonePartPerGlob([(an opening(and[, closed by]);)?git diff --stat 7b0a96a0 HEADlists the same 12 filesweakeningReasonsrun still flags theNOT_YET_FILESandSELF_MODIFICATION_PARTSdeclarations and theengineSourceGlobchangeRound 14 (round-13 second review issuecomment-6311276826: wording only, no code route)
Head
36eafd03, one commit on89425a73, signed off with no other trailers (measured:git log -1). It changesdocs/process.md,docs/design.mdand the changelog fragment. Both docs are protected, so this push resets both reviews. origin/main is still9f9533aa(measured: the push gate).process.md:7, the task-text sentence (A5). It said the task text and the checkout are "data … not decisions or instructions". The review measured that as false: the issue text comes first in the planner's brief, unfenced. It now reads: "The task text (the issue's title, body and done-when list) is the planner's assignment: it tells the planner what to plan, and the code doesn't mark it as data. The checkout's code is data that planners read by design. Neither is a decision record." It then points to the agent-written task text entry.
planner runs: take input only from an allowlist enforced in code #975 is marked open (A6). The changelog fragment now reads "planner runs: take input only from an allowlist enforced in code #975 (open): planner runs will take input only from an allowlist enforced in code". process.md:7, this body's Intent and its "What remains" line say the same.
Three more known routes go under planner runs: take input only from an allowlist enforced in code #975 in process.md. They are stated generically, with no file names or steps (Public adversarial suite in CI: the merge gate refuses every hostile PR case (prompt and delimiter injection, rename and copy tricks, poisoned .claude settings and hooks, credential smuggling), with the existing guard tests folded in #707):
"What remains" lists them too, each with its label.
Optional, done:
docs/design.md's plan-citation rule (A12). It said the index takes the records "in the checkout". It now says "committed at the plan's base", as the code does. That is main's text, so this is a one-phrase change to a protected file this PR already touches.Verified (measured):
src/change this round, so dist was current;Adversarial pass (round 14)
src/comments name #975 only as a pointer. Corrected in round 15: this row said the result was clean, but Round 13's section in this body still said "take input only from …" in the present tense. My grep covered the repo files and the Intent, not the body's earlier round sections. That line is fixed in round 15Round 13 (round-12 second review issuecomment-6310912283: text only, no code route)
Head
89425a73, one commit on062beb8f, signed off with no other trailers (measured:git log -1). docs/process.md is protected, so this push resets both reviews. origin/main is still9f9533aa(measured: the push gate).For the fourth round running, a review found routes the list left out. This round fixes the class: the list no longer claims to be complete, and nothing else claims it either.
src/plan-decisions.ts(PROCESS_DOC),src/coordinator/decisions.tsandsrc/merge-policy-weakening.ts;src/changes are comments only, and all on lines this PR added. Against main, these three files remove the same 21 lines as before this round (measured:git diff origin/main | grep -c '^-[^-]'), so the weakening hold sees nothing new (inferred).Adversarial pass (round 13)
Round 12 (round-11 second review issuecomment-6310598355: text only, no code route)
Head
062beb8f, one commit onf672543c, signed off with no other trailers (measured:git log -1). It changes docs/process.md only. The file is protected, so this push resets both reviews. origin/main is still9f9533aa, already merged in round 11 (measured: the push gate).Adversarial pass (round 12)
Round 11 (round-10 second review issuecomment-6310271940; round-10 delta check issuecomment-6310344340)
Head
f672543c(measured:git push). Ond13857d9there are three commits, each signed off with no other trailers (measured:git log):6a187284, the list;4b50017c, the delta check's items;f672543c, a merge of origin/main9f9533aa.The merge re-derives exactly:
git merge-tree --write-tree 4b50017c 9f9533aagives treecc4ff684, the merge commit's tree (measured). The combined check must be built on9f9533aaor later. docs/process.md is protected, so this push resets both reviews.Neither check found a code route. The second review found three routes that the open-routes list left out, all on main too, so none is a regression (inferred). This round only adds them to the list, and the text that points to the list now points to the new routes as well.
PROCESS_DOCdocstring name decisions index: pin the base it reads to the forge's branch #962 and Decisions index cites agent- or reporter-written question text as an owner decision #956 as examples only, so they no longer read as the whole list. The docstring change rewords only lines this PR added.GUARD_FILEScomment insrc/merge-policy-weakening.ts(this PR's own line) said "only committed records", which left out decisions already made. It now says "records committed at the base" and points to the list. Against main the file still has additions only (measured:git diff origin/main...HEAD). That keeps this PR within the registry permission, and it remains a critical digest.Adversarial pass (round 11)
merge-policy-weakening.tsedit remove a registry line that main has?git merge-tree --write-treeexactlyRound 10 (round-9 second review issuecomment-6310047202: claims only, no new working route)
Head
d13857d9(one commit on2647eeaf, signed off, no other trailers; measured:git log -1). docs/process.md is protected, so this push resets both reviews.For the third round running, a sentence said there was no other route into the decisions index, and a review found one it left out. The review measured #956's route (C1) with an in-memory fixture. Following #899's pattern, this round fixes the class, not the sentence:
One list, in docs/process.md. A new block, "Open routes into the decisions index", is the one list of known routes the two checks don't close:
A route leaves the list only when its fix merges.
Everything else points to the list and no longer claims completeness on its own:
PROCESS_DOCdocstring. Only the two lines round 9 added are reworded, so no line from main is removed;src/coordinator/decisions.ts: two comment lines are added at thedecisionIndexcall, pointing to the list (decisions index: pin the base it reads to the forge's branch #962). None are removed (measured:git diff -U0shows@@ -42,0 +43,2 @@)."A checkout that isn't a git checkout's top indexes no records" (C5): the review measured it as false when a base is given, and a base is the coordinator's only form. It is dropped from the changelog. In this body's round-7 list it is scoped to "only when no base is given".
Verified (measured):
Adversarial pass (round 10)
src/files for "only through a PR", "any route", "apart from that route", "reads nothing else", #962 and #956PROCESS_DOCdocstringsrc/change remove a line that main has?git diff -U0)Round 9 (round-7+8 delta check issuecomment-6309508803: wording only)
Head
2647eeaf(one commit onc0df669a, signed off, no other trailers; measured:git log -1). docs/process.md is protected, so this push resets both reviews.The delta check measured, with a fixture, a route the text didn't cover: a process running as the engine's user in a task worktree can reach the source the coordinator fetches the base from. The next base is then one that process chose, and its committed records are indexed with every id check passing. The code is otherwise unchanged by this round. The by-design fix (pin the base to the forge's branch) is #962, out of this PR's scope. Per the #707 rule, no steps are given here.
.gitredirect add no record) and names the remaining route as decisions index: pin the base it reads to the forge's branch #962:.gitredirect) as well as the round-7 ones, and says the old line was false at 6edea7b too;PROCESS_DOCdocstring insrc/plan-decisions.ts: two comment lines added, none removed (measured:git diff --cached --statgave2 ++), so the guard file's weakening hold has no removed line to flag (inferred);Round 9, body only (round-9 delta check issuecomment-6309874629)
No new commit: the head stays
2647eeaf.git merge-tree; resolutions inferred):a336bf6b) and git: one runner for every engine git call, with replace refs and grafts off (#807) [PROTECTED: owner merges] #927 (6aa0853b) conflict insrc/review.ts. Each addstest/git-runner.test.tsat the spot in the locked-test list where this PR addstest/plan-decisions.test.ts. These are adjacent additions, so keep both entries.3ec041b6) conflicts insrc/review.ts. It addsHARNESS_CONFIGwhere this PR addsDECISION_FILES,DECISION_DIRSandonePartPerGlob. These are also adjacent additions, so keep both sides.// check-table: never-growaboveNOT_YET_FILES, a list this PR grows by 13 entries (each paired with a new lock; see the loosening disclosure below). How they meet:NOT_YETmatches Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886'sALLOW_NAMED, so the list is held on growth even without the mark (inferred, from that diff).src/review.ts, unchanged from round 8 (see Round 7's merge-order note). Whichever PR merges second resolves each conflict, and the combined check must cover it.Adversarial pass (round 9)
src/Round 8 (round-7 adversarial review at 6edea7b, stopped before measuring; its routes forwarded as inferred)
Head
c0df669a. origin/main is stilld2a549f4(measured, after a fetch).git cat-filereturns the forged text (measured)..gitfile rewritten to point at another repository holding that forged pack.decisions.md § D-1: Agents merge).src/plan-decisions.ts: the index walks the base commit itself. It reads the commit, each tree down to a record folder, and each record's blob withgit --no-replace-objects cat-file --batch. It recomputes each object's id from its content (<type> <size>\0<content>, SHA-1 or SHA-256 by the id's length) and compares.src/coordinator/decisions.ts) passes the coordinator's base sha and the instance's own clone (this.d.repo). The objects are read there, never through the worktree's.gitfile..gitredirect. Both index nothing through the forged store and the right record from the clone.recordEntriesanddecisionIndex: "the plan's base commit (HEAD only when no base is given)" (A7).test/fw920r8-plan.test.tsin a fresh worktree at 6edea7b holds the two tests and their helpers, verbatim except a cast for the 4-argumentdecisionIndex. Result: 2 tests, 2 fail: the forged record was indexed both times.d2a549f41,481,905 / 4,963,126. That is +4,547 tarball and +13,914 unpacked, the same 493 entries.Round 7 (second adversarial review issuecomment-6308710896; round-6 delta check issuecomment-6308732220, PASS)
Head
6edea7b8. origin/maind2a549f4is merged in (merge commit29b85112, signed off; measured:rev-list --count HEAD..origin/main= 0 after a fetch). The combined check must be built ond2a549f4or later. Every "41f6dd42" below is a past round's main, not today's.A1 closed by design: the index reads only committed records (required change 1).
recordEntriesread the working tree. So a file at a record path that no PR added was indexed and citable. Untracked files are left by a setup step (npm cirunsprepareand dependency install scripts), and pooled worktrees keep them.recordEntriestakes the records git tracks at the plan's base commit:git ls-tree -r -z --full-tree <base> -- <record paths>, run through the OS layer's child-process module;git cat-file --batch, never from the working tree.basecommit (src/coordinator/decisions.ts) instead of the worktree's HEAD. So a setup step that commits a record in the worktree adds nothing either.--no-replace-objectsmeans a replace ref can't stand another tree in for the base.HEADor a full commit id; the checkout isn't its repository's top (only when no base is given, that is withHEAD; corrected in round 10); git fails; or git's output can't be read.x.mdis a tree entry, not a blob;records()and the config-folder test), now commit their files, since the index reads only committed records. No assertion of main's changed.docs/decisions.md -> ../decisions.md,decisions -> docs/decisionsanddocs/DECISIONS.md -> design.md. Main never had them.Fail-without tests (required change 2). These are new in test/plan-decisions.test.ts:
git replaceof the base and a rev that isn't a full id (main, a short id,--output=x,<sha>^) add nothing;New in engine-guard-locks: a removed line in
src/plan-decisions.tsraises the weakening hold.False claims corrected (required change 3, principle 5):
Recommendations, done:
src/plan-decisions.tsis added toGUARD_FILESin src/merge-policy-weakening.ts. That's an addition, under the registry permission, and a PR touching it is a critical digest..worklane/tests.yamlsetup steps,package.jsoninstall-time scripts and what they run. It notes that the index no longer reads what they write.The weakening hold (measured by the round-6 delta check): main's hold flags
SELF_MODIFICATION_PARTS's changed declaration and closing lines, as expected. It will also flag this round's removed lines insrc/plan-decisions.ts(inferred), since that is now a guard file. The PR waits anyway as critical.Merge-order note, updated (measured by the round-6 delta check): against Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886
c1f13341there are now conflicts in two files:NOT_YET_FILES;// check-table: never-shrinkmark sits above the old= [.Whichever PR merges second keeps each mark directly above its new declaration line (
NOT_YET_FILES's multi-line list, andSELF_MODIFICATION_PARTS ... = onePartPerGlob([).Fail-without (measured): NEW files
test/fw920r7-plan.test.tsandtest/fw920r7-guard.test.tswere written into a fresh worktree at 1d11ea3. They hold the 4 new plan-decisions tests and the guard-file test. The round-4 symlink check is there as its own test, and the base-commit test callsdecisionIndexthrough a cast, since 1d11ea3's has no rev parameter. Result: 5 tests, 5 fail. For example:decisions.md § D-1: Agents merge,docs/decisions/0002-link.md,docs/decisions/0003-merges.md;docs/decisions.md § D-1: Keptwas indexed;After (measured):
Round 6 (round-5 delta check, issuecomment-6308270567)
Head
1d11ea38. origin/main was then41f6dd42(measured, after a fetch). It has since moved, and round 7 mergesd2a549f4.docs/design.md(aDECISION_FILESentry) in two self-modification parts.selfModificationPartnames the first part. test/coordinator.test.ts:2142 walks every part's globs and expected the second, so shard 1/4 failed on Node 22 and 24.SELF_MODIFICATION_PARTSis now built withonePartPerGlob, which keeps each glob only in the first part that lists it.docs/design.mdstays "the release procedure and the design rules".DECISION_FILESandDECISION_DIRS, and every path they name is still locked. The lock itself is unchanged; only the duplicate listing is gone.selfModificationPartnames): 133 globs checked, 0 wrong at the new head; at 41343a7, thedocs/design.mdmismatch.docs/design.mdis the design rules'; everyDECISION_FILESandDECISION_DIRSpath is still a lock glob.SELF_MODIFICATION_PARTSdeclaration line and its closing line change (= onePartPerGlob([...]);). It is a never-shrink table, so the weakening hold flags those two changed lines (inferred). No glob is dropped from the lock:LOCKED_CATEGORIESandselfModificationPartgive the same answers for every path as at 41343a7, except thatdocs/design.mdnow has one name (measured: the partnames walk, and the 12 named files below).computeLevelputs any changed file underself-modification,engine-guardsorrelease-signing.scripts/lib/signing.mjs,src/update-drain.tsandsrc/updater-setup.ts.recordEntriesindex fewer records, never more" is not true in one case (A8d, measured by the delta check). On main, a decisions folder with a dangling entry throws ENOENT, so the plan step crashes. The head skips that entry and indexes the real records (27 in that fixture). No text becomes citable that isn't a real record at a record path (inferred). Corrected in "What this can never do".3b23fa41) in test/merge-policy.test.ts (measured by the delta check withgit merge-tree). Merge policy: a check table or lint test registers itself where it's declared; only removing or loosening one waits (#869) [PROTECTED: owner merges] #886 adds// check-table: never-growaboveNOT_YET_FILES, and this PR rewrites that declaration as a multi-line list. Whichever PR merges second keeps the marker above the multi-line list, and the combined check must cover that merge.test/fw920r6-locks.test.ts) into a fresh worktree at 41343a7, gave 1 test, 1 fail:a glob in two parts, actual['docs/design.md'].What remains
The known open routes are listed in docs/process.md ("Open routes into the decisions index"). Reviews found them one by one, so the list is not proven complete. The by-design closure is #975. The #962, #956, #969, #970, #973 and #975 lines below are highlights of that list, and the list itself is the reference.
Case-variant locked/record paths on case-insensitive checkouts (A10 from #920) #953: A10, case-variant record paths on case-insensitive checkouts.
Docs/adr/x.mdordocs/ADR/x.mdcould be read through a record path. Its git path isn't locked: the delta check measured these variants as L0[docs].Agents act as the owner's GitHub account and main needs 0 approvals: carry out the approved wl-dev migration (own App, owner-only main ruleset) #917: every agent posts as the same GitHub account (the round-4 named residual).
decisions index: pin the base it reads to the forge's branch #962: which commit is the base. The index checks everything it reads from the base commit, but not which commit that is. The coordinator takes it from a fetch whose source a process running as the engine's user can still reach (measured by the round-7+8 delta check). Not a regression: main reads the working tree, which is weaker (inferred). The by-design fix is pinning the base to the forge's branch, out of this PR's scope.
Decisions index cites agent- or reporter-written question text as an owner decision #956: question and answer text in decisions already made. A made decision is indexed as
question -> answer. The question can be an agent's or a reporter's words, and the answer is one of the asker's options, so for an agent's question an agent wrote it too, uncapped and unredacted (A5 in round 8, measured by reading the code; the answer measured by the round-10 second review). Not a regression: main does the same (inferred).decisions index: trust only answers recorded through the answer path #969: decisions already made that no one answered. The index doesn't check who answered or how, so whatever can write the instance's event log can add a citable decision (measured by the round-10 second review). Not a regression (inferred).
plan brief: give planners the committed record text, not the checkout copy #970: the checkout copy planners read. Planners and the plan reviewer are told to read the record files in their checkout, which can differ from the committed text the index cites (measured by the round-10 second review with a fixture). Not a regression: main's index read the checkout itself (inferred).
planner runs: load no project-level agent instructions from the checkout #973: project instructions in planner runs. Planner and plan-reviewer runs load the agent's project instructions and config from their checkout. That text reaches every planner as instructions. In this repo it is L3, but no critical-set subject names it (the run's command line, working directory and categories were measured by the round-11 second review with a probe; the loading itself is inferred from vendor docs). Not a regression: main loads them too (inferred).
planner runs: take input only from an allowlist enforced in code #975 (open): planner input from an allowlist enforced in code, the by-design closure. These known routes are listed under it:
None is a regression: main has them all (inferred).
Round 5 (second adversarial review issuecomment-6307750538, round-4 delta check issuecomment-6307787151)
Head
41343a76. origin/main had not moved (measured:rev-list --count HEAD..origin/main= 0 after a fetch), so there was nothing to merge.SELF_MODIFICATION_PARTSinsrc/review.ts:DECISION_FILESpath, and everyDECISION_DIRSfolder itself (a symlink to anywhere) and<dir>/**;src/plan-decisions.ts.src/review.ts, andsrc/plan-decisions.tsimports and re-exports them, so every existing import still works.review.tsimportingplan-decisions.ts:src/review.tsis in the agents' tool guard's pinned import tree. Importingplan-decisions.tsthere would pull it andsrc/events/redact.tsinto that tree.test/review.test.tswould then fail (GUARD_TREE; redact.ts is in LEFT_THE_TREE), and adding them to GUARD_TREE would be an allowlist entry in a locked test (inferred from the import lines; not run).engine-guardsin.worklane/review.yaml(13 record paths plussrc/plan-decisions.ts). Also inlocked,presentandbeforeintest/engine-guard-locks.test.ts, and inNOT_YET_FILES(below).recordEntriesindexes the record there. It then checks the path is L3 self-modification under all three review.yaml files and underengine-guards(measured: passes).engineSourceGlobnow also matches the decision-record globs, exactly. A managed app's records keep the categories its review.yaml gives them (measured: thebeforetable for all 14 new paths, as measured at cfc69f0; andtest/review.test.ts's exact list of the globs that hold in every repo still passes unchanged).locked-paths.jsoncases wait withhigh-risk: self-modification:decision-record-docs-only: a newdocs/decisions/0001-x.mdwith a reworded copy of the rule;decisions-index-code: an edit ofsrc/plan-decisions.ts.recordEntriesnow indexes a record only when all of these hold:realpathSync) is inside the checkout's real path;DECISION_FILESentry, or a file directly in aDECISION_DIRSfolder;docs/process.md(same device and inode);docs/process.mdexists but can't be stat'd (anything but ENOENT: EACCES, ELOOP, ...), or the checkout's own realpath fails, no record is indexed.<clone>/.claude/worktrees/plan-1withdocs/decisions.md -> ../../../../docs/process.md, plus links out to the clone's other files and a decisions folder linked to the clone'sdocs/;docs/process.md;docs/(mode 0600) with a top-leveldecisions.mdhard-linked to the page.docs/adr -> ../notesmerged, a later L0 edit tonotes/0001.mdchanged an indexed record (measured on the first round-5 build: indexed, andnotes/0001-merges.mdunlocked). A link between record paths (decisions -> docs/decisions,docs/DECISIONS.md -> design.md) is still indexed (measured).decisions.md -> notes.md), drops out of the index. A plan that cites it is then refused, with the message that lists the record paths. Whendocs/process.mdexists but can't be read, no record is indexed. Plans can still cite decisions already made. The changelog fragment says this under Upgrading. Round 4's test asserted thatdocs/decisions.md -> notes.mdis indexed; that assertion is now a link between record paths. The PR added that assertion, so main never had it.The engine's ownCorrected in round 6: that wording let the code take a PR out of the owner's subjects. The code now only adds, and the subjects decide. If it can't be run or read, the PR is in.computeLevelorselfModificationPart, run on the PR's diff, decides.recordEntries. Corrected below.NOT_YET_FILESintest/merge-policy.test.ts(owner decision Review levels: the security pages, their citations test, the adversarial suite and the Dockerfile are locked as engine guards (#858) [PROTECTED: owner merges] #885 option 2: added entries, each paired with a new lock). That list exempts a glob from the check "self-modification: every glob names real engine files (none dead)". The check's finding for these 13 globs would be "matches no file". That finding is true but not a fault: they are locks placed before the first record exists (measured: no tracked file matches them).test/not-yet-files-locks.test.tspasses, so each entry is paired with its lock.NOT_YET_FILESis a never-grow table, so the weakening hold flags these lines (inferred).Round 4 (second adversarial review, issuecomment-6307564006)
The symlink route is closed by design.Corrected in round 5: false for a symlink out of the checkout. See Round 5.recordEntries(src/plan-decisions.ts) skips any decisions file or folder entry that is the checkout'sdocs/process.md. It compares identity (device and inode, through symlinks), so a hard link is covered too.test/plan-decisions.test.tsis now under "the tests of the harness controls" inSELF_MODIFICATION_PARTSand underengine-guardsin review.yaml. It is also inlocked,presentandbeforeintest/engine-guard-locks.test.ts.engine-guard-locksandplan-decisionsgave 3 fails: the two lock tests for the test file, and the new symlink and hard-link regression. After the fix: 0 fail.What changed (round 3, with rounds 4 and 5 above on top)
src/review.ts, plus review.yaml'srelease-signingandengine-guards(this page included). The changed files decide, never the title marker; when unsure, a PR is in.[PROTECTED: owner merges]no longer means the owner merges. It marks a protected path, which is far wider than the critical set.sectionsOfdrops the level-1 title section, so the pointer is not indexed (measured by the test below).docs/process.md(round 3). Round 5 adds the decision-record and index parts, and the record lists (above).docs/process.mdadded underengine-guards, beside design.md, and the round-5 entries.test/plan-decisions.test.ts: in this repo, no decisions-index entry carries the merge process, and a fixture'sdocs/process.mdis never indexed. Round 5 adds the fail-closed cases.test/engine-guard-locks.test.tsandtest/merge-policy.test.ts: docs/process.md, the decision records andsrc/plan-decisions.tsare self-modification and L3, never auto-merge, and sit under engine-guards. They are not locked in a managed app.merge-process-docs-only,decision-record-docs-onlyanddecisions-index-code.Corrections to earlier claims in this body (principle 5)
decisionIndexreturning §20. Now the rule is in docs/process.md, which noDECISION_FILESentry orDECISION_DIRSfolder names, and the test holds that (measured).src/finds no reader ofdocs/process.mdother than the lock glob andPROCESS_DOC).engine-guardspath.How it was verified
Round 5 fail-without (measured). I wrote the new tests as NEW files into a fresh worktree at the round-4 head
cfc69f06:test/fw920r5-plan.test.ts: the 4 new plan-decisions tests, verbatim;test/fw920r5-locks.test.ts: the head'sengine-guard-locks.test.ts, verbatim;test/fw920r5-mp.test.ts: the new merge-policy assertions (part names, and L3 under a review.yaml that lists nothing);test/adversarial/cases/fw920r5.json: the 2 new cases.Result: 12 tests, 11 fail, 1 pass. The one that passes is the managed-app "unchanged" test, which should pass before and after. Each failure is for the right reason:
decisions.md § D-1: Agents merge,docs/decisions.md § Who merges,§ The critical set,decisions/process.mdanddocs/adr/0001-merges.mdwere all indexed;decisions.md § D-1: Keptanddocs/design.md § 8. Roleswere indexed;decisions.md § Who mergesand§ The critical setwere indexed;selfModificationPart('decisions.md')was null, anddecisions.mdwas L0 under this repo's review.yaml;decision-record-docs-onlymerged under the always-PASS stub.decisions-index-codehad onlyhigh-risk: engine (src/plan-decisions.ts), with no self-modification.Round 5 after (measured): all run with a scratch HOME, TMPDIR, state and held-out dirs, and
claudeandghstand-ins first on PATH.ghwas never called. None of these tests sets its own PATH.node --import ./dist/test/isolate.js --teston plan-decisions, engine-guard-locks, not-yet-files-locks, merge-policy, review, adversarial, brand, changelog, changelog-fragments, changelog-fragments-selfcheck, docs-guardrails and plan-mode: 259 tests, 247 pass, 0 fail, 12 todo;run typecheck: clean;run check:denylist: "1397 files clean";Not run locally:
test/managed-apps.test.ts, because it sets its own PATH with /usr/bin. CI runs the full suite, which I did not rerun.Earlier rounds' results are in the git history of this body. Round 4: 240 tests, 228 pass, 0 fail, 12 todo.
Adversarial pass (round 5)
All on scratch fixtures, against the round-5 build.
docs/decisions/0001-x.mdhigh-risk: self-modification, under all three review.yaml filessrc/plan-decisions.tsdecisions-index-code)src/review.ts("the review levels", already L3)docs/process.mdunreadable (ELOOP, or an unsearchabledocs/with a hard link)docs/adr -> ../notes(locked, so it needs both reviews), then editnotes/0001.mdat L0docs/adr(a file in git, not underdocs/adr/**)docs/adr)--no-renames)Docs/adr/x.md, sorted beforedocs/)present()checks only the last path part's case, so on macOS or Windows such a file could be read asdocs/adr/x.mdwhile its git path isn't locked. Linux CI can't build this. Proposal: check every path part's exact case inrecordEntries, failing closed. Not done here because it can't be tested in CIengineSourceGlob(one condition added), theNOT_YET_FILESfirst line (it lost its];), and the plan-decisions record lists (moved tosrc/review.ts, same values)Package size
Measured with
npm pack --dry-run --json --ignore-scripts, without the web UI built. This is the method the round-4 delta check used. My measurement of cfc69f0 matches theirs exactly (1,470,597 / 4,928,020), so their main figure is comparable.41f6dd42(delta check's measurement)cfc69f06(measured, mine)41343a76(measured, mine)1d11ea38(measured, mine)d2a549f4(measured, mine, a worktree at main)6edea7b8(measured, mine)PACKAGE_BUDGET.dist/src/plan-decisions.js11,109 to 16,973 (+5,864);dist/src/review.js39,950 to 43,550 (+3,600);merge-policy-weakening.js(one GUARD_FILES entry) andcoordinator/decisions.js(the base argument). Inferred, not split per file.The earlier rows compare with the earlier main
41f6dd42:dist/src/review.js(onePartPerGloband its comment).PACKAGE_BUDGET(2,050,000 / 6,400,000).dist/src/review.js40,439 to 42,785 (+2,346), anddist/src/plan-decisions.js12,011 to 13,731 (+1,720), together +4,066. docs/, test/, changelog.d/ and the config folder are not packed.New dependencies
None.
What this can never do
onePartPerGlobdrops only a glob's repeat listing, never its only listing.recordEntriesindexes fewer records wherever main didn't throw. The one exception is a decisions folder with a dangling entry: main throws ENOENT there and the plan step crashes, while the head skips that entry and indexes the real records (A8d, measured by the delta check). Since round 7, a record's text is indexed only when it is a regular file committed at a record path in the plan's base commit (measured: the round-7 tests). Text can still reach planners in other ways. The known ones are listed in docs/process.md, and that list is not proven complete (planner runs: take input only from an allowlist enforced in code #975). At 1d11ea3 that was false for untracked files (A1).engineSourceGlobnow also matches the new decision-record globs, exactly. So those new locks stand aside in a managed app, but no existing lock does (measured: the every-repo glob list in test/review.test.ts is unchanged).tmpdir()(inferred, from reading the tests).Compromised component
src/worktrees.tsandsrc/runner.ts, per the round-9 second review)..gitfile add no record to the decisions index (measured: the round-7 tests for untracked, staged and committed-in-worktree records; the round-8 tests for the forged object store and the.gitredirect). That is about the index only. What it can still do is the open routes listed in docs/process.md, for example the checkout copy planners read (plan brief: give planners the committed record text, not the checkout copy #970).computeLevel, and the only effect is that more paths wait.docs/process.mdexists but can't be read. That fails closed: plans citing them are refused.src/finds no other reader).