Skip to content

Slim the test suite, and treat an undeclared supportsInjectedMessages as false - #934

Merged
nissa-seru merged 170 commits into
anima-research:mainfrom
nissa-seru:test-slimming
Oct 30, 2026
Merged

nissa-seru merged 170 commits into
anima-research:mainfrom
nissa-seru:test-slimming

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

This slims the test suite file by file, makes a provider stream or call that a test didn't script fail that test in six test files, and changes src in two commits, each with a changelog fragment.

Of its 170 commits, 161 slim one test file each, two of them with a helper or fixture beside it. Between them they remove tests, or checks within tests, that we judged to check how the code works rather than what a caller relies on, to repeat another check, or to be unable to fail; rewrite others to check the requirement; and replace fixed sleeps with waits on events, or shorten them where a test-only setter or the test's own timeout allows. One more, 5c8ee8fcb8, has tests that poll a fixture's JSONL log parse it only up to its last newline, and another, 662c6cd4c6, makes a code_execution test wait for the first turn to end before it lets the test's script go on to its wake; under Bun, that test failed in this pull request's CI. In two alternating whole-suite rounds on Linux at Node 22.12.0, run at 32da22bfdd, an earlier head of this branch, the suite ran 9,392 tests where d700dce18b, then main, ran 9,437, and took 7.2% and 13.0% less wall time than d700dce18b and 0.7% and 1.7% less CPU time, so the gain is mostly in wall time; the second round's figure is likely overstated, since its run of d700dce18b met the heaviest load of the four (Tests has the figures, and what has changed since). In src, 93cdb7d628 reads a stream that doesn't declare supportsInjectedMessages as one that doesn't carry injected messages, and bbba75bc59 removes two readings kept for older inputs: XML tool mode's fallback for membrane before 0.5.79, and a version 1 Discord awareness ledger's refusal of its own. It closes #886.

Problem

At main (c5306693e0):

  • Some tests check how the code works rather than what a caller relies on. That's our judgment of each test this change removes or rewrites. For example, "every state the framework registers goes through ensureStateRegistered" (test/state-registration-errors.test.ts:319) collects the lines under src that match /\.registerState\(/ (:333), leaving out comments (:332), and asserts that there is one and that it's in module-registry.ts (:338–:339).
  • In outgoing-stream, a provider stream that a test didn't script didn't fail the test. test/outgoing-stream.test.ts passes 57 of 57 tests, and its output logs "unexpected extra inference" three times. Its three tests of a publish the server reports undelivered, "hybrid >>> target", "explicit mid-turn" and "explicit text-only", take about 3 s each.
  • The derive skip guards don't skip on any version package.json accepts (Derive skip-guards check view/derive alone; derive now needs selective forks (supportsDerivation) #886). Seven places skip shared-inheritance and reader-fork tests unless JsStore.prototype.view and ContextManager.prototype.derive are functions, such as test/dendrite-derive.test.ts:31–:32. One of them is test/helpers/reader-fork-rig.ts's exported skip (:24), which three tune-out-reader files take. As Derive skip-guards check view/derive alone; derive now needs selective forks (supportsDerivation) #886 says, derive also needs chronicle's selective forks, which the guards don't check. package.json accepts chronicle ^0.5.1 and context-manager ^0.24.0 (:51–:52), and on October 30 at 13:07 UTC npm has chronicle 0.5.1 and context-manager 0.24.0, 0.24.1 and 0.24.2 in those ranges. With each of the three pairs, ContextManager.supportsDerivation returns true for a fresh store, and the nine files whose guards this change removes run 68 tests, none skipped.
  • src kept two readings for older inputs, and a field set only to an empty list.
    • In XML tool mode, a round's persisted text is roundPreamble ?? event.context.preamble (src/framework.ts:25197–:25199). The comment above it gives roundPreamble as membrane 0.5.79's and later, and the preamble as cumulative in XML mode (:25191–:25193). package.json requires membrane ^0.8.0 (:53).
    • A version 1 Discord awareness ledger has a refusal of its own (src/recovery/discord-awareness-outbox.ts:1321–:1330), whose message says that only agent-framework commit b16731b2 wrote that version, "before any release".
    • ReferenceTestimony.truncatedFields appears in src only at its declaration (src/mcpl/references.ts:53) and where it's set to out.truncated, an empty list (:185–:186). takeString, which out is passed to, types it as { rejected: string[] } (:84). src/index.ts doesn't export ReferenceTestimony.
  • src read a stream's supportsInjectedMessages three ways. src/framework.ts tests it as currentState.supportsInjectedMessages !== false at the tool boundary (:16604), as === true at :16967 and :17003, and reads it with ?? true for presentsInjections (:25273–:25274). For a stream that leaves it out, the first and third give true and the second gives false. The comment at :25222–:25225 gives it as absent on membrane 0.5.x. membrane 0.8.0's own tests assert that a yielding stream's tool round declares it true on the native path and false on the XML prefill path (tests/unit/round-report.test.ts:163 and :310 at antra-tess/membrane v0.8.0). The suite's test/helpers/mock-membrane.ts declared it only when a test passed a value: its default is undefined (:204), and its tool-calls event carries the field only when it's defined (:125–:126).

Changes

src, in two commits

  • bbba75bc59 removes these:

    • XML tool mode's fallback to preamble, so a round's persisted text is its roundPreamble. The test that pinned the fallback goes ("legacy membrane without roundPreamble keeps the cumulative fallback (pinned)", test/xml-round-persist.test.ts:272 at main), and so does its mock's switch for leaving roundPreamble out.
    • the version 1 ledger's own refusal, the if (version === 1) branch, with its test. README.md's sentence on version 1 now says such a ledger "is refused as any version but 2 and 3 is, until it is moved away".
    • truncatedFields, with the test lines that checked it was empty.

    It also changes deriveAgent's doc from "It needs branch-bound store handles" to "It needs a store that supports derivation (ContextManager.supportsDerivation)", the sentence Derive skip-guards check view/derive alone; derive now needs selective forks (supportsDerivation) #886's "Also" quotes. Derive skip-guards check view/derive alone; derive now needs selective forks (supportsDerivation) #886 places that sentence above deriveSharedContext; at main it's in deriveAgent's doc (src/framework.ts:10957). Fragment: changelog.d/unsupported-compat.changed.md.

  • 93cdb7d628 reads the declaration as === true at a tool boundary with a stream: main's :16604 becomes currentState.supportsInjectedMessages === true || !currentState.stream (:16609 here), and its :25274 becomes === true (:25264 here). A tool boundary with no stream, as the direct API's (Agent.runInference) is, still enters that branch, through || !currentState.stream.

    • MockMembrane and MockYieldingStream now declare true unless a test passes false, or 'undeclared' to leave the field out.
    • Tool rounds that six test files build by hand now declare it: true where the round carries roundContent (end-turn-usage, framework, exhausted-turn-rearm, tool-wrapper-prose-guard-integration, and same-round-think-policy's paused stream), and false in skip-reply-wake-xml-mode.
    • Three tests asserted what an undeclared stream got. deferred-window-only's becomes "a stream that doesn't say whether it carries injections is taken as one that can't: what is queued waits for the turn to end". module-query-for-agent's and route-failure-targeting's go, and their siblings with a stream that declares it stay.
    • New tests: deferred-window-only's "a boundary with no stream, the direct API's, stores what was queued for the agent there", which also checks that the stored message stays unread, clock-rows' "after a direct-API tool round whose result ends the turn: the clock follows the results", and prose-drafts' "a stream that doesn't say whether it carries injections gets no mid-turn notice: its held reply is named in the receipt".
    • README.md's paragraph on unread messages now says "A stream that doesn't say whether it carries injections is taken as one that doesn't", and Agent.markContextShown's doc comment now speaks of "a tool boundary that injected messages into its stream". Fragment: changelog.d/injection-declared.changed.md.

The derive skip guards

Closes #886.

c58280c59f removes the seven guards and the skips they set, in compression-after-release, dendrite-derive, dendrite-fork-view, tune-out-reader-forks-e2e, tune-out-reader-epoch-shared, turn-inputs-pin and test/helpers/reader-fork-rig.ts, whose skip the three other tune-out-reader files took. #886 proposes one shared guard on ContextManager.supportsDerivation. This removes them instead, since every chronicle and context-manager version on npm that package.json accepts can derive (Problem, above). A chronicle without the selective forks, which #886 says a linked tree could give, fails 17 of these tests at main and here alike (Tests). #886's "Also" asks for the doc line to name the selective forks; it now names ContextManager.supportsDerivation, which returns false without them (bbba75bc59, above; Tests).

In six files, a stream or call a test didn't script fails that test

d9b8eddcc8 changes the ScriptMembrane mocks in outgoing-stream, stop-without-stream and tool-result-guard: a stream the framework opens with no script left is recorded before the mock throws, and an afterEach in each file fails the test that opened one, naming each such stream and the last message its request carried. The three undelivered-publish tests in outgoing-stream now script the turn their bounce wakes and check that its request carries the bounce notice. 062c6fc0c0 does the same for the hand-written stubs in conversation-spawn-in-flight, state-read-and-write-errors and tool-result-well-formed: each records a call it has no response for, and its test checks there was none.

Four more commits

  • 20fe9ac6c9 answers an internal review of stop-with-work-in-flight: its header now says which pushes to a stopped framework throw, its wait for a tool to start is bounded at 5 s, and a stubbed pushCoalescer is restored in finally.
  • 96445deb6f makes workspace-three-way's two gate tests wait until one of chokidar's reads has reached the closed gate before they open it. One of the two is the test d700dce18b failed once in the whole-suite rounds below. The commit's message gives its red-first runs.
  • 5c8ee8fcb8 has 14 test files and test/helpers/reader-fork-rig.ts read a fixture's JSONL log through a new helper, test/helpers/json-lines.ts. Its jsonLines parses the file only up to its last newline, and returns [] for a file that doesn't exist yet (Tests). The commit's message lists the reads it changes, which it says are each made while the fixture can still be writing, and the reads it leaves, each with why.
  • 662c6cd4c6, this head, makes "a resident's left script wakes it with wake_agent(), and its failure wakes it too" in test/code-execution.test.ts wait for the first turn's inference:completed before it releases the script's first slow call, which the script awaits before its wake, as the test's comment "Turn 1: start the script, which yields; then rest." describes. At 68db93392a, an earlier head of this branch, the test failed on all four of this pull request's Bun CI legs (Tests). The commit's message gives the cause, which it found by run.

For what each of the other commits changes and why, read its message.

This branch was caught up onto main c5306693e0 on October 30, which gave every commit a new hash. By git range-diff, 168 of the 170 commits make the same changes as before. The other two are 6bb7b446b0, which slims test/mcpl-transport-close.test.ts, where #935 had changed one of the lines it removes, and 5c8ee8fcb8, whose diff differs only in the unchanged lines around an import it adds to test/mcpl-down-for-good.test.ts.

Open pull requests

By git merge-tree on October 30 at 13:04 UTC, 43 other pull requests were open. Five of them conflict with this head in files where they don't conflict with main c5306693e0: #844 (next section) and four of the house's own. #690 merges cleanly with main and conflicts with this head in src/framework.ts. #850, #256 and #257 already conflict with main, and this head adds one file to each one's conflicts: test/event-gate-judge.test.ts to #850's, and test/inference-exhaustion-rewind.test.ts to #256's and #257's. In #256, #257 and seven more, a conflict in a file that conflicts either way has different lines with this head, because lines this change makes fall inside it. The seven are the house's #253, antra-tess's #195 and #201 (below), and #155, #163, #177 and #436, from authors outside the house. The other 31 merge with this head as they do with main, with the same conflicted files and conflict lines.

What each part is for

  • The slimming commits remove or rewrite tests we judged to check how the code works rather than what a caller relies on, to repeat another check, or to be unable to fail, and replace fixed sleeps with waits (the lead, and Problem's first bullet). For each commit's reasons, read its message.
  • 93cdb7d628's one reading of supportsInjectedMessages. At main, the three places Problem names give a stream that leaves the field out true, false and true. Reading it as === true in each place gives such a stream one answer.
  • MockMembrane and MockYieldingStream declaring true, so the tests that use them get the declaration membrane 0.8.0's own tests assert for a yielding stream's tool round on the native path (Problem).
  • || !currentState.stream at the tool boundary keeps the direct API's boundary, which has no stream, entering that branch. Two new tests cover it (Changes).
  • bbba75bc59's three removals and its doc line each have their reason in Problem's fourth bullet and the bullet for bbba75bc59 under Changes.
  • c58280c59f's removal of the derive skip guards: "The derive skip guards", under Changes.
  • The checks for unscripted streams and calls (d9b8eddcc8, 062c6fc0c0): Problem's second bullet.
  • 20fe9ac6c9, 96445deb6f, 5c8ee8fcb8 and 662c6cd4c6: each one's bullet under "Four more commits".

With #844

@tel-0s, by git merge-tree on October 30 at 13:05 UTC, #844 at aeac830921 includes main's c5306693e0 and merges with it cleanly, and conflicts with this head, 662c6cd4c6, in 20 files: src/agent.ts, src/framework.ts and 18 test files. In one of them, test/deferred-write-failures.test.ts, #844 deletes the file and this change modifies it.

With #195 and #201

@antra-tess, by git merge-tree on October 30 at 13:04 UTC, each of these conflicts with this head in the same files as with main c5306693e0, and in src/framework.ts one conflict's lines differ: this head's side carries this change's tool-boundary condition, currentState.supportsInjectedMessages === true || !currentState.stream, and the comment above it, where main's side has !== false. In #195's conflicts in test/channel-registry-routing.test.ts and test/present-while-acting.test.ts, this head's side carries this change's slimmed lines.

Tests

The whole suite, before and after. These rounds ran at 32da22bfdd, an earlier head of this branch, and at d700dce18b, then main. Since then the branch has moved onto main c5306693e0, after #910, #909, #913, #912, #929, #932, #930 and #935 merged, 5c8ee8fcb8 and 662c6cd4c6 have been added, and fixes from a third internal review have changed four commits; the rounds ran none of these. 32da22bfdd merged onto c5306693e0 by git merge-tree conflicts in one file, test/mcpl-transport-close.test.ts, which #935 changed. In the other files it differs from 96445deb6f, the commit before 5c8ee8fcb8, in six (git diff --numstat): src/agent.ts +1 -1, a doc comment; test/prose-drafts.test.ts +23, one more test; test/turn-inputs-pin.test.ts +2 -9, its guard removed; and, from those fixes, test/deferred-window-only.test.ts +1, test/mcpl-tool-resolution.test.ts +10 -3 and test/operator-surgery.test.ts +10 -3. Linux x86_64 (kernel 6.12), 12 CPUs, each run pinned to CPUs 0–3 with taskset; nodejs.org's Node 22.12.0 (npm 10.9.0). Dependencies were resolved once at 32da22bfdd with no lockfile, as CI has none, and then installed with npm ci from that lockfile on both sides: @animalabs/membrane 0.8.0, @animalabs/context-manager 0.24.1, @animalabs/chronicle 0.5.0, typescript 7.0.2, @types/node 22.20.5. npm run build, then npm test with AF_TEST_CONCURRENCY=4. Two alternating rounds, d700dce18b first, each run starting once the one-minute load was under 8. CPU is the run's user plus system time.

Round Commit Tests (pass / fail / skipped) CPU Wall One-minute load, start → end
1 d700dce18b 9,437 (9,374 / 1 / 62) 607.2 s 312.3 s 0.50 → 7.17
1 32da22bfdd 9,392 (9,330 / 0 / 62) 603.2 s 289.7 s 7.17 → 3.61
2 d700dce18b 9,437 (9,375 / 0 / 62) 637.0 s 336.9 s 3.61 → 15.57
2 32da22bfdd 9,392 (9,330 / 0 / 62) 626.2 s 293.2 s 6.65 → 3.89
  • 45 fewer tests at 32da22bfdd. Wall time 7.2% less in round 1 and 13.0% less in round 2. CPU time 0.7% and 1.7% less, which is about 0.2% and 1.2% less per test.
  • By their own notes, other jobs ran on the machine during three of the four runs; no such note covers round 2's run of 32da22bfdd. Round 2's run of d700dce18b ended at a one-minute load of 15.57, so its 13.0% is likely overstated.
  • d700dce18b's one failure, in round 1, is workspace-three-way's "starts once the watcher is ready, …", at its check "some of chokidar's directory reads waited at the closed gate", one of the two tests 96445deb6f changes.

Single files, red first. Linux, Node 24.22.2 (npm 11.19.1), each commit a clean checkout with dependencies resolved by npm install with node_modules and package-lock.json removed: membrane 0.8.0, context-manager 0.24.2, chronicle 0.5.1, typescript 7.0.2, @types/node 22.20.5, except where a pin is named. npm run build, then each file alone with node --test. These runs are at this head, 662c6cd4c6, and at main (c5306693e0), except the last bullet's, which names its own commits.

  • Unscripted streams.
    • At main, outgoing-stream passes 57 of 57 with three "unexpected extra inference" lines, and the three undelivered-publish tests take 3,051, 3,048 and 3,053 ms.
    • At this head, the six files pass whole (outgoing-stream 56, stop-without-stream 43, tool-result-guard 156, conversation-spawn-in-flight 6, state-read-and-write-errors 12, tool-result-well-formed 16), with no such line, and the three tests take 22, 27 and 26 ms.
    • With those three tests' wake left unscripted again, by one line changed in the built test file, exactly those three fail, each at the afterEach check "every stream the framework opened was one the test scripted".
    • Added to each of the other five files: in stop-without-stream and tool-result-guard, a test that runs a turn with no stream scripted; in each stub file, one more inference after the scripted ones, its error caught. At main every test in the five files passes with the addition. Here each addition fails its test at the new check, "every stream the framework opened was one the test scripted" for the two mocks and "every inference the framework asked for was one the test scripted" for the stubs. In tool-result-well-formed the extra inference is in a helper two tests share, so both fail.
  • The injection reading. At this head, each change below is one expression in the built dist/src/framework.js, run against ten of the eleven test files 93cdb7d628 changes, all but framework.test.ts. All ten pass with the build unchanged, prose-drafts 254 of 254.
    • main's !== false back at the tool boundary: only deferred-window-only's "a stream that doesn't say whether it carries injections is taken as one that can't: …" fails.
    • || !currentState.stream taken out: only deferred-window-only's "a boundary with no stream, the direct API's, …" and clock-rows' "after a direct-API tool round whose result ends the turn: …" fail.
    • presentsInjections read with ?? true again: only prose-drafts' "a stream that doesn't say whether it carries injections gets no mid-turn notice: …" fails, at "no mid-turn notice: a stream that doesn't say is taken as one that carries no injections".
  • The derive guards.
    • At both commits, with chronicle 0.5.1 and each of context-manager 0.24.0, 0.24.1 and 0.24.2 pinned in turn, a fresh JsStore gives ContextManager.supportsDerivation(store) === true, and the nine files whose guards this change removes run 68 tests: 68 pass, none skipped.
    • With a preload that deletes createBranchWithStates and createBranchAtWithStates from chronicle's JsStore.prototype, at both commits: supportsDerivation returns false, and 17 of the 68 tests fail and none skip. Twelve, in compression-after-release, dendrite-derive, dendrite-fork-view and turn-inputs-pin, fail with ContextDerivationUnsupportedError: Context derivation needs branch-bound store handles (JsStore.view) and branches forked with selected states (createBranchWithStates, createBranchAtWithStates), which this @animalabs/chronicle build does not provide. The other five, one in each tune-out-reader file, fail by timing out, three "waiting for: a reader fork is derived", one "waiting for: a reader fork is derived (coalesced wake)" and one "waiting for old reader request and new hold", and each logs a reader fork failing with that same message.
  • The fixture-log reads (5c8ee8fcb8). No test fails without this commit, since a read can't be made to land in the middle of a fixture's append on demand.
    • test/helpers/json-lines.ts, imported as it is at this head into a script run with Node 24.22.2, which strips its types: for a file whose third line stops inside a string ({"a":1} and {"a":2}, each ending in a newline, then {"a":"b), jsonLines returns the first two lines, and readFileSync(path, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)) throws SyntaxError: Unterminated string in JSON at position 7 (line 1 column 8). For a path that doesn't exist, and for a file holding only {"a":"b, jsonLines returns [].
    • Built at this head, each of the 14 test files the commit changes passes, and so do the three tune-out-reader files that use reader-fork-rig.
  • The third review's fixes. Built at this head, each of the four test files they change passes: deferred-window-only 85, mcpl-tool-resolution 6, mcpl-transport-close 34 and operator-surgery 27.
  • main's newer tests. Built at this head, each of the 15 test files that fix(gate): refusals name fields as the agent wrote them, and file errors give their path and what to do #910, fix(tune-out): a channel's cancel makes its dump durable before it records the end #909 and fix(framework): awaited tool calls and the turn's dispatch share one routing function #913 changed or added passes, and so does each of the 13 test files that main changed between fd6d6a362c and c5306693e0. Eight of them import this change's MockMembrane, among them fix(gate): refusals name fields as the agent wrote them, and file errors give their path and what to do #910's gate-messages-as-written (18 of 18) and fix(framework): awaited tool calls and the turn's dispatch share one routing function #913's awaited-tool-routes (10 of 10).
  • The script wake test under Bun (662c6cd4c6). It is code-execution's "a resident's left script wakes it with wake_agent(), and its failure wakes it too".
    • This pull request's CI at 68db93392a, an earlier head of this branch (https://github.com/anima-research/agent-framework/actions/runs/41039082681) failed on all four "code_execution under Bun" legs, Linux and macOS at Bun 1.4.0 and 1.4.3. In each, test/code-execution.test.ts passed 98 of 99, and this test failed at "timed out waiting for the wake's inference request", after the line "[wake] prime: dropped 1 wake(s) for writes an answered request carried, …". Every other file the job runs passed. At e53cd6bade, the branch's next head (https://github.com/anima-research/agent-framework/actions/runs/41044640947), and at this head (https://github.com/anima-research/agent-framework/actions/runs/41056481378), all four legs pass.
    • Our runs: Linux, Bun 1.4.4 and Node 24.22.2, each commit a clean checkout with node_modules and package-lock.json removed and dependencies installed by npm install --ignore-scripts, as CI's Bun job does, and the test files run from source with bun test --timeout 60000. The runs that printed their dependencies' versions printed chronicle 0.5.1, context-manager 0.24.2, membrane 0.8.0, typescript 7.0.2 and @types/node 22.20.5. The three runs at a low load below ran before npm published context-manager 0.24.2.
      • At 68db93392a, in runs that started and ended at one-minute loads between 12.8 and 17.5, test/code-execution.test.ts failed this test in two of six runs, each time at the same line after the same [wake] line, and passed 99 of 99 in the other four. Alone, the test passed in each of three such runs, and failed in each of three that started at a one-minute load of 0.10, at the same line after the same [wake] line.
      • At e53cd6bade, the file passed 99 of 99 in each of six runs, the test alone passed in each of ten, and each of the other ten files the Bun job runs passed. Built with npm run build, the file passes 99 of 99 under Node. At this head, the file passed 99 of 99 in each of ten runs.
      • At fd6d6a362c, an earlier main, the test alone passed in each of three runs, and the file passed 99 of 99.
      • With scriptWake's this.pendingRequests.push({ … reason: 'script:wake', … }) in src/framework.ts made to run only when agentName === '', the test alone fails at e53cd6bade in each of three runs, at "timed out waiting for the wake's inference request".

Not verified

  • CI's time for this change set against main's. This pull request's CI runs give its time, and we haven't compared them with main's.
  • The whole suite at Node 24, or on macOS or Windows. The counted rounds ran at 32da22bfdd, and this pull request's CI ran it at this head, on Linux at Node 22.12.0.
  • Only tests that an internal reading pass flagged were judged. The pass read the test files at dd31225c71, a commit that no branch of this repository holds now: 0.29.0 and 26 commits after it, from October 28, so a test that isn't in the test files at that commit wasn't in it.
  • A word-for-word read of Cordelia-2434's first round of review fixes by a colleague who built none of the change. The internal review read it.
  • Not every rewritten test was run against a mutation of what it checks.
  • No test at this head reads a version 1 Discord awareness ledger. Its refusal tests cover version 4, no version, and a version written as text (test/discord-awareness-outbox.test.ts:524–:535).

  • Changelog fragments added: changelog.d/unsupported-compat.changed.md and changelog.d/injection-declared.changed.md.

Hilda-2580 and Cordelia-2434 judged the flagged tests and made the slimming commits, and Hilda-2580 made the src commits, the #886 commit and 20fe9ac6c9. Nigel-2405 made the two commits for unscripted streams and calls, and Pippa-2571 the gate-wait fix. Ada-1017 placed the work, set the rules the judging followed, and ruled on the questions put to her, cover-changing removals among them. Effie-2756 showed by run what membrane's XML path does with a message said while a turn runs. Esther-2584 and Effie-2289, who built none of it, read the change by file as it stood before the review fixes: Effie-2289 most of the test files Cordelia-2434 slimmed, and Esther-2584 the rest. Esther-2584 then read Hilda-2580's first round of review fixes, and, word for word, the last round of review fixes and the two test changes after 32da22bfdd. Duncan-2410 weighed an earlier version for whether each part is worth what it adds, and Phoebe-2419 read Pippa-2571's commit. Octavia-2412 made 5c8ee8fcb8, and Hilda-2580 read it word for word. Ansel-2428 then weighed the branch as it stood before the third review's fixes for whether each part is worth what it adds, and a third run of the house's internal review read what had changed in that version since the second run. Hilda-2580 and Cordelia-2434 made the fixes it led to, in four commits, and Fern-2426 read them word for word. Hilda-2580 made 662c6cd4c6, Fern-2426 read it word for word, and a fourth run of the internal review read it. Hilda-2580 caught the branch up onto main c5306693e0, Ada-1017 ruled on its one resolved conflict, and Fern-2426 read the resolution. Daniel-2612 wrote this description.

Hilda-2580 added 30 commits October 30, 2026 08:31
…tands test's opening

"initiallyOpen at first sight registers open with server-bootstrap provenance"
made the same three steps as the first three lines of "an agent decision to
close is never overridden by initiallyOpen": a fresh registry,
ensureInitialDesiredState with initiallyOpen, and a desired state of 'open'.
It asserted nothing about the provenance its title names; the other tests in
the file show what provenance decides by its consequences. Cover unchanged.
…s assertion

- "a version verdict gets no reconnect stub even with reconnect: true":
  "a version verdict is never retried: one launch, and that child is reaped"
  makes the same connectWithReconnect call with reconnect: true on a
  rejecting server and asserts the same McplProtocolVersionError
  rejection (no stub), then also counts the launches.
- "a modern stdio configuration is refused before anything is spawned":
  "a modern configuration is refused by the legacy engine before anything
  is dialed or spawned" makes the same connect with protocol 'modern' on a
  stdio configuration, expects the same refusal, and checks that the
  fixture never wrote its start log.

Cover unchanged.
… duplicate and a check that can't fail

- "a turn asks membrane for no retries of its own: the framework is the one
  retry layer": removed. "a failed turn is retried by the framework on the
  base schedule, each attempt a fresh stream with no membrane retries"
  asserts retry: false on every stream the framework sends, the failed first
  one included. Cover unchanged.
- The overloaded schedule test no longer sums its own steps array, which
  couldn't fail; the per-retry ranges carry the schedule.
- afterTurn's "held" reads the agent's providerAdmission.cooldownReason from
  the public healthSnapshot() in place of the private
  providerAccelerationCooldowns map. Every hold sets a reason, so the two
  agree. "an acceleration 429 a stream throws after its answer installs no
  hold" and the later-event comparisons read it this way now.
…s, not the steps it takes

- "settling a held write whose landing is unknown, not found, persists the
  queue without its mark, as an edit does" reads the durable queue from its
  documented place, <storePath>/recovery/deferred-writes.json, and checks
  the entry there has no landing mark, in place of counting calls to the
  private persistDeferredWrites.
- "at a tool boundary, a sharded body stored whole is carried as the next
  compile renders it" no longer counts calls to the private storedCopiesOf,
  which is the boundary's own work; that the injection equals the next
  compile's rendering is the claim, and stays.
- "a module's edit or removal by a stored id settles the held write that row
  is a copy of, even while its store can't be searched": removed. "a
  module's edit or removal by a stored id, while the store can't be
  searched, settles the write through the copy it names" has the same setup
  and calls and the same two assertions, and also checks the log line.
  Cover unchanged.
…f the first test

- "the published traces are emitted when the channel's server takes no
  stream" no longer asserts that no chunk was sent: the harness's
  streaming: false registry has no chunk method, and its proxy answers any
  missing method with a no-op that logs nothing, so h.chunks() stays empty
  whatever the framework does. The traces assertion that follows is the
  claim, and stays.
- "a published trace carries no message id when the server returned none":
  removed. The first test runs the same harness and script and compares the
  traces with node:assert/strict's deepEqual, which fails on any messageId
  key, so it already shows both inference:published traces carry none.
  Cover unchanged.
"recipe private routes zero same-round prose for the exact Tilde shape" ran
the same membrane script (a tool round with the Tilde preface, then an empty
answer), the same createFramework({ policy: 'private' }) and the same
trigger as "private keeps the preface in Chronicle while withholding it from
channel routing", and asserted the same deepEqual(routed, []). The kept test
also checks the preface stays in the compiled context. Cover unchanged.
…vers

"a room smaller than the dump's frame shows no message and counts them all"
checked room 10 over the five-message backlog. "at every room, over six
backlogs, ..." runs that backlog at every room from 0, room 10 included.
With the frame longer than the room, its branches allow no line there: a
cut line must fill the room, and whole lines must leave the dump within it.
So it shows no line, and it checks that truncated= counts all five and that
the report is told of all five. Cover unchanged.
…entries and a typing stop's form

- "cleans logical-turn state when an ephemeral startup watchdog fires" and
  "conversation disposal reclaims logical-turn state": removed. Each
  asserted only that an entry was gone from logicalTurnToolCalls, a WeakMap
  keyed by the agent, whose entries live as long as the garbage collector
  lets them; no caller can tell when one leaves. The startup watchdog's
  rejection is pinned in other files.
- "returns empty speech to ephemeral callers for a contained wrapper" keeps
  its claim, the empty speech returned, and drops the same WeakMap checks.
- "successful ephemeral completion stops typing and finalizes outgoing after
  caller disposal" accepts a typing stop for the run's channel in either
  form: a stop with no channel, which covers it, or one naming world:test.
  Which form the framework uses is its own choice.
…-API spy and the tests' own limit wiring

- "the bin load hook goes in through registerHooks where Node has it, and
  through register where it doesn't": removed. It spied on which
  node:module call installs the hook, not on what the hook does. What the
  hook delivers, that a bin's own code never runs and an ES module bin's
  imports load, is pinned by this file's bin tests, and CI's packed-release
  job runs the check on 22.12.0 (register) and 24 (registerHooks), where a
  hook that didn't go in would let the bin start. Preferring registerHooks
  where a Node has both is the script's own choice (its comment gives
  Node 26's deprecation of register as the reason).
- "check() runs the script's npm under the tests' npm limit, short of the
  runner's own timeout" becomes "an npm pack that fails without timing out
  fails the check, pointing to npm's own output": it keeps the only
  assertions of that report (exit code 1 and "npm pack failed (its output is
  above)."), and drops the checks of the tests' own wiring (the limit the
  shimmed npm was started under, and that limit against npm test's timeout).
…ll sites in src

"every state the framework registers goes through ensureStateRegistered"
walked src and counted the lines calling registerState, expecting the one
in module-registry.ts. That checks the code's shape, not what it does. What
a registration written around the helper would break, a failed
registration taken for "already exists" or lost, is what the rest of this
file pins for each state the framework registers today. A registration
site written outside the helper later would meet no test here.
…xt grant admits

McplServerConnection is exported, and on(), establishGrant() and ready()
are public. Where a test read the private bufferedEvents to show what a
transport boundary kept, it now opens the planes under a grant and checks
what the flush admits, as "readied between transports, as a gate readies
every connection, ..." already does. A new helper, admittedIds, opens both
planes and returns the admitted messages' ids in order.

- "keeps the kept bound where boundaries join with no flush between,
  keeping the earliest": 10,000 admitted, m100001 first and m110000 last.
- "starts the next pause's counts afresh after a flush": the second flush
  admits 1,000, the oldest (m30001) gone.
- "keeps notifications out of the kept bound where boundaries join": 10,005
  admitted, 5 of them with no responder.
- "a control-plane flush keeps what a boundary kept out of the next
  transport's budget": nothing is admitted on the control plane alone, and
  all 10,001 are admitted once the data plane opens.
- "holds the next transport's notifications beside 10,000 kept requests,
  dropping none": 10,005 admitted.
- "frees the budget of the requests a transport boundary drops: ...": only
  the next transport's message is admitted, and none of the dropped
  inference requests.
- "a transport boundary crossed during a flush holds the rest of the
  snapshot to the boundary rule": after the reconnect, the next grant admits
  the push and nothing else.
- The admission-order test no longer calls the private static
  admissionOrder on hand-built records: a removal named by an object whose
  string form throws is sent through the file's kept-message setup, and the
  flush still moves the registration ahead and admits the kept message.
The shared helper named the policy an event matched by diffing the private
eventGate.getStatus() match counts, and two tests read sampling and
rate-limit state from it. The gate emits a gate:decision trace for every
event it weighs, naming the matched policy and whether it triggers, and
the framework passes it to onTrace, where an operator sees it. The helper
now waits for that trace, checks there's one decision and that a policy
matched, and returns the policy's name.

- "a channel's message, a reaction and a voice report there share one
  sampling counter under keyBy channelId": with every: 3, the third event
  in the channel fires, so the triggers read false, false, true.
- "a rate limit keyed by channelId counts a channel's message and reaction
  in one bucket, whatever its policy matches": with one token, the message
  passes and the reaction is denied, so the triggers read true, false.
… ops:alert trace

Eleven tests replaced the private opsAlert to capture what it was called
with. opsAlert emits an ops:alert trace, with the alert's kind, agent,
message and data, on every call, before the webhook's cooldown, and the
framework passes it to onTrace. These tests now listen for that trace:
"routes a maintenance failure through the ops-alert pipeline", the four
tests of one agent's failure staying that agent's, "a failure that lasts
is logged and alerted once, and again once the agent has recovered",
"a failure that lasts is alerted again when its whole text changes, ...",
the two readiness-refusal tests, and the two tests of a pass under a
surgery hold. "a maintain begun from an alert a pass raises while it
prepares joins that pass" starts its maintain from such a listener, as its
own comment already described it.

The trace's message is bounded, as a webhook's is, so the test of a long
text alerted again after the cooldown compares the repeat with the change's
own alert rather than with the unbounded text.

- "drains without a new message and refreshes tools before ticking" no
  longer pins the number of tools a tick receives (six, which moves with the
  framework's own meta-tools); it checks the module's tool is among them,
  that every tool the agent may call (getAllTools(), under allowedTools:
  'all') is too, and that both ticks receive the same set.
- "a pass that ends before its agent is ready records what is still
  pending" no longer pins the pass's fixed tick count; the pending state is
  the claim.
…floors

Seven places skipped the shared-inheritance and reader-fork tests when
the installed chronicle lacked JsStore.view or context-manager lacked
ContextManager.derive: compression-after-release, dendrite-derive,
dendrite-fork-view, the reader-fork rig (and the three tune-out-reader
files that took its skip), tune-out-reader-forks-e2e,
tune-out-reader-epoch-shared and turn-inputs-pin.

The floors are chronicle ^0.5.0 and context-manager ^0.24.0, and between
them the floor versions, 0.5.0 and 0.24.0, declare view, both selective
forks (createBranchWithStates, createBranchAtWithStates), derive and
supportsDerivation. Installed at those versions, supportsDerivation
holds for a fresh store, every guard reads supported, and the eight files
run 52 tests with none skipped, the same with the guards forced on.
turn-inputs-pin, whose guard came later, runs its 16 tests at those
versions with none skipped once its guard is gone. A
rig that lacked them would be an unsupported install, where a failure says
more than a skip.

The guards, the skip options, the header lines that described skipping,
and the imports only the guards used go.

Refs anima-research#886
- XML tool mode: a round's persisted text is its roundPreamble alone. The
  fallback to the cumulative preamble served membrane before 0.5.79, which
  sent no roundPreamble; the floor is 0.8.0, whose XML path always sends
  it. The test that pinned the fallback ("legacy membrane without
  roundPreamble keeps the cumulative fallback (pinned)") goes with it, as
  its own comment said it would once the floor passed 0.5.79, and so does
  its mock's switch for leaving roundPreamble out, which only that test
  turned off. The branch's comment now says it is XML tool mode's, and keeps
  why it reads roundPreamble (membrane's XML preamble is cumulative)
  without the incident's history; xml-round-persist's header says the
  framework persists roundPreamble alone, and its stream declares
  supportsInjectedMessages false, as the XML path does. The turn-end
  routing comment and agent-allowed-tools' header, which named an older
  membrane's stream with no roundContent, now name the XML tool path.
- Discord awareness outbox: a version 1 ledger no longer gets a refusal of
  its own. Only an unreleased commit wrote version 1, and any released
  build rewrites such a file as version 2 at its next write; one that
  remains is refused as any version but 2 and 3 is. Its test goes, and
  the README's sentence on it no longer says its error tells what moving
  it gives up.
- ReferenceTestimony.truncatedFields is gone: the parser set it to an empty
  list and nothing ever added to it, and the type isn't exported from the
  package. The test lines that checked it was empty go with it.
- deriveAgent's doc names the check that decides shared inheritance,
  ContextManager.supportsDerivation, in place of "branch-bound store
  handles", which no longer said everything derive needs.
…private rates

The expected stamps embedded the estimator's own rates (2.9 and 2.3
characters per token), private constants of the module, and some checks
compared object identity where the claim is about content.

- "stamps the residual of output tokens over visible blocks on the signed
  block": with nothing visible beside it, the signed block carries every
  output token; with text beside it, the text is taken out, and the rest of
  the output tokens are the residual (a thousand fewer output tokens stamp a
  thousand fewer). The text block carries no stamp, and the input isn't
  mutated.
- "splits the residual across several carriers by signature length": with
  nothing visible, 4,000 splits 3,000 and 1,000 by the signatures' 3:1; with
  a tool_use between them, the tool_use takes no share, its estimate comes
  out first, and the carriers still split 3:1.
- "leaves unsigned thinking, already-stamped blocks and non-positive
  residuals alone" and "drops the stamp from each top-level block, ..."
  compare by content, not by object identity; the blocks handed in keep
  their stamps.
- "stamps each round from that call's own output tokens (membrane usage is
  cumulative)" computes each round's expected stamp through
  stampThinkingTokenEstimates from that call's own output tokens, and checks
  the cumulative count would have stamped more.
…est mock declares true

membrane's yielding stream, the only one the framework continues across
tool rounds, declares on every tool round whether it carries messages
injected with the round's results: true on its native path, false on its
XML prefill path (membrane 0.8.0, the floor). The framework still gave an
undeclared stream its own mix: it drained and injected at the tool
boundary (!== false), didn't mark the injected messages read (=== true),
and counted the round as presenting them (?? true). No stream the
framework continues across tool rounds takes that path. For a boundary
with a stream, all three now read the declaration as === true: a stream
that doesn't say is taken as one that doesn't carry, and what is queued
for the agent waits for the turn's end. The README's paragraph on unread
messages says so, and the comments and docs that described a stream that
doesn't say (the AgentState field, the consumed watermark, isUnread, the
deferred-writes window, releasedWindow and the module context's note on
writes to a run or fork) now don't.

The direct API (Agent.runInference) reaches the framework's tool boundary
with no stream and no declaration. That boundary drains as before
(=== true || !stream): no stream carries what it stores, and the
re-inference that follows compiles from the store. A round whose result
ends the turn runs no re-inference, so without it the clock row due after
the round's results, and a write queued while the host was quiesced,
wouldn't be stored there; clock-rows and deferred-window-only each have a
test of that route, which fails with the condition taken out.

The test mock was the one stream that didn't say: MockMembrane and
MockYieldingStream left the declaration out unless a test passed it, so
most of the suite's mid-turn injection tests ran the undeclared mix rather
than the native path production takes. The mock now declares true by
default, as membrane's native path does; a test that passes false keeps
the XML path, and 'undeclared' leaves the field out, which
deferred-window-only's test of a stream that doesn't say uses, and so
does prose-drafts' test that such a stream's held reply gets no
mid-turn notice and is named in the turn's receipt. The
tool rounds tests build by hand now declare the path they model: true
where the round carries roundContent, as membrane's native path does
(end-turn-usage, framework, same-round-think-policy's paused stream and
tool-wrapper-prose-guard-integration), and false on the XML path
(skip-reply-wake-xml-mode). same-round-think-policy's stream with neither
roundContent nor roundPreamble, a shape no membrane round takes, stays
undeclared.
exhausted-turn-rearm's hand-built tool round declares true for the same
reason: its test pins the native path's re-arm. On membrane's XML path
(supportsInjectedMessages: false), a message said while a turn runs is
stored when the turn ends, after its exhaustion decision, so an exhausted
turn isn't sent again; the message's own inference request then runs a
turn that carries it.

Changed or removed with the reading, since they asserted the undeclared
mix itself:
- deferred-window-only: "a stream that doesn't say whether it carries
  injections gets them, and they stay unread" becomes "... is taken as one
  that can't: what is queued waits for the turn to end"
- module-query-for-agent: "on a stream that doesn't declare whether it
  carries injections, an injected write counts as unread, so the primary
  gets a copy, as for a notice" goes (its true and false siblings stay)
- route-failure-targeting: "passes on, by name, a notice injected into a
  stream that doesn't declare it carries injected messages" goes (its true
  sibling stays)
…budget's arithmetic or private steps

- "edit refuses a problem in its own input before it looks its path up on
  disk" no longer counts calls to the private parseFilePath: an invalid
  input on a path that doesn't exist gets the same refusal as on one that
  does, and the missing path's own refusal, with a valid input, differs.
- "every entry is set in one record: ..." no longer pins where a long
  failure is cut (491 characters, the bound's arithmetic): the refusal
  keeps its whole lead and ending, the ellipsis names the failure's whole
  1016 characters, and what's kept is a head of the failure.
- The three floor tests ("a call counts occurrences its edits don't need,
  ...", "an edit matched with quotes read as equal, or read without its
  CR, ...", "a crowded edit whose count stopped short of its ask, ...") no
  longer pin the floors 43, 85 and 561, which only the count budget's
  arithmetic gives: each refusal names the edit by a count that's a floor,
  above the 20 shown and below the real count or the ask, and states that
  same count as the floor. The at-the-ask case asks for the floor the first
  call reported.
- "every path is checked before any entry is set: ..." no longer counts the
  private barriers; that neither file is stored, and the error says the call
  stopped before storing, are the claim.
…s arithmetic

Four tests pinned exact counts that only the budget's current allocation
gives (21 free, then 21 more and one: 22, 43, 61, and the 199,997 a
crowded problem names). Each now states the requirement those numbers
illustrated:

- "a call counts occurrences past what its edits need only within its
  budget, and past it a count is a floor": the first edit's exact
  5,000,000 stays; every later count is a floor above the 21 a refusal
  shows; and 200 such edits together count no more than the budget with a
  small allowance an edit, not 200 times the text.
- "a crowded edit whose count the budget stopped short of its ask, or at
  it, ...": the third edit's count is a floor above 21 and at or below its
  ask; the at-the-ask edge asks for exactly the floor the first plan found.
- "one count budget raised for a text longer than it is raised once for
  each length, ...": the second text's count is a floor below its length,
  since the budget isn't raised again.
- "crowded edits whose counts stopped short, where the crowded check names
  none, ...": one crowded problem names every edit without a count
  problem, the two name all 200,000 once, and only a few edits get count
  problems; the line-feed search bound stays.
…ept counter would fail them

- "keyBy partitions counters — busy channel does not affect quiet one":
  A's third message fired and reset a shared counter before B's first, so
  B's assertion held either way. Now two of A's come first, then B's,
  which would be the third on one shared counter, and then A's third.
- "rate_limit keyBy changed: old keyed buckets do NOT carry over" and
  "passive_sample keyBy changed: per-key counters cleared": the event after
  the reload had a different key from the old state's, so a kept bucket
  or counter would have passed too. The old rule now keys by userId and
  the new one by senderId, and the event after the reload carries the old
  key's value, K, as its new key, so only a cleared state passes. The old
  rule can't key by channelId for this: that keys by the channel the gate
  routes an event to, not the field's value.
- "trace event lists policies whose state was cleared": no policy removed
  may read as an absent or an empty removedPolicies.
…and look for any file moved aside

- "the host's starting rules are read rule by rule: a first start writes
  them as given ...": the written gate.json parses to the rules given. Its
  exact serialization (two-space indent, a trailing newline) is the code's
  choice.
- "a gate.json saved with a BOM reads as the same file without one: ...":
  after the wake_add_rule, the directory holds the same entries as before,
  so the file was written in place. Looking only for names under the
  'gate.json.unreadable-' prefix would miss a file moved under another
  name.
…hot, and drop a stop() half no source states

- The shared continueTurn helper checks that a capacity hold holds the turn
  through healthSnapshot()'s providerAdmission.cooldownReason in place of
  the private cooldown map. Releasing the hold at its deadline still goes
  through the framework's own release, as the hold's timer would.
- "a settle closes the open logical turn only when it is the settling
  stream's own, and stop() clears open turns" keeps its first half, under
  the title "a settle closes the open logical turn only when it is the
  settling stream's own": the CHANGELOG says a logical turn stays open from
  the start that opens it until the turn settles, so a settle from a stream
  a newer one replaced leaves the newer turn open, and the turn's own settle
  closes it. Its stop() half goes: no source a caller reads states that
  stop() clears open logical turns, or that it keeps the live routing state.
…hows the agent

- "is told by its own fields, and a resource with a uri of its own stays a
  reference" read every case through classifyBlock's record, which isn't
  exported and is the classifier's own. Each case is now read as the push
  lane shows it to the agent: the embedded text as text, the embedded
  image as an image, a resource with a uri of its own as a reference stub
  that doesn't carry the uri, a record with no string uri as the "no link"
  line, a disposition as withheld, and a media type kept as sent whatever
  its case.
- The empty-link tests drop their classifyBlock records; what each lane
  shows is asserted beside them.
- The marker test no longer compares its own constant with a copy of the
  same literal.
…assifier's record

- "withholds inline data beside a uri that is not a string ..." drops its
  check of classifyBlock's record (not exported); the history text it
  shows, a placeholder with no uri, is asserted beside it.
- "governs media given a uri that is not a string, ..." shows the bare
  media block through the push lane, which reads it as it does a block with
  fields that aren't strings: the "no usable data or link" line. (The
  history text writes a bare block with no data and no link as its JSON,
  which carries nothing to withhold.)
The shared heldRestartMeets helper checked that the restart was held by
the size of the private cooldown map. It now reads healthSnapshot(), where
the held agent's providerAdmission carries the hold's reason: the one
agent, assistant, is held, and still held after the event arrives.
Releasing the hold at its deadline still goes through the framework's own
release, as the hold's timer would. Three tests use the helper: a held
restart meeting a message, a channel message, and an ambient one.
…uard's audit record

- "settles the tool-result batch and the open tool round a fork still
  holds when stop() ends it" no longer spies on the guard's abandon. The
  requirement is that a batch a stopped fork held settles conservatively,
  withheld rather than admitted; the guard's audit record, which
  docs/tool-result-guard.md documents for operators, has the fork's batch
  withheld, with the reason 'aborted' that stop() gives.
- "tells the primary of each failure notice a live fork never read when
  stop() ends the forks, ..." compares the forks' disposal traces as a set:
  no requirement fixes the order in which stop() ends them.
- "resumes past a run that ended while the host was quiesced: ..." reads
  the quiesced flag through the public getHostModeStatus().
…they show

- "retract('all') takes the refs of every batch not retired and every
  reaction the ledger holds, ..." compares the due removals as a set: their
  order follows the ledger's entry creation, which nothing states.
- "a bounded text never splits a surrogate pair at either cut" checks that
  requirement directly, with no lone surrogate at either cut, a head and a
  tail of the text, and the omitted count equal to what lies between, in
  place of the exact head width the cut's arithmetic gives.
- "one drain pass per server: ..." no longer checks that the second call
  returns the same promise, which is how a join is done: nothing more
  leaves while the reply is out, and the first pass delivers both, so the
  request made owed joined it.
…and read alert bounds on the trace

- "an over-budget phrase quoted inside a classified provider error does not
  kick the drain" and "a reason that is not a string is described whole,
  ...": the drain's exact tick count (8) is the drain's own. The claim is
  that bounding a reason for presentation never changes the decision, so a
  long reason's ticks equal a short one's with the same phrase, and that
  one kicks the drain at all.
- The two alert-data tests read the bound and projection checks from the
  ops:alert trace alone. Their 'failures.log' sink was the argument the
  harness captured before logFailure applies its own bound, so it showed
  where in the code the bound happens; "logFailure bounds every field it
  writes" pins the written record. The small records' shapes stay, and
  the one title that named the failures.log record names the trace alone.
…ccessor

"module utilities are callable tools out of the tool list, and utils is
gone" popped the array the private getAllUtilities returned and checked the
framework's list was unchanged. No caller holds that array, so its copy
semantics are the accessor's own. The utilities being callable and out of
the tool list are the claim, and stay.
"the interrupt stops a spinning script for good" asserted the interrupt
was asked more than three times. An engine that reported 'interrupted'
without asking its interrupt would fail that, and it fails the file's two
checks === 101 as well ("the slot was asked again after it said stop" and
"interrupt() was asked no more once it said stop"), whose scripts only an
interrupt that answers at its 101st asking stops. The interrupted outcome
is this test's claim.
Cordelia-2434 and others added 25 commits October 30, 2026 08:32
…s by a restart, and counts what the service took

- "re-review anima-research#1: a 404 from /delete ... drops the removals instead of
  retrying forever": the check after the last pass that nothing was left
  pending read the module's saved state through a cast; it now stops the
  module, starts a new one on the saved state, syncs, and checks no
  /delete was called. The check that a later removal is dropped as it
  comes stays as it was: that is the stated requirement through the
  module's state, and a restart at that point would hand the rest of the
  test to a module that hasn't met the 404.
- "a removal before this store's index is made ...": the pass's whole
  call sequence, stats then upsert, is no doc's; it now checks that
  /stats was asked and no /delete was called.
- "G2#1 pending changes are restored and persisted when start() runs
  before bind()": the check that a new edit was persisted read the
  saved state through a cast. It now stops the module, starts a new one
  on the saved state, binds it and syncs, and checks the index holds
  the edited text. The edit is of a third message, o, a second after p:
  q is the newest message, inside the overlap the next pass re-scans, so
  its text would reach the index whether or not the edit was kept, and p
  is already in the state the test starts from, so a pending set never
  saved again would still name it.
- "G2#5 queued edits spend the catch-up budget": the changelog makes
  the budget the most items one pass writes, as the service reports
  them, each queued edit counted as one. The test read the module's own
  count of what it pushed, which can't catch a pass that writes more
  than its budget while counting less. It now counts the items the
  service holds with edited text after the budgeted pass, at most 100.
… its stated parts

CHANGELOG says a conversation fork's or an ephemeral run's sleep is
refused, with a result saying that nothing was set, and why: the gate's
sleep quiets the whole agent. "a subagent's sleep is refused, and the
gate stays awake" matched the refusal's whole text; it now checks the
parts the changelog states: one refusal of sleep, saying nothing was
set and that a sleep quiets the whole agent. The check that the gate's
sleep state stays null keeps: it is the refusal's "nothing was set"
through the gate's own encoding.
…'s note record

"a send into a channel the agent closed says so, what still reaches
the agent, and how to open it, until they turn the note off" checked,
after agent_settings turned closed_channel_note off, the stub state's
private closedChannelNotes record, a shape no doc names. The checks
around it show the setting through agent_settings and the send's
answer, and another test shows it kept across a real restart through
agent_settings, so the record check goes, and the test no longer takes
the harness's state reader.
…hangelog states

- The coordinator's refusals are matched by what the 0.22.3 entry on them
  states, in place of their whole sentences: each says what didn't
  happen and what to do instead; tune_out on a channel already tuned out
  gives its settings, how to change them, and its end time where it has
  one; a cancel of a channel that isn't tuned out names the connected
  channels that are, how a tune-out ends by itself, and whose window its
  backlog goes to, "you" to the resident and "the resident" to the
  subconscious; a fork-bound channel says why tune_out won't divert it,
  and that calling again won't change that until its conversation ends.
  A refused cancel with no connected channel tuned out names no channel.
- The refusals of an id two servers' channels share, and of the turn's
  channel once its server withdrew it, are matched by what the 0.27.0
  entry on shared channel ids states: a shared name is refused with each
  candidate named by its server-qualified id, and that form is the one
  to call again with; within a turn, once that channel's server no
  longer registers it, the call is refused. Each also says what didn't
  happen, and the withdrawn one until when. The subconscious's wake
  reasons are checked for naming a:general, which the same entry states.
- The alreadyTunedOut marker leaves the two comparisons that held it. It
  is the coordinator's signal to the framework's dispatch, whose stated
  outcome, that tune_out called again for a channel already tuned out
  tries the open again where its transport may not be open (0.24.0's
  entry), tune-out-transport pins through the tool itself.
- cancel_tuneout inside the subconscious's turn runs the coordinator's
  own cancel, where it ran a stub put in its place, and the test reads
  which tune-out ended and which stands from the registry's
  getTuneOutState.
- The file header no longer says the refusal is marked for the dispatch,
  which the file no longer checks.

Checked by mutation: with the marker gone from enter's refusal,
tune-out-transport's "... tries the open again, and opens it" fails
waiting for the open, which the dispatch never tries;
with one tuned-out channel left out of the cancel's list, the end time
dropped, the turn's id read as the first-registered server's channel,
the cancel wake's reason naming the bare id, the withdrawn refusal
reworded, the shared-id refusal naming no candidates, or the backlog
always going to "you", the rewritten checks fail.
… the close trace and refused connects

- "a hold releases only its own claim": the checks of each hold's
  previous promise, and the setImmediate wait for the second to run, go.
  How a hold chains to the one before is the code's own shape. What the
  changelog states stays: after the first release, a connect is refused
  while the second disconnect's hold is held, then admitted.
- "a modern connect is refused while a legacy connect ... is still
  handshaking": the loadHostStates wrapper and its "the refused connect
  read no trees" check go. That is the order of the connect's steps, not
  something a caller meets. The refusal and "the modern server was never
  launched" stay.
- "modern, without reconnect: a server lost on its own holds its id until
  its child is reaped, then leaves": the reads of the private server map
  go. The refused connect shows the id is held while the reap runs; the
  test then waits for the server's tools to leave the list, checks its
  child had exited and that nothing launched beside it, and shows the id
  is free by connecting the server again.
- "stop() closes every connection, keeps the ones it cannot reap
  registered ...": in place of the private server map and registry, the
  test reads mcpl:server-closed. The failed stop confirms neither close,
  and the retried stop, which the changelog says retries the reap, traces
  one close for each server.
- "a server connected while a refresh waits keeps its own tool prefix
  ...": the count of tools/list calls, and "the dropped refresh started
  no other", go; one more refresh is one more listing, which no caller is
  promised against. The gate is lifted by restoring the real listTools.
- "a refresh in flight when a server restarts under the same id ...": the
  check that the private map holds a new connection goes; the new
  prefix's tools show the restart.
- "a disconnect still under way when a connect's tool listing ends fails
  the connect": that m is still registered and connected is read from
  listMcplServers() in place of the private map and the connection.

The ignore-sigterm child's two-second exit grace in the three tests
that use it is left as it is: the grace also bounds the wait after
SIGKILL, and setStdioExitGraceForTests is only for a child that can exit
by no means.

Checked by mutation: with a hold's release freeing whichever claim the
id holds, "a hold releases only its own claim" fails at the refused
connect; with a lost server leaving at once, the lost-server test fails
at its refused connect, and with it leaving before its reap is
confirmed, at "its child had exited"; with a failed close dropping the
server from the map, the stop() test fails at its retry's traced
closes, which lack m.
…the self-wake it left pending

"an ephemeral run's sleep and wake are refused, and its end tells the
gate" spied on the gate's agentGone to see that the run's end reached
it, though the run had set nothing for it to end. The changelog states
the outcome: a self-wake a conversation fork or an ephemeral run left
pending ends at its end. The test now arms a self-wake for the run once
its turn is under way, at the result of its refused sleep, as its own
skip_reply would arm one mid-turn (a turn's start cancels a pending
self-wake, so one armed before the run would be gone before its end).
It checks the arm took, and after the run that none is left for it,
read as the file's agentGone test reads it. The gone-names helper's
import goes.

Checked by mutation: with the run's disposal not telling the gate it is
gone, the test fails at its check that no self-wake is left for the run.
An earlier form, which armed the self-wake before the run, passed under
the same mutant, since the run's turn start had already cancelled it.
…ld, and waits on events

Removed:
- "the drafts tool's example id has the form newId issues": a check of a
  private static's description against the id alphabet only newId
  gives. Agents resend the ids the tools hand them, so the example's
  shape is cosmetic.
- The store's record count after branch moves, a figure only the
  journal's layout gives; each draft's state after each move stays.
- Checks of internals only the code gives: an attempt's via label, the
  inherited-risk chain's ids, source and reason wording (each re-bounce
  test's notice or riskText still says which words may already have
  been posted, and where), and the attempt list with its
  confirmedDuplicate flag.

Read another way:
- A held-draft notice is found by its "[drafts]" opening, which the
  changelog names among the openings the framework writes into an
  agent's context, in place of a private metadata kind; the XML-mode
  test checks that no text opens with it, in place of a kind count.
- "through the real connection, an error response is unconfirmed":
  the resend's tool result carries the connector's words and says a
  resend needs confirmDuplicate, in place of reads of the attempt
  record. The draft's state check stays.
- "hybrid mode: after a send, a drafts round's envelopes still move the
  router": in place of a spy on the private continuation check, the
  test waits for the model to be asked again, the continuation turn
  that " !" asks for (docs/explicit-prose-routing.md).

Duplicate checks dropped, each where a later check in the same test
fails without what it pinned (see the mutation note): a draft's state
read from the private store after a failed, unknown or unrecorded
outcome, or after a dismissal, where the next resend's, {{unsent}}'s or
dismissal's words, or what is published, show it.

Time:
- "stop() during the wait after a retry's start failed names the
  speech" took its error policy's 2 s delay twice, in real time. The
  harness takes a delay per retry: 10 ms for the first, and a long one
  for the second, which the stop ends. The test waits for the second
  wait's inference:waiting trace, which the trace's doc says is said as
  the wait begins, in place of reading the private waker set.
- The two heartbeat bounce tests that expect a wake wait for the second
  model call, in place of a fixed 200 ms. The absence check in the
  heartbeat half keeps its 200 ms.

Checked by mutation, each against the tests whose state checks went:
with an unknown outcome not counted as uncertain, the failed-and-
uncertain resend test, the later-failure test and both possibly-posted
{{unsent}} tests fail; with an attempt that never recorded an outcome
not counted, the after-restart test and the throwing-publish test
fail; with only the latest attempt counted, the later-failure test
fails; with a failed outcome counted as uncertain, the failed-and-
uncertain test fails at its first resend's words; with inherited risk
not making a draft unconfirmed, the re-bouncing test fails when a
draft goes out without confirmation; with a dismissal recording
nothing, the batch test never settles and fails by its timeout; with no
continuation asked for, the continuation test fails waiting for the
model's second call; with an unknown outcome's reason left out of the
resend's words, the error-response test fails at the connector's words.
…te's upcoming wakes, and drops restated premises

- "two servers' channels with one id each get a fork of their own": the
  other server's fork is read from its binding, checked to differ from
  srv's and to be registered, and looked up by that name, in place of a
  name built with generation 2, which pins how the generation counter is
  keyed (a private field's doc says by bare id). The changelog states
  that each server's channel gets its own fork.
- "a disposed conversation agent's name is refused for a new agent"
  asks for the disposed fork's name through the public
  createEphemeralAgent, in place of the private createConversationAgent,
  which only the router's spawn calls. A host can pass any name there,
  and agent names key Chronicle namespaces and generation identities, so
  a fork's name stays refused after the fork is disposed, twice over,
  and no agent takes it. The refusal's whole text is checked. The
  comment above the test goes.
- "a fork's end at the idle sweep tells the gate" spied on the gate's
  agentGone, though the fork had left no self-wake for it to end. The
  changelog states the outcome: a skip_reply self-wake a fork left
  pending ends when the fork does. The test arms the fork's self-wake as
  its closure turn's stream starts (a turn's start cancels a pending
  self-wake, so one armed earlier would be gone already), checks the
  gate's upcomingWakes lists it then, and lists nothing for the fork once
  it is disposed.
- "a fork's end at stop() tells the gate, and leaves the primary's
  sleep": in place of the spy, a self-wake armed for the fork before
  stop() is gone from upcomingWakes after it. The primary's sleep check
  stays.
- The stored-injection premise read through storeInjections' return
  goes; the next check reads the same premise from the trunk's messages.
- Three deferred-write tests drop their check of the private addMessage's
  '' return; each test's later checks of where the write was stored show
  it was held to the turn's end.

Checked by mutation: with storeInjections storing no row, the seed test
fails at its trunk-message premise; with a write for an agent whose
turn is alive stored at once, the three deferred-write tests fail at
where the write was stored, or at the queue the quiesced one keeps;
with a disposed agent's release not telling the gate, the idle-sweep
test fails at its upcoming-wakes check (the stop() test passes, since
the gate's dispose clears every self-wake), and with that dispose's
clearing taken out too, the stop() test fails at its own. With
createEphemeralAgent checking only live agents' names, or with a fork's
name never reserved, the disposed-fork test's first createEphemeralAgent
isn't refused.
…ough its call's context, and names ids by value

- The refusals of another agent's script and of another host's id are
  checked for naming the very id they were given, in place of patterns
  of the id's shape, which only the code that mints ids gives; the check
  of a run's id against that shape goes with them.
- "stop watches its script with the caller's watch ..." is removed. It
  read the private script record's watch; "a stop watches the script
  with its own call's watch: a call in flight sees it as the one
  watching" checks the same through ScriptCallContext.watching, as the
  host's dispatch is handed it.
- Three abandoned-turn tests read whether a script is still watched
  through the same context, from the script's held call (the file's
  setup now keeps each slow call's context), in place of the private
  record: a returned script's call sees no one watching once its turn is
  abandoned, and one of a live turn's still sees that turn's watch.
- Four 50 ms sleeps after a wait() go. A wait takes its watch before its
  first await, so they waited for nothing; where a test then reads a
  call's context, it waits for that call to arrive.

Checked by mutation: with stop() not taking the caller's watch, "a stop
watches the script with its own call's watch" fails; with an abandoned
turn keeping a returned script's watch, each of the three
abandoned-turn tests fails at its watching check.
…it grace, and drops reads of Node's own fields

- Five tests whose child's kill() is a no-op waited out the stdio exit
  grace three times, about 6 s each. setStdioExitGraceForTests is for
  exactly such a child, one that can exit by no means, where a short
  grace races no exit and only brings "could not be reaped" sooner: they
  now set 50 ms, and the two that match the verdict's text read it from
  the grace they set. A sixth, a host close() during the escalation,
  took about 4 s. It sets 50 ms too, and calls close() as soon as the
  escalation has sent its SIGTERM, from the child's stubbed kill(), in
  place of polling for the pipe failure and then calling it. The close
  then joins the escalation's attempt whatever the load: one SIGTERM
  and one SIGKILL go out, the close rejects as "could not be reaped",
  and no 'unreaped' comes, which the test checks as soon as the close's
  rejection is awaited, in place of 50 ms later: the escalation's own
  failure handler has run by then, since with its closed-guard removed
  the check fails with no further wait.
- Three checks that a child's exitCode is null go. Node sets exitCode,
  null for a child a signal ended (the close info's SIGKILL already says
  so) and for one still running a turn after a stderr error, so no
  change to the transport could fail them. The checks that kill() was
  never called stay.
- "the framework's close handler withdraws the closed transport's
  channel confirmations first ..." checks that the withdrawal comes
  first, which is the title's claim, in place of the order of all six
  steps.
- "a legacy connection held as unreaped before its first ready() ..."
  drops its checks that the stderr line is still in the private buffer
  and that the data plane flag is unset; that nothing the server sent
  was delivered is what a caller sees, and stays.

Checked by mutation: with the close handler removing the feature sets
before it withdraws the channel confirmations, the close-handler test
fails at its first step. With the escalation saying 'unreaped' though
the host is closing, the host-close test fails at its no-'unreaped'
check.
…s checks of the file system and of chokidar

- "an atomic save (unlink, then write) comes out as one modified entry"
  and "a new file written twice comes out as one created entry" are
  removed, with their comment, and the readyWithPolling helper only they
  used. Their own comment said they pass with any merge rule removed, and
  a run bore it out: with each chokidar event flushed alone, with no
  merge and no debounce, both passed 10 of 10 while the atomic-save rule
  test failed 10 of 10, and with awaitWriteFinish removed they passed 10
  of 10 too. They pin chokidar's polling, not the watcher; the rule tests
  pin the merges.
- Three root-liveness tests waited a fixed four poll intervals for polls
  that would show a second re-attach, or a re-attach of a gone read-only
  root; under load that can hold no poll, and the test then passes
  without reaching its case. They now count the root's polls (a new
  rootPolls helper over checkRootLiveness, as the link-count test already
  did inline) and wait for three after the point the test reached.
- "read-only mount does not create the watched path" checks at once
  after start(): a mount makes its path in attach, which start() runs
  synchronously, and its first root poll is 2 s away.
- "holds its root open while it watches ...": the check that the root
  made again took another inode number goes. Once the test has seen the
  watcher hold the root, a directory held open keeps its number on any
  local file system, so that checks the file system, not the watcher.
  The title now says what the test checks: the root held, and a
  re-attach to the root made again in its place.
- The link-count test no longer checks that the watcher holds no such
  root, which is how the code gets there rather than something a caller
  meets; it is retitled to its claim, that its polls leave such a root
  be.

Checked by mutation for the three poll-count waits, each against its
test. With every root poll re-attaching, the one-cycle test counts 4
re-attaches for its 1. With a read-only mount re-attaching when its
root is gone, it watches the missing root again (5 readies for 1). With
the held root's identity no longer matching once the traffic's modified
change is flushed, a poll inside the traffic test's wait re-attaches,
and the test fails at its count of none.
…running pass, and reads alerts from the trace

- Four tests waited a fixed 50 ms after a move began, then checked that
  it had neither settled nor moved the store while a maintenance tick
  ran: "an undo's re-initialization waits for the tick before any
  manager re-initializes", "a branch switch waits for the tick before it
  moves the store", and the two whose rollback runs through
  duringMaintenanceWait ("a fork spawned while a rollback waits for
  running maintenance ..." and "while a rollback waits for running
  maintenance, a fork's channel message is deferred ..."). Under load a
  move may not reach its wait within 50 ms, and the checks then pass
  whether or not it would wait. A new watchRunningPass helper watches
  the running maintenance pass for a waiter, as the drain-kick test's
  watchedPromise does for its kick, and each test waits until the move
  waits on the pass before it checks. The changelog states both waits:
  an undo's or redo's re-initialization waits first for maintenance
  already running, and a rollback, suppression or switchBranch waits for
  a running pass before it switches or forks the branch.
- "each move withdraws the parked call, and its re-initialization
  releases the store" (both variants) read its alerts by replacing the
  private opsAlert. It now reads them from the ops:alert trace, which
  every ops alert reaches, as the changelog states.

Checked by mutation: with the running pass left out of what a move
waits for, the four tests fail at their new waits (the undo's
re-initialization, the switch, and the rollback in each
duringMaintenanceWait test never waits on the pass), and the two
store-held-long alert tests fail too. With a withdrawn maintenance call
reported as a maintenance failure, both parked-call variants fail at
their alert check, read from the trace.
… into the live one, and compares or gates on what a caller sees

- "leaves another server's DM as it was ...": discord's DM rows are
  taken once discord's DM is registered and compared after slack's push,
  in place of the literal placeholder label, which only the code gives.
  The changelog states the requirement: a push leaves the entry of
  another server's channel with that id as it was.
- "a drafts resend from a script ...": the wait for the follow-up read
  the private registry's default channel. It now waits for the
  follow-up's message:added trace (source mcpl:channel-incoming), which
  the framework emits as it places the message, in the same synchronous
  step in which it makes that channel the default the old wait read. The
  wait stays: without it, the variant whose follow-up must not re-pin the
  turn would pass whether or not the follow-up came.
- "a script started in one turn and waited on in a later turn ..." no
  longer checks that the two turns' private ids differ. Were they the
  same, the first script's call would be the later turn's and would post
  through slack, which the test's check of that call already fails.
- "a background script's channel_publish naming no channel ..." no
  longer reads the private default channel; "default while shared" going
  out through slack is that default's consequence, which the changelog
  states: a publish naming no channel posts to the latest message's
  channel, on the server it came from.
- The two tests of a gate wake's route on a framework built by hand
  (Object.create with private fields set) are removed, and their claims
  move into the live conversation-routing test, which already checked
  each fork takes only its own server's route: discord's fork is now also
  checked not to take slack's route while it is the only fork bound, and
  the trunk not to take either server's route once both forks are. The
  changelog states that an addressed wake's route applies only to the
  fork homed on that server's channel.

Checked by mutation: with a push's registration relabelling another
server's placeholder entry of that id, the DM test fails at its
before-and-after rows ("DM: mallory" for the id). With a script's call
taken as the watching turn's whichever turn started it, the drafts
variant whose follow-up must not re-pin fails at its reply (through
slack), so the new wait lets the follow-up land before the turn goes
on. With every turn given one id, the later-turn script test fails at
its first script's publish (through slack). With the default recorded
on discord whatever server set it, the background script test fails at
"default while shared" (through discord). With a fork taking any
server's route on its home's id, the live test fails at the check made
while discord's fork is the only one bound; with an agent that is no
fork taking a route a fork owns, it fails at the trunk's check.
…nd open state through getDescriptor and isChannelOpen

Eleven tests read whether a channel is registered, or whether it is open,
from the registry's private channel map, through the file's lookup
helper. They now read the same through getDescriptor and isChannelOpen,
which the registry offers its callers (the changelog names both among
the calls that take a ChannelRef). The helper stays for setup: the
tests that force a channel closed or open by hand still use it, and
the tests that no longer do drop it from what they take.

- "ensureChannelRegistered keeps a DM closed ...": getDescriptor on
  discord's ref shows the DM registered on that server, in place of the
  entry's serverId.
- "ensureChannelRegistered is idempotent and does not reopen a closed
  channel" no longer checks that the second call kept the same entry
  object, which is how the registry is idempotent, not something a
  caller meets; it is retitled to what it checks, that the second call
  leaves the closed channel closed, as ensureChannelRegistered's doc
  states (a push never changes lifecycle state).
- "plain speech into a channel its server declared inbound-only ...":
  both reads of the open state are swapped, including the one after the
  refusal, which also catches a channel marked open without an open
  being sent, a case the check that no open was sent can't see.

Checked by mutation: with isChannelOpen answering true for any
registered channel, each swapped read that expects a channel closed
fails at it, so each names the registered channel's server and id (a
read naming another server's ref would answer false and pass): the DM,
the re-ensured channel, the agent-closed channel, gc-b, the DM that
registers closed, and the inbound-only precondition.
…elog states, and drops private reads its public checks repeat

- Three tests pinned publishRouteFor's whole sentence, the line a bounce
  or a resend's refusal adds for a channel shown by its id and server.
  The changelog states its parts: it gives the channelId channel_publish
  reaches the channel by, as JSON, or says no channelId reaches it alone.
  A new offered helper reads the channelIds a route gives, and the tests
  check those (the qualified name first where it picks the channel, the
  id where only that does) or, where none is given, that the route says
  no channelId reaches the channel alone and names its id and server.
  The test that posted with the offered channelId now takes it from the
  helper.
- "a name one server's two channels answer to is refused ...": the >>
  target's and the resend destination's refusals are checked for being
  refusals that name both candidates, which is what the changelog states
  (a name two channels answer to is refused as a shared id is, naming
  each candidate so it can be told apart), not for their whole wording.
  The refusal that says the destination named no server keeps its whole
  text: no source states its parts.
- "a DM is listed by its label only where ...": the <@42> refusal is
  checked as a refusal with both DMs as candidates, which is what the
  changelog states of a mention one person's DMs on two servers answer
  to.
- "removing a server stops its channel's typing ..." no longer reads the
  private message and thread fields after b's removal: incomingFor giving
  no message, the check before it, fails if the message stays.
- "a default channel whose server disconnected ..." reads the default
  through getDefaultPublishChannel, in place of the private field.
- "a fork's turn whose wake names no channel still has its home ..."
  compares incomingFor's channel and message, where its deepEqual also
  pinned a threadId key holding undefined.
- "a wake naming a shared id without its server ..." no longer reads the
  turn's private trigger channel and locus: resolveLocus answering null,
  a check the test already makes, shows the turn has neither, since the
  framework gives the registry a turn's locus, else its trigger channel.

Checked by mutation: with publishRouteFor trying the bare id before the
qualified name, the qualified-name-first test fails at the channelIds
its route offers. With removeServer keeping the message of a default
on that server, the typing test (both orders) fails at incomingFor
giving no message. With removeServer dropping that default's ref, the
disconnected-default test fails at getDefaultPublishChannel. With a
wake's unresolved shared id taken as a's channel, the no-fallback test
(both orders) fails at its check that nothing is published.
…pshot, and checks a timed-out stop's error and log line by their stated parts

- "a turn whose retry stop() cancels leaves no agent inferring at the
  event gate ...": the agents inferring are read from healthSnapshot()'s
  gate, the event gate's own diagnostics, which healthSnapshot() gives
  for /healthz and doctor tooling, in place of the private eventGate.
- "a stream that outlasts the budget fails the stop ...":
  - the ShutdownTimeoutError is checked for naming what it waited for,
    stream:assistant, which is what the changelog states of it, not for
    its whole message;
  - the check that the note is still in the private queue goes: that it
    isn't stored ahead of the stream, and is stored once the stream ends,
    are both checked, and the wait for the stream's end now waits for the
    note to be stored rather than for the private queue to empty;
  - the stop's log line is found by its [deferral] stop: opening and
    checked for the parts the changelog states: the agent, that its
    writes stay queued, that its stream may still write, and that a
    memory-only queue loses them if the process exits.
- "a write deferred behind the entry a retry that failed to compile left
  is stored by stop() ...": the private addMessage's '' return no longer
  stands for the deferral; the note not yet being in the agent's context
  does. The read of the private queue after stop() goes: the reopened
  store holding the note shows the final flush stored it.
- "a responder without respondError reaches the handler without one" no
  longer checks what the handler saw of the responder's shape, which no
  server meets, and is retitled to what it checks: a responder with
  respond alone gets the handler's answer.

Checked by mutation: with the event gate's dispose leaving its agents
inferring, the cancelled-retry test fails at healthSnapshot's gate after
stop(). With stop()'s final flush draining a still-streaming agent's
writes too, the outlasting-stream test fails at its check that nothing
landed ahead of the stream. With the final flush doing nothing, the
failed-compile test fails at the reopened store holding the write.
…y their stated parts, and drops an order no doc states

- The changelog states what the [inference-rearm] stderr lines say: when
  each re-send is coming, when it went, and, as "turn not re-armed", why
  none is (re-arms that are spent, a turn that would have to guess a
  channel, among others), and that a message said to the agent starts
  its count again with a line saying so, so a (1 of 3) can follow a
  (2 of 3). A new rearmLines helper finds the lines with that opening
  that hold each part given, and the tests check those parts in place of
  each whole line: the agent, the delay and (n of 3) for a re-send
  coming; (n of 3) without the delay for one that went; "not re-armed"
  with "spent" or "guess a channel"; "count" and "message" for the
  count starting again. The check that no such line came before the
  message was said reads the same parts.
- "a turn whose retries ran out on a server error is sent again ..." no
  longer checks that each inference:exhausted trace comes before its
  re-arm shows as held in healthSnapshot(): no doc states that order.

Checked by mutation: with the line saying a re-send went losing its
(n of 3), the server-error test fails, for each kind of failure, at its
check that the operator is told re-arm 1 went. The file also passes
with the commit that makes the test's tool round declare that it
carries injected messages, as membrane's native path does.
… and checks the surgery refusal by its stated parts

- "a resident whose turn a stated wait holds for its retry is mid-turn
  ...": the hold's end is read from healthSnapshot()'s cooldownUntil, in
  place of the private hold map, and the rollback's agent-busy refusal is
  checked for the parts the changelog states it names: the provider
  hold, its end, its reason, and how to go on sooner (a quiesce with
  abandon: true, or release-provider-wait), not for its whole wording.

Checked by mutation against that test. With the refusal naming an end
one second after the hold's, the end read from healthSnapshot isn't in
it; with the hold's reason left out of the refusal, or
release-provider-wait, the test fails at that part.
…retries, renders and backoff, and drops the host's internal layout

- "a retraction ..." and "a replacement is evaluated by the gate ...":
  the wake queue emptying at the retraction, or at a non-qualifying
  replacement, is the host's own layout. RFC-006 §4.2 states the outcome,
  no inference whose sole pending cause was retracted unread or replaced
  by a non-qualifying occurrence, and leaves how to the host ("Internal
  timer layout is not part of this contract"). The no-model-call checks
  stay; the first test is retitled to what it checks.
- "a replacement while a turn is alive edits the deferred queue ..." no
  longer counts the deferred queue: the next request holding the
  replacement and not the original is the check.
- G9 read the store's receipt record, its key and its born field. It now
  checks what that record is for: an acknowledged acceptance, after a
  crash before the snapshot flush, answers its resend from the receipt
  and delivers nothing again (RFC-006 §3.1: a host returns an admitted
  occurrence's original result without repeating its effects, and this
  must survive host restarts). Retitled.
- "a receipt record whose write failed ..." read the host's private list
  of records to write. It now checks the consequence that list exists
  for: a deferred batch whose receipt write failed is then dropped (its
  authority revoked at the assembly), another subject's receipt is
  written, and the host crashes before the snapshot flush; after the
  restart the dropped batch isn't delivered. A render start or a later
  receipt of the same subject would hide what the test is about, so the
  batch moves on by revocation. Retitled.
- The fixture's reply-deadline test is removed: REPLY_DEADLINE_MS is the
  fixture's own hang guard, not a requirement of the framework. Its one
  fact about the host, that a channels/incoming request whose first
  commit is held admits all its messages once the commit is made, is now
  a test of its own, checked in the request's answer and in the agent's
  context. The REPLY_DEADLINE_MS import goes with it.
- "an assembly for an agent outside the audience ..." no longer counts
  the settled set of the assembly in the audience. A turn is withdrawn
  only when its assembly materialized none of its subjects, so once the
  render is materialized the settled set decides nothing. The declined
  assembly's settled check stays: that settled subject is what withdraws
  a turn the batch alone woke for an agent outside its audience.
- "a batch-only turn that reaches its assembly while another
  resident's ...": the joined assembly's settled and materialized checks
  go. The withdrawn turn and the one model call are their consequence.
- The two "a turn a wake to read ... held by a provider hold ..." tests
  read whether the held trigger kept its read mark. No source a caller
  reads says what a held trigger carries; what the mark would do is
  stated: a wake whose write a request carried that was answered starts
  no turn (InferenceRequest.readsDeferredWrite and readsStoredMessage).
  Each test now marks a request that carried the render or the message as
  answered while the turn is held, and the retry still has to run.
  Retitled.
- "a provider recovery whose released wakes start no turn ...": the
  gate's primaryPending and the recovery map are mechanism; the parked
  auxiliary call being admitted is what the tests read. Where a wake
  beside the gone render's starts the turn, the test now checks that a
  parked auxiliary call stays parked, which is what "Context Manager
  maintenance waits behind the primary lane" says, where it read the
  recovery map. Retitled.
- "an assembly that throws closes the window ..." read the private
  assembling set. It now checks what the changelog states: a render that
  settles after its turn's assembly failed, while the turn is still
  alive, is deferred like any other write during the turn, and is stored
  once the turn ends. Retitled.
- "a write deferred for an agent while its assembly awaits a render
  ...": the private addMessage's '' return no longer stands for the
  deferral; the write not being in the agent's context does. The
  assembling-set check goes.
- "a batch-only turn whose call a provider's capacity limit held ..." no
  longer reads the held record's marks: getActiveTurnTrigger showing the
  retry's trigger without them, and the retry running, are the checks.
- R16b counted the wake queue. It now checks that the failed freeze
  started no model call, and that after the producer's retry a drain
  alone makes the one call, which reads the fallback.
- R17 and R17b checked the backoff's arithmetic: the attempt numbers,
  each delay as 40 x 2^n, a cap at 60 x the base and the list [3, 6, 12,
  24, 48, 96, 180, 180]. Only the coalescer's own option comment states
  them, and the coalescer isn't exported: the framework builds it
  without options, so no host sets the base they are stated against.
  They now check its properties: under a persistent outage no wake's
  delay is shorter than the one before it, and the delays grow; they
  stop growing; and a second outage starts again from the first delay,
  which also shows the failure series was forgotten on success, where
  R17 read the private recovery map. Retitled.
- Two anima-research#157 first-view tests on the coalescer: the snapshot's
  firstViewEnded field, the snapshot receipt's keys, and n1's
  receiptFirstView are the code's layout. Each test keeps the check of
  the consequence: the restored coalescer's next batch isn't a first view,
  and a restored receipt has no first view, which follows from "a doubt
  counts as shown".
- Two receipt-retry tests on the coalescer: the records' batch fields,
  pendingBatchOccurrences and the no-second-write count go. The restore's
  delivery checks stay: the rendered notice isn't delivered again, and
  the rendering one comes back for its fallback.
- vectors 21/23 stalled the renderer 5.3 s of real time to pass the
  host's 5 s render timeout. Mock timers now carry the timeout, the
  renderer answers late when the test releases it, and the test waits for
  the documented late-render trace with discarded: true in place of a
  400 ms sleep.
- vector 41 waited for the reconnect's initialize and then slept 100 ms.
  It waits for the documented mcpl:server-reconnected trace instead,
  which comes once the reconnect's attempt has settled.

Checked by mutation, each mutant against the tests it names. With a
replaced write left in the deferred queue, the replacement test fails at
the next request. With the receipt record never written, G9 fails at the
copy count (2 for 1). With a failed receipt record left on the host's
list, the dropped batch is delivered after the restart. With only a
request's first message handled, the held-commit test fails at its
answer. With a joined assembly taking the render as its own, the
batch-only turn test fails at its one model call (2). With a retry
keeping the read marks, both held-turn tests fail at the retry (1 call
for 2). With any dropped read wake giving up the pending primary turn,
the provider-recovery test fails at the parked auxiliary call. With the
assembly's window never closed, the late-render test fails at the
deferral. With a live turn's token no longer deferring writes, the
deferred-write test fails at its context check. With a retry keeping the
batch marks, the capacity-hold test fails at the retry (1 call for 2).
With a producer retry not re-waking the batch, R16b fails at the retry's
call (0 for 1). With no cap on the delay, R17b finds no repeat; with a
constant delay, R17 finds no backoff; with the failure series never
forgotten, R17's second outage starts at 320 ms for 40. With a record
carrying the last batch it saw, the rendered notice is delivered again
(2); with a record ignoring a rendering batch, the accepted notice
doesn't come back. With freeze declining an agent outside a rendering
batch's audience without settling the subject, the outside-audience test
fails at its settled count (0 for 1).
…ts and private reads its public checks repeat, and waits on routing rather than a delay

- "routes each round's prose live ...": each move waited 25 ms so that
  the round's prose would be routed before the tool returned, and the
  test read that order. The robot module now takes a beforeResult hook,
  and the test's move returns once its round's prose has been routed (or
  after 10 s, so a prose that routes only after the results fails the
  order check rather than hanging). The order is read with no time in it.
- 27 tests set the robot's toolDelayMs to 25, a fixed wait in each move
  so that live routing came before the tool returned. Every one of them
  passed with it at 0, in each of five runs of the file (Linux, load
  about 3), so the setting goes, and with it the module's delay field.
- "a start that continues the turn and throws before it takes the
  turn's routing ..." no longer reads the private open-turn map after
  the failed start: the next start resolving a locus of its own, where
  continuing the turn would have kept the first, is the check.
- "a silent start that throws leaves an open prose turn alone" is
  removed. It builds an open prose turn by hand while a silent start
  runs, a state the framework doesn't enter (a capacity hold holds
  silent wakes with the turn's, and a re-inference's wake batches with
  them), and the test's own comment says so.
- "a silent turn's over-budget boundary leaves no arrival on an open
  prose turn" is removed for the same reason. Its reachable half, a
  silent turn that crosses its budget restarts silent and routes
  nothing, is pinned by turn-continuation, explicit-prose-routing and
  prose-drafts between them.
- "a message deferred in a start that fails before its stream lands
  then ..." no longer reads the deferred queue after the failed start:
  had the message stayed queued, it would have landed after the newer
  one and won, which the routed check reads.
- "a failed start's flush stops at a message it can't store ..." no
  longer reads the deferred queue's order: what the store holds after
  each start, nothing and then both in order, is the check.
- "a send made outside the agent's turn engages none of its channels"
  waits a setImmediate after the open resolves, as the file's other
  out-of-turn and failed-open cases do, in place of 20 ms: the open's
  outcome is handled on the microtask queue.
- The sleep-announcement test loses its middle leg, a silent start's
  routing taking over while a prose turn is open, the state the two
  removed tests built. Its explicit, locus and disabled legs stay.
  Retitled.
- "passes no injection options when nothing arrived mid-turn" checked
  that the tool-result options argument was undefined, which is the
  code's shape. It now checks that no message is injected. Retitled.

Checked by mutation, each mutant against the test it names. With a
start that throws outside its stream leaving its turn open, the next
start keeps the first locus, and the failed-start test fails at it.
With the failed start's flush waiting on any agent's tool cycle, the
deferred message lands after the newer one and wins. With a write the
store refused requeued without itself, the store ends up holding the
second message alone. With any caller turn resolving to the latest
turn, the outside-turn send engages its channel within the one
setImmediate, and the test fails there.
…e walk's place, the pin and a patched set, drops checks of mechanism, and holds the mount on gates rather than sleeps

- "a failed durability barrier is reported, and its intent stays
  pending" no longer counts the fsync calls: the skipped reason naming
  the file fsync is the point the test reaches, and whether a later
  barrier ran is no requirement a caller states.
- "this branch agreeing with disk leaves another branch's pending
  adoption ..." no longer reads the agreement after the other branch's
  write: main resolving its own adoption to B is the check.
- "the ctime trust gate ..." keeps only its untrusted half: no NFS,
  FUSE, SMB2 or 9p filesystem is trusted, since a ctime trusted where it
  isn't reliable lets a fingerprint vouch for a changed file. The exact
  list of trusted local filesystems, the bigint form and the platform
  check go: they follow the code's current table, and leaving a
  filesystem off it costs only a rehash. Retitled.
- "a recheck of an unchanged file records nothing" counts the store's
  workspace/disk-agreement records, where the changelog tells hosts the
  evidence lives, before and after the two syncs, in place of a patched
  private set.
- "the walk takes files by name, whatever order they were written in"
  is removed. Node's readdir already returns these names sorted on
  Linux, as its own comment said, so it can't fail there if the walk
  stops sorting; the next test is the one that does.
- "names that sort apart by UTF-8 byte and by UTF-16 code unit ..."
  checks that each name is taken once across the three syncs, not the
  walk's exact resume order, which no caller-read source states.
  Retitled.
- "a directory sync leaves the mount root's place as it was" reads what
  the next bare sync takes, c.txt after b.txt, in place of the private
  place.
- "an accepted new root starts with no place" reads the first sync's
  take, a.txt and b.txt, in place of the private place, and its last
  place read goes: a walk that resumed after the old place would have
  taken c.txt, which the test refuses.
- "a pass that records only a place binds no root ..." no longer reads
  the private root identity: the replaced root read as it is, with no
  disagreement reported, is the check.
- "a pass that moves only its place makes no store sync" is removed. No
  doc promises that a pass which only moves its place skips the store
  sync, and what it saves is time alone.
- "a scan that outlasts its deadline ..." holds the mount on a gate the
  test releases after its checks of the late scan, or as soon as one of
  them fails, so a failure is reported rather than leaving the mount
  held through teardown. It replaces a 400 ms sleep that could run out
  before the checks under load.
- "batches that come while a scan waits share it ..." holds the mount
  on a gate released once the three batches have called in, in place of
  200 ms: each batch queues its scan before its first await.
- "a materialize between initStore and start keeps its pin ...", "a
  materialize whose push is refused on a branch change leaves the pin
  ..." and "a first materialize refused on a branch change leaves an
  unpinned mount unpinned ..." read the pin and watermark from status
  (lastMaterializedBranch, lastMaterializedSeq), in place of the
  mount's private fields.
- "takes up only what the evidence says disk owes ..." no longer
  records which paths each push was handed: what the forced and bare
  materializes report, and what disk holds, are the checks. The helper
  goes with it.

Checked by mutation, each mutant against the test it names. With
another branch's pending adoption settled before this branch's write,
main reads a workspace draft rather than synced, and the test fails
at main's own resolution. With every pass re-recording its evidence, the
disk-agreement record count moves from 6 to 8. With the walk taking
entries in the order readdir lists them, the three syncs take b.txt and
U+FF21 but never the emoji name. With a directory sync recording its
place under the mount root's scope, the next bare sync doesn't take
c.txt. With an accepted root keeping its walk places, the sync after it
takes c.txt. With every pass binding the root, the replaced root is
reported as not the directory disk last agreed with. With each batch
queueing its own scan, three scans run for the three batches. With the
saved state applied over a materialize made before start, status
reports the saved watermark, 3 for 9. With a push that planned nothing
moving the watermark, or setting the pin, both refused-push tests fail
at status's pin and watermark. With the deadline's reason worded
otherwise, the deadline test fails at its reason check while the mount
is held, and ends at once.
…l-start wait, restore the stub in finally

- The header said any other event pushed to a stopped framework still
  throws to its caller. That holds for pushEvent(), which its test checks;
  a module's other events are logged and dropped (pushModuleEvent), so the
  header, and the test's title, now say so.
- frameworkWithRunningTool awaited module.started with no bound, so a tool
  that never started ended the test only at the runner's timeout, naming
  nothing. It now waits with the file's own until(), 5 s, naming the slow
  tool.
- The render test put pushCoalescer back after its asserts, inside try, so
  a failed assert left the stub in place for stop(). It's restored in
  finally now.
…te before opening it

"starts once the watcher is ready, …" and "a re-attached watcher runs its pass at its own ready, …" hold chokidar's directory reads at a gate, then check that some read waited there (`scan.held > 0`). The gate counts only reads made while it is closed, and each test opened it after a fixed run of awaits: `env.open()` or `reattach()`, then `ended()`, which with no pass started is one `setTimeout(0)`. Nothing in that run waits for chokidar. Its first `readdir` comes only after an `lstat` of the mount's root (chokidar 4.0.3, `esm/handler.js:577`), so when that `lstat` took longer than the awaits, every read came after the gate opened, `held` stayed 0, and the check failed. The start test failed that way in a run that began at a 1-minute load of 31.7.

Each test now waits, before `ended()` and the gate's opening, until a read has reached the closed gate, bounded at 15 s like the tests' other waits on chokidar. The check after the gate opened can no longer fail, so it goes, and its message is now the wait's.

Red-first, on Linux with Node 24.22.1, at 1-minute loads between 27.7 and 36.6 where each run began and ended: the two tests ran by name eight times at `main` and eight with this change. In three of the eight, a preload that isn't committed delayed each of chokidar's `lstat`s by 200 ms. It has to replace `lstat` before chokidar loads, since chokidar copies it into `statMethods` then (`esm/handler.js:29`), so a patch made inside the test never reaches it. Delayed, both tests failed every time at `main`, at the held check, and passed every time with this change. Unslowed, at `main` the start test failed once and the re-attach test once in five runs, and with this change both passed all five.
…e fixture may still be appending

Tests that wait on a fixture by polling its JSONL log parsed every
non-empty line, so a read that landed while the fixture was appending
could meet a half-written last line and throw from JSON.parse instead of
waiting. One such read threw "Unterminated string in JSON" in a test that
polls a log for a minute while its fixture writes several hundred lines.

test/helpers/json-lines.ts gives jsonLines(path): the whole lines of the
file parsed, [] for a file that doesn't exist yet. A line still being
written has no newline yet and is left for the next read.

Its readers now, each called while its fixture can still be writing (in
a wait or poll loop):
- batched-wake-locus: sent, publishes;
- discord-awareness-delivery: jsonl;
- host-quiesce: records;
- mcp-modern-outcomes: wire;
- mcpl-down-for-good: legacyPid, which waited for the file to be
  non-empty and then parsed its first line, which could still be
  partial; it now waits for a whole first line;
- mcpl-reconnect-registration: serverEvents;
- mcpl-tool-lifecycle-e2e and mcpl-tool-lifecycle-script: readLog;
- prose-drafts: publishes, sendMode's count, sends;
- reply-server-routes: lines (four describe blocks) and via;
- stop-ends-awareness-drains: untilCalls;
- surgery-marks-receipt: jsonl;
- tune-out-e2e: statusEvents; tune-out-transport: events;
- helpers/reader-fork-rig: published.

Read only once their writer is done, so left as they are:
- continuing-start-silence's lines, after the turn has ended and the
  framework is idle;
- failed-create-releases, after create has rejected;
- liveness-watchdog, after the child has exited;
- mcpl-down-for-good's first line in the version-verdict test, which the
  raw fixture writes before it reads its input;
- pack-installs-alone, after npm has exited;
- tune-out-reader-epoch and tune-out-reader-epoch-shared, after the
  reader's calls have settled.

Given a file whose third line is half-written, jsonLines returns its
first two. A torn read can't be produced on demand, so no test fails
without this change.
…ke finds the agent at rest

Under Bun, "a resident's left script wakes it with wake_agent(), and its
failure wakes it too" failed on every leg of this branch's CI, waiting for
the wake's inference request; under Node it passed.

The test releases the script's first slow call as soon as the script is
waiting on it, while the first turn may still be at its code_execution
tool round. Under Bun the script reached wake_agent() before that round's
tool boundary, so the wake was written while the turn's stream was live,
and the boundary injected it into that stream. With the mock now declaring
supportsInjectedMessages true, as membrane's native path does, the
injected wake counts as carried and the turn that carried it as answered,
so the wake starts no turn, which is what a live native-path stream should
get. Before this branch the mock left the declaration out, and the
undeclared mix injected the wake but left it unread, so a second turn sent
it again; under Node the wake came after the boundary either way. So the
test passed or failed by which side of the tool boundary the script's
wake landed on.

The test's own comment says the script yields and the agent rests, and
that turns 2 and 3 are the wakes. It now waits for the first turn's
inference:completed before releasing the slow call, so the wake always
finds the agent at rest and has to start a turn.

Runs, on code-execution.test.ts's test alone, bun 1.4.4 and node 24:
- at this branch's previous head: bun failed 3 of 3 at the wake's request,
  with the wake injected into the first turn's stream and no second
  stream; node passed with the wake in a second turn's request;
- at main 48f26ce: bun passed 3 of 3, the wake injected into the first
  turn and also sent by a second turn;
- with this change: bun passed 10 of 10 and node passed; at main with the
  same change, bun passed 3 of 3;
- with this change and scriptWake storing its message but requesting no
  turn: bun failed 3 of 3 at the wake's request.
@nissa-seru
nissa-seru merged commit a687fa6 into anima-research:main Oct 30, 2026
12 checks passed
tel-0s added a commit that referenced this pull request Oct 30, 2026
…g pass (#934), an undeclared supportsInjectedMessages read as false

main at a687fa6 is one merge past c530669, #934, with no release in it,
so package.json stays 0.30.0 and this branch's floors stand (chronicle
^0.6.0, context-manager ^0.25.0, membrane ^0.8.0). #934 slims the suite
file by file (161 commits: tests or checks that pinned how the code works,
repeated another check or couldn't fail, removed or rewritten; fixed sleeps
replaced by waits), makes an unscripted stream or call fail its test in six
files, adds test/helpers/json-lines.ts for fixture logs, and removes the
derive skip guards (#886). In src: 93cdb7d reads a stream that doesn't
declare supportsInjectedMessages as one that doesn't carry injected
messages (=== true at the tool boundary, with `|| !currentState.stream`
for the direct API's, and for presentsInjections), MockMembrane and
MockYieldingStream now declare true; bbba75b drops XML tool mode's
preamble fallback, the version-1 awareness ledger's own refusal and
ReferenceTestimony.truncatedFields.

20 paths conflicted (2 in src, 18 in test).

src (5 regions):

- src/agent.ts (2, docs): the shown watermark's doc and markContextShown's
  keep this branch's text (a ReadMark; the window, or in legacy mode
  everything stored; presented-mode marks) with main's change: the shown
  mark moves at a boundary that injects into its stream, no longer
  "whether or not the stream says it carries injected messages".
- src/framework.ts (3):
  - the deferred-write mirror's doc, where main reworded its clause on a
    stream that doesn't declare it carries injections: this branch's
    importer doc (deferred-import), as the queue is gone (#291).
  - the tool boundary: this branch's presentation (presentAtBoundary with
    `injects`, the clock row gated by it, heard rows rendered after they
    are presented), with main's reading: `injects` is now
    `supportsInjectedMessages === true || !currentState.stream`, where it
    was `!== false`. Its comment says so: only a stream that declares it
    carries injected messages hears what arrived; the XML path's false and
    an undeclared stream hear nothing, and what arrived waits for the
    turn's end; a boundary with no stream, the direct API's, presents it.
    On this branch that clause gates the direct-API boundary's clock row
    (clock-rows' new direct-API test) and its presentation of arrivals.
  - releasedWindow's doc: this branch's "presented without sending", with
    main's clause for the direct API's boundary, which has no stream, and
    without the clause for a stream that doesn't declare it.
- The rest of 93cdb7d and bbba75b merged cleanly (presentsInjections's
  === true, the boundary's mark comments, the XML roundPreamble, the
  ledger, references, push-coalescer and types docs).

Tests (18), by what became of each:

- Main's slimming and this branch's adaptation both kept (8):
  - conversation-routing: main drops the private addMessage's return
    checks in the three closure-turn tests; this branch's "stored at once,
    out of the turn's window until its end (#291)" comment stays.
  - deferred-wake-integrity: main's wait for the DM's processing, and its
    write through the module's context, which here is stored at once (a
    stored id, not a held: handle) and out of the window. Internals keeps
    addMessage, which this branch's first test still writes through.
  - deferred-window-only: the header takes main's reading of an undeclared
    stream with this branch's "what arrived" and #291 paragraph. The
    can't-carry test is this branch's; main's new undeclared test takes
    this branch's wording and body (no queue to check at the boundary).
    Main's new direct-API test: here a write during a direct-API tool
    cycle enters the window at once (no turn is alive), so it is made
    while quiesced, as main's is: the boundary presents its results and
    nothing of another's, and resume presents the write after them, still
    unread. The queue-mechanics region main slimmed stays deleted.
  - mcpl-coalescing (8 regions): the replacement-while-alive test is this
    branch's (store holds the replacement alone, the window neither). The
    two held-provider tests take main's "a request that carried it was
    answered" form, with readsRow, and mark answered the consumed
    watermark, checked to cover the row (coverTest). Main's rewrite of the
    assembly-throws test reads here as: a render that settles after its
    turn's assembly threw is stored at once out of the live turn's window,
    and presented at the turn's end. The write-during-assembly test drops
    the assembling set and the private return, and checks the store holds
    the write at once.
  - prose-drafts: both imports; the notice is found by its [drafts] text,
    as main does, in this branch's window.
  - puppet-turn-reservation: main's reservation check through
    getHostModeStatus; this branch's check that the pair is in the window
    stays. Main dropped the deferred-queue check because the stored pair
    shows nothing was stranded; here a stored row may still be out of the
    window, so the window check is the one that says so for this
    parked-admission case.
  - save-recent-image: main's snapAfterCall hold on tool:started, with this
    branch's onSnap hook, awaited first.
  - stored-injections: main's readsAt form of the reads-by-id check, with
    presentAll after the rows each framework writes past the framework;
    the rewind test reads the window, since on a presented window the shed
    is the window's and the record keeps the row.
- Main's deletion taken (2):
  - tool-wrapper-prose-guard-integration: "conversation disposal reclaims
    logical-turn state" goes, with this branch's stand-in context manager.
  - batched-wake-line: the wake line's metadata is checked for its source
    alone; this branch's audience-stamp check goes with the rest, as
    presentation-points' "each resident a batched wake was for writes its
    own line into its own window, once" pins the stamp.
- This branch's deletion stands, main having changed a test of the queue or
  of held rows (7): deferred-write-failures (the file; the short abandon
  grace main gave it is in host-quiesce's unabandonable test already),
  ephemeral-settle (what is queued for a run), module-held-writes (held
  rows' matcher), present-while-acting (a failed start's flush), process-
  response-edit-refusals (an edit of a held write), stop-with-work-in-flight
  (the write deferred behind a failed retry's entry; and the stop's
  [deferral] line, where this branch checks the window), tune-out-cancel-
  held (the held rows the notice no longer carries).
- This branch's version kept (1): reinit-after-branch-move's undo test,
  where an undo here waits for no tick; main's wait on the running pass
  is for a wait this test says doesn't happen, so its watcher is left out.

Read onto this branch past the conflicts:
- turn-inputs-pin: JsStore and ContextManager as ContextManagerClass
  imported again; main removed them with its derive guard, and this
  branch's walkAfterTheSwitch opens an earlier release's store with them.
- tune-out-reader-framing-presented: its derive guard goes, as #886's
  removal did for the rest (reader-fork-rig no longer exports supported).
- present-while-acting: this branch's addressed-from-elsewhere notice test
  drops module.toolDelayMs, which main removed from the robot module.
- tune-out-messages: main's rewritten cancel_tuneout test passes the
  coordinator a getDeferredMessages hook, which this branch removed from
  TuneOutFrameworkHooks; the line goes, as no-deferral-residue asks.

storeSees: no new site; main opens no context manager. agentSees: no new
view decision. Notice tags: none new.

Changelog: no release in main's range, so no fragment was consumed; main's
two new fragments come in, injection-declared rewritten for this branch
(what arrived waits out of the window for the turn's end; the direct API's
boundary presents it), as is README's sentence on an undeclared stream.

Tests: built with zero errors; no stale compiled test; fixtures copied.
The 18 conflicted files and main's other changed test files this branch
also changed, with tune-out-reader-framing-presented (53 files): 2698
tests, 2691 pass, 2 fail, 5 skipped; the two failures, the direct-API test
and the rewind test, adapted above, then pass. With the built boundary's
`|| !currentState.stream` taken out, clock-rows' direct-API test fails;
with `!== false` back, deferred-window-only's undeclared test fails.
Full suite (npm test): 9659 tests, 9609 pass, 1 fail, 49 skipped
(runtime-conditional: Linux-only rules, temp directories that fold case
or normalization, and a structured-content case this runtime makes moot).
The failure was no-deferral-residue, which found
two names of the queue in tests: a held: prefix in deferred-wake-integrity's
check, now a read of the stored row, and tune-out-messages' hook, above.
Those three files and the two adapted ones then pass, 119 of 119.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Derive skip-guards check view/derive alone; derive now needs selective forks (supportsDerivation)

1 participant