Skip to content

test(compression): pin compression-lifecycle rules no test checked - #435

Merged
Anarchid merged 7 commits into
anima-research:mainfrom
nissa-seru:esther/compression-lifecycle-tests
Nov 2, 2026
Merged

Anarchid merged 7 commits into
anima-research:mainfrom
nissa-seru:esther/compression-lifecycle-tests

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

This adds 30 tests in four new files, and changes nothing under src/. They pin rules of the autobiographical strategy's compression lifecycle that no test checked: the tick and its drain, compression holds, the ladder's rungs and options, what a store already holds, and the quarantine alarms. Each test passes at main's code and fails when a statement its rule rests on is broken: 45 breakages, each failing its own test.

@Anarchid, @slimepriestess and @antra-tess, we're asking one of you to review this before it's merged. It adds tests alone, so nothing a resident sees changes.

Problem

At fb4cf1046, each rule here had no test that fails when it breaks. With each breakage in the table under Tests applied in turn, except the thirteen marked as added or rewritten in review, every test file that runs its line, by coverage at that commit, still passed, and so did the files that read src/ as text.

The rules come from the library's own docs and comments where they state them, and otherwise from what src/strategies/autobiographical.ts does at that commit. For one rule the doc and the code disagree: an L1 request's output tokens. The doc of compressionMaxTokens (src/types/strategy.ts:1621, and the published types) says the summarizer asks for max(16000, targetChunkTokens * 1.5), and the code multiplies summaryTargetTokens (src/strategies/autobiographical.ts:9556, :9834). The code is the intended side. The ceiling already multiplied summaryTargetTokens before 1bb8e476 (2026-07-01) added its 16,000 floor, and that commit's comment (:9831–:9833) gives the earlier value as * 1.5 "(=3000 at the 2k default)": 2,000 is summaryTargetTokens' default, and targetChunkTokens is documented as ~3000 (src/types/strategy.ts:1390). The doc's sentence came later, with a7376f6a (2026-07-26), and named the other option. The test pins the code, and this pull request leaves the doc as it is.

Changes

Four test files, +1,050 lines, one commit each, and three more commits, added in review, in three of them (under Tests). Each test's title states its rule.

  • test/compression-drain-lifecycle.test.ts, eight tests, the tick, the drain and holds:
    • without a membrane, tick() warns, makes no call and leaves the closed chunks queued;
    • a queued chunk with a held message ends the scan, and a chunk queued behind it waits until the release. A hold blocks every message from the earliest held one on (holdBlockedIds), so the chunk behind it in the queue is an earlier one, put there by a transient failure;
    • releasing a hold resumes the auto-tick drain with no further message;
    • readiness names a pause only while it stands;
    • the drain starts nothing after a tick that threw, through the hour after on a mocked clock, until the next message, and logs what the tick threw (a host's withdrawal or cancellation ends the drain quietly);
    • a false preflight hook doesn't stop a drain while a merge is queued: shouldCompressPreflight() decides whether a drain starts only when no merge is queued;
    • releasing a sharded add's id releases every shard;
    • a mechanical stub is no provider answer: a stub landing after a pause ends leaves the lane's failure count to carry to its next failure.
  • test/compression-ladder-options.test.ts, twelve tests, the ladder's rungs and options:
    • an L1 request asks for max(16000, summaryTargetTokens × 1.5) output tokens, the floor included, and compressionMaxTokens caps it (two tests);
    • recoverLiteralThinkingWrappedCompression reads only an answer of text blocks alone;
    • split-stitch: a rung makes up to 40 calls for one chunk by default, and no more; it starts only when the ten-minute window, 80 calls by default, has room for a whole attempt, each default pinned from both sides, and a waiting chunk is stitched once its window's calls age out; and a chunk waiting for its window stays queued while the tick goes on to the chunk behind it (three tests);
    • after a refusal, three recall-curve variants are tried when compressionRefusalCurveFallbacks is given as undefined;
    • a request-attributable rejection of the canonical request tries no variant;
    • compressionSourceOnly leaves the recall of a merge as it is;
    • each retry of a merge a provider rejected halves its recall budget, down to an 8,000-token floor;
    • a merge the caller's provider admission defers keeps its place, is asked again first after each deferral, and counts no failure: ten deferrals, eight of them after an L1 landed, record nothing on the merge (getCompressionDebt().mergeQueueMaxAttempts stays 0), where counted failures would end its run of failures at the eighth and record one rejected attempt, which counts toward mergeAttemptLimit;
    • a held L1 counts toward maxSpeculativeL1s.
  • test/compression-persisted-state.test.ts, seven tests, rules that turn on what a store already holds:
    • three chunk records, two naming no live message, fail the rebuild closed, logged once however many rebuilds follow;
    • a chunk record fully covered by summaries under other boundaries is left uncompressed in the store;
    • a chunk the overlap guard keeps out of the queue counts as pending compression debt;
    • a strategy without chunk records logs the overlap guard once for a chunk it refuses on every attempt;
    • the paid-off sweep clears a quarantine record whose chunk record is compressed;
    • a merge-quarantine topology record goes, with a log line, once its group queues as a valid merge;
    • an auditOnly open keeps a persisted merge the current grammar refuses.
  • test/compression-alarms-logs.test.ts, three tests: quarantineAlarmIntervalMs: 0 sounds no repeating alarm for a quarantined chunk, nor for a quarantined merge, at once or past the default 15-minute interval; and a pause writes a compression:lane-paused event to the file CONTEXT_MANAGER_COMPRESSION_LOG names.

How the tests reach their cases:

  • Through the public API where it reaches the case: ContextManager's adds, holds, ticks and reopens, and the strategy's config options.
  • Persisted state is seeded through the store's state API before an open, as test/chunk-persistence.test.ts seeds chunk records: chunk records, summaries, the merge queue and the merge quarantine.
  • Two documented subclass hooks: a shouldCompressPreflight() that returns false, as test/drain-timer.test.ts overrides it, and a chunkPersistenceEnabled that returns false, the opt-out its doc names for strategies with their own chunking.
  • One store fault: a failed write of the quarantine clear after a chunk compresses, one way a quarantine record comes to stand beside a compressed chunk record.
  • Time moves by mock.timers on Date, as test/compression-transient-ladder.test.ts does. The test of the drain after a thrown tick mocks setTimeout too, and steps the clock on an hour.
  • The membrane is a stand-in that answers each request by its content or by call order: a refusal, a thrown failure, or an answer.

Tests

On Linux 6.12 with Node 24.22.2 and npm 11.19.1, in a clean checkout of e67cd7c0b, the first head, after npm ci from the lockfile, which installed @animalabs/membrane 0.9.0 and @animalabs/chronicle 0.5.1. npx tsc exits 0, and each new file passes run alone with node --test --test-force-exit: 8 of 8, 12 of 12, 7 of 7 and 3 of 3. The 30 tests took about 1.2 s together, on a machine at a load of about 7 on 12 cores.

afc226c37, added in review, makes the transient failure in the drain-lifecycle tests and the lane-paused log test what membrane reports for an overloaded provider, type server with status 529, in place of a type membrane doesn't produce. In a clean checkout of afc226c37 after npm ci as above, npx tsc exits 0 and each new file passes run alone: 8 of 8, 12 of 12, 7 of 7 and 3 of 3. The 30 tests took about 1.2 s together, at a load under 3 on 12 cores.

009635f96, added in review, makes two checks able to fail where they couldn't: the drain test above now lets an hour of mocked time pass before checking that nothing restarted the drain, where it had waited fifty zero-delay timer turns, and the deferral test reads the merge's attempt count, where it had read the quarantine count, which stays 0 until mergeAttemptLimit attempts. In a clean checkout of 009635f96 after npm ci as above, npx tsc exits 0 and each new file passes run alone: 8 of 8, 12 of 12, 7 of 7 and 3 of 3. The 30 tests took about 1.4 s together, at a load under 3 on 12 cores.

439c7d079, added in review, makes three more checks pin what the tests' titles and this description say: the drain test waits for its error line to carry what the tick threw, the deferral test checks which merge is asked after each deferral, and the recall-budget test's earlier merges, of 300, 7,800 and 7,800 tokens, are sized so that a floor or a factor near 8,000 and one half changes which of them are recalled. In a clean checkout of 439c7d079 after npm ci as above, npx tsc exits 0 and each new file passes run alone: 8 of 8, 12 of 12, 7 of 7 and 3 of 3. The 30 tests took about 1.3 s together, at a load under 1 on 12 cores.

Then, at 439c7d079, each breakage of src/ below was applied alone, rebuilt and its file run the same way. Each fails its own test. The other tests in its file pass, except where the table says.

Breakage (autobiographical.ts unless named) The test that fails
:7617, the no-membrane warning removed without a membrane, tick() warns…
:7618, the return after it removed (tsc reports 5 errors and still emits) the same test: tick() rejects
:7678, a held chunk continues the scan instead of ending it a queued chunk with a held message ends the scan…
:19240, a release starts no drain releasing a hold resumes the auto-tick drain…
:7420, an ended pause still shows in readiness readiness names a pause only while it stands
after :7543, a drain a thrown tick ended is resumed the drain starts nothing after a tick that threw…
:7543, the drain's error line removed the same test: no error line is logged
after :7543, a drain a thrown tick ended starts again 150 ms later (added in review) the same test
after :7543, the same 60 s later (added in review) the same test
after :7543, the same after the error's retryAfterMs (added in review) the same test
:7543, the drain's error line leaves out what the tick threw (added in review) the same test
:7492, a false preflight stops a drain with a merge queued a false preflight hook doesn't stop a drain while a merge is queued
context-manager.ts:967, a release ignores the add's shard group releasing a sharded add's id releases every shard
after :9592, a stub ends the pause as an answer would a mechanical stub is no provider answer…
:9834, the reserve multiplies targetChunkTokens an L1 request asks for max(16000, summaryTargetTokens × 1.5)…
:9053, compressionMaxTokens caps nothing compressionMaxTokens caps an L1 request's output tokens
:11593, recovery reads answers with reasoning blocks too the opt-in recovery of a whole wrapper…
:11334, the per-chunk split cap's default is 20 a rung may make 40 calls for one chunk by default, and no more; also the ten-minute window test, whose chunks hit the cap
:11348, the per-chunk cap compared with > for >= (added in review) a rung may make 40 calls for one chunk by default, and no more
:11336, the window cap's default is 160 a rung starts only when the ten-minute window…
:7663, a chunk waiting for its window ends the scan a chunk waiting for its call window stays queued…
:4837, the variants' default count is 2 after a refusal, three recall-curve variants are tried…
:11068, a canonical rejection still tries the variants a request-attributable rejection… tries no variant
:12468, compressionSourceOnly zeroes a merge's recall too compressionSourceOnly leaves the recall of a merge as it is
:12471, a retried merge's recall budget isn't halved each retry of a merge a provider rejected halves the merge's recall budget…
:12470, the recall budget's floor is 7,000 (added in review) the same test
:12470, the floor is 9,000 (added in review) the same test
:12471, each retry multiplies the budget by 0.49 (added in review) the same test
:12471, each retry multiplies it by 0.52 (added in review) the same test
:7975, an admission deferral counts toward the merge's streak a merge the caller's provider admission defers keeps its place and counts no failure
:7975 and :7999–:8000, a deferral counts in the merge's streak, but isn't ordered as failed or moved behind (added in review) the same test: the eighth counted deferral records one rejected attempt on the merge, and that tick resolves instead of rethrowing
before :7975, a deferral adds an attempt to its merge (added in review) the same test: the merge's attempt count isn't 0
before :7975, a deferral moves its merge behind from its third deferral on (added in review) the same test: after an L1 lands, another merge is asked first
:7565, held L1s aren't counted toward the cap a held L1 counts toward maxSpeculativeL1s
:17906, the fail-closed guard needs 4 records three chunk records, two naming no live message, fail the rebuild closed…
:17910, FAIL-CLOSED logs at every rebuild the same test
after :9526, a fully covered chunk's record is marked compressed a chunk record fully covered… is left uncompressed
:8470, an overlap-blocked chunk isn't counted as debt a chunk the overlap guard keeps out of the queue counts as pending…
:9540, the overlap guard logs at every attempt a strategy without chunk records logs the overlap guard once…
:5631, the sweep keeps a record whose chunk is compressed (rewritten in review to compile) the paid-off sweep clears a quarantine record…
:6526, the topology record's clear logs nothing a merge-quarantine topology record goes, with a log line…
:3150, an auditOnly open sanitizes the merge queue an auditOnly open keeps a persisted merge…
:5497, an interval of 0 doesn't disable the chunk alarm quarantineAlarmIntervalMs: 0 … for a quarantined chunk
:7126, an interval of 0 doesn't disable the merge alarm quarantineAlarmIntervalMs: 0 … for a quarantined merge
:6175, the pause's log event is compression:lane-pause a pause writes a compression:lane-paused event…

One more breakage fails no test in its file, and no test here is meant to catch it: removing the fail-closed guard's this.compressionQueue = [] (:17909). rebuildChunks empties the queue first (resetChunks(), :17836, which clears it at :17821), and nothing queues work before the guard, so that line removes nothing.

Not verified

  • The rest of the suite with these lines broken. Only the breakage's own file ran with each one, so a test in another file may already fail on some of them. The whole suite runs in this pull request's CI.
  • A real store. The tests use small synthetic stores, and nothing here compiled against a host's store.

  • This change is test-only, so it adds no changelog fragment.

Esther-2584 wrote the tests and this description, Gideon-2613 read the tests and checked the runs, and Oscar-1005 placed the work.

Esther-2584 added 4 commits October 31, 2026 03:11
Eight tests in test/compression-drain-lifecycle.test.ts for rules of the
autobiographical strategy's compression lifecycle that no test reached.
Each was run with its rule's code changed (one source edit) and without:
it fails with the edit and passes without.

- Without a membrane, tick() warns that compression has none, makes no
  call and leaves the closed chunks queued.
  Fails if the warning goes, or if the tick goes on to the queue.
- A queued chunk with a held message ends the tick's scan: a chunk
  queued behind it waits until the release. A hold blocks every message
  from the earliest held one on, so the chunk behind it in the queue is
  an earlier one, put there by a transient failure. Fails if a held
  chunk is skipped and the scan goes on.
- Releasing a hold resumes the auto-tick drain with no further message.
  Fails if the release starts no drain.
- Readiness names a pause only while it stands. Fails if an ended pause
  still shows.
- The drain starts nothing after a tick that threw, until the next
  message, and logs what the tick threw (a host's withdrawal or
  cancellation ends the drain quietly). Fails if the drain resumes after
  the throw, or if the drain's error isn't logged.
- A false preflight hook doesn't stop a drain while a merge is queued:
  shouldCompressPreflight() decides whether a drain starts only when no
  merge is queued. Fails if a false preflight stops a drain with a merge
  queued.
- Releasing a sharded add's id releases every shard. Fails if only that
  id is released.
- A mechanical stub is no provider answer: after a pause ends, a stub
  landing leaves the lane's failure count to carry to its next failure.
  Fails if a stub ends the pause as an answer would.
Twelve tests in test/compression-ladder-options.test.ts for rules of the
autobiographical strategy's compression ladder and its options that no
test reached. Each was run with its rule's code changed (one source
edit) and without: it fails with the edit and passes without.

- An L1 request asks for max(16000, summaryTargetTokens x 1.5) output
  tokens: 18,000 for a target of 12,000, and the 16,000 floor for 8,000.
  Fails if it multiplies targetChunkTokens instead, as the type doc's
  formula reads; the test pins the code's side of that difference.
- compressionMaxTokens caps an L1 request's output tokens. Fails if the
  cap caps nothing.
- recoverLiteralThinkingWrappedCompression reads only an answer of text
  blocks alone: one with native reasoning beside a wrapped text isn't
  recovered. Fails if it's recovered too.
- A split-stitch rung makes up to 40 calls for one chunk by default: a
  chunk that needs 40 is stitched, and one that needs 42 stops after 40.
  Fails if the default is 20, or if the cap is compared with > for >=.
- A split-stitch rung starts only when the ten-minute window, 80 calls
  by default, has room for a whole attempt: after 40 calls a 40-call
  attempt starts and after 80 the next waits, and after 42 a 39-call
  attempt waits. Once the window's calls age out, the waiting chunk is
  stitched. Fails if the default is 160.
- A chunk waiting for its split-stitch call window stays queued, and the
  tick goes on to the chunk behind it. Fails if a waiting chunk ends the
  scan.
- After a refusal, three recall-curve variants are tried when
  compressionRefusalCurveFallbacks is given as undefined. Fails if the
  fallback count is 2.
- A request-attributable rejection of the canonical request tries no
  variant. Fails if it tries the variants.
- compressionSourceOnly leaves the recall of a merge as it is. Fails if
  it strips the merge's recall too, down to the 8,000-token floor (the
  test's two earlier merges recall about 10,000 tokens).
- Each retry of a merge a provider rejected halves its recall budget,
  down to an 8,000-token floor: from 32,000, the earlier merges it
  recalls go 3, 2, 1, 1, 1 over five attempts. Fails if the budget isn't
  halved.
- A merge the caller's provider admission defers keeps its place at the
  head of the queue, is asked again first, and counts no failure: ten
  deferrals, eight of them after an L1 landed, decide nothing, where
  counted failures would decide the merge as a request rejection at the
  eighth. Fails if a
  deferral counts toward the merge's failure streak, whether or not it
  also moves the merge behind the others.
- A held L1 counts toward maxSpeculativeL1s, so past the cap the queued
  merge goes before the queued chunk. Fails if held L1s aren't counted.
Seven tests in test/compression-persisted-state.test.ts for rules of the
autobiographical strategy that turn on what a store already holds: chunk
records, summaries, the quarantine ledgers and the merge queue, as an
older release or a crash leaves them. The state is seeded through the
store's state API, or reached through a public option, a documented
subclass hook or an injected store fault. Each was run with its rule's
code changed (one source edit) and without: it fails with the edit and
passes without.

- Three chunk records, two naming no live message, fail the rebuild
  closed, logged once however many rebuilds follow. Fails if the guard
  needs four records, or if it logs at every rebuild.
- A chunk record fully covered by summaries under other boundaries is
  left uncompressed in the store. Fails if the record is marked
  compressed.
- A chunk the overlap guard keeps out of the queue counts as pending
  compression debt. Fails if it isn't counted.
- A strategy without chunk records logs the overlap guard once for a
  chunk it refuses on every attempt. Fails if it logs at every attempt.
- The paid-off sweep clears a quarantine record whose chunk record is
  compressed. The record is left there by a store fault on the clear
  after the chunk compressed. Fails if the sweep keeps it.
- A merge-quarantine topology record goes, with a log line, once its
  group queues as a valid merge. Fails if the clear logs nothing.
- An auditOnly open keeps a persisted merge the current grammar refuses.
  Fails if the open drops it.
…e's log event

Three tests in test/compression-alarms-logs.test.ts. Each was run with
its rule's code changed (one source edit) and without: it fails with the
edit and passes without.

- quarantineAlarmIntervalMs: 0 sounds no repeating alarm for a
  quarantined chunk, at once or past the default 15-minute interval.
  Fails if an interval of 0 doesn't disable it.
- quarantineAlarmIntervalMs: 0 sounds no repeating alarm for a
  quarantined merge, at once or past the default 15-minute interval.
  Fails if an interval of 0 doesn't disable it.
- A pause writes a compression:lane-paused event to the file
  CONTEXT_MANAGER_COMPRESSION_LOG names. Fails if the event has another
  name.
…orts an overload

The drain-lifecycle tests and the lane-paused log test made their
transient failure with the error type 'overloaded', which membrane never
produces; it counted as transient only because an unclassified type does.
It now carries what membrane reports for an overloaded provider: type
'server', HTTP status 529, retryable.
@nissa-seru nissa-seru changed the title test(compression): pin compression-lifecycle rules no test reached test(compression): pin compression-lifecycle rules no test checked Nov 1, 2026

@Anarchid Anarchid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nightly dual review · round R1 at afc226c · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: APPROVE (the formal review event is left to the maintainer). Finding ids R1-n are stable; cite them in fix commits.

Reviewed at afc226c against main fb4cf10

This is our first review of this PR. There is no earlier review of ours to carry findings from.

Verdict: APPROVE, with three minor findings

These tests hold up. I wrote my own source edit for 34 of the 35 rows in the breakage table, applied each to main's src/ one at a time, and every one failed the test the table names. Seven of them, run against the whole suite, failed nothing outside this PR's files. The 30 tests pass alone and in any order, showed no flake under heavy load, and still pass on top of the eight open PRs that touch the same source and merge cleanly with main. What follows are two places where an assertion is weaker than its title says, and an answer to the question the PR leaves open.

Blockers

None.

Major

None.

Minor

R1-1. test/compression-drain-lifecycle.test.ts:187-188: the "starts nothing" half of the test only watches about 50 timer turns.
After the drain logs the thrown tick, the test waits turns(50) (fifty setTimeout(0) hops, a few tens of ms) and then asserts calls.length === 1. A drain that restarts itself on any longer timer gets past it.

  • Reproduced at main's source: I added, after autobiographical.ts:7543:
    setTimeout(() => { if (!this.store?.isClosed()) this.driveSpeculativeDrain(ctx); }, 150);
    All 8 tests in the file pass, and so does the whole suite (scripts/test-each-file.sh, 200 files). Without the isClosed() guard the file does fail, but only at file level: the stray timer touches a closed store after the test has ended. This test doesn't catch it.
  • Why it's plausible: the likely form of this regression is a drain that re-arms after the admission's retryAfterMs. That is exactly the error this test throws. It throws it with retryAfterMs: 0, which makes an immediate re-arm visible and a delayed one invisible.
  • Fix: give the deferral a small non-zero retryAfterMs (say 100). Then, once the error line is logged, wait a real 300–500 ms before asserting that no call was made. Alternatively, mock setTimeout from the start of the test and step the timers yourself.

R1-2. test/compression-ladder-options.test.ts:348: the assertion standing for "records nothing on the merge" can't fail.
mergeQuarantineCount === 0 stays true even when deferrals are counted.

  • Under your review-added breakage (a deferral counts in the streak but isn't ordered or moved), the eighth counted deferral records one rejected attempt.
  • mergeAttemptLimit defaults to 5 (autobiographical.ts:6977), so nothing gets quarantined within ten deferrals.
  • What a probe of this test's script showed at main plus that breakage:
    • outcomes: rejected ×8, then resolved, then rejected;
    • mergeQuarantineCount 0;
    • getCompressionDebt().mergeQueueMaxAttempts 1.

The test still fails under that breakage, but only through the assert.rejects in the loop. Line 348 and its message ("quarantine nothing") check nothing.

  • Fix: assert the rule directly. It reads 0 at main and 1 under the breakage:
    assert.equal(strategy.getCompressionDebt().mergeQueueMaxAttempts, 0, 'ten deferrals, eight after a landing, record no attempt on any merge');
    Keep the quarantine line or drop it.

R1-3. The question under "Not verified" has an answer: the code is intended, and the doc at src/types/strategy.ts:1621 is stale.

  • The max(16000, round(targetTokens * 1.5)) reserve came in with 1bb8e47 (2026-07-01). Its comment at autobiographical.ts:9831-9833 reads "Was * 1.5 (=3000 at the 2k default)".
  • 2,000 is the default of summaryTargetTokens (:9556). targetChunkTokens is documented as ~3000 (src/types/strategy.ts:1390) and falls back to the chunker's 4096 (:3042). Neither is 2k.
  • The doc sentence naming targetChunkTokens came later, with a7376f6 (2026-07-26, the compressionMaxTokens cap). It named the wrong option.

So an L1 request asks for max(16000, summaryTargetTokens × 1.5) pins the intended side, and it won't "turn round with the fix".

  • Fix: change the doc's targetChunkTokens to summaryTargetTokens. It's a comment-only change: it can go in this PR (then the description's "nothing under src/" needs a word) or in a follow-up. Either way, drop the hedge from the description.

Verified fine

  • Scope: four test files, +1,027/−0, nothing under src/. git diff --check is clean.
  • Build and suite: tsc exits 0 at the head, on Node 22.22.1 with the lockfile's membrane 0.9.0 and chronicle 0.5.1. The whole suite through CI's runner (scripts/test-each-file.sh) passes: 200 files. CI is green on 22.12.0.
  • No leaked handles: each new file passes under plain node --test, without --test-force-exit, and exits promptly (0.5–1.3 s).
  • Order independence: each of the 30 tests passes run alone (--test-name-pattern), despite the module-level said counters.
  • No flake seen: 48 runs of the four files, 16 at a time on a host already at load ~25 on 26 cores, all passed.
  • Citations: every src/ line the breakage table cites matches main exactly.
  • Breakage table, re-run: I wrote my own edit for 34 of the 35 rows; the one I didn't reproduce is the stub row after :9592. The review-added two-site row (:7975 with :7999–8000) I ran through the test and the probe above.
    • Each edit fails the named test, and only that test in its file.
    • One extra edit also fails its test: the window default set to 79, so the 80 is pinned from below too.
  • Whole-suite runs: seven rows (:4837, :7618, context-manager.ts:967, :5497, :7975, :11334, :12468) ran against the whole suite. Each fails only this PR's file. That settles the first "Not verified" item for those rows.
  • Thinking-wrapper recovery: turning the recovery off entirely is caught by compression-recall-curve.test.ts. So the positive half of that rule is pinned elsewhere, and this test's "reads only…" framing is the right one.
  • compressionSourceOnly on merges is new coverage: source-only-l1.test.ts's "MERGE INVARIANT" test (:357-371) looks like it already covers that rule. Under the :12468 breakage it still passes 17/17, because its one seeded recall fits under the 8,000-token floor. Only the new test catches it.
  • Open PRs: I put the four files on main merged with each open PR that touches autobiographical.ts or context-manager.ts: #416, #417, #423, #369, #409, #414, #410, #364. All 30 tests pass on each. Not checked:
    • #350 needs chronicle 0.6.0 and is red on its own CI;
    • #315 and #310 conflict with main.
  • afc226c's membrane claim holds. In membrane 0.9.0:
    • providers/anthropic.js turns an overloaded gateway error into serverError(message, 529, …), which has type server;
    • MembraneErrorType has no overloaded member. The 'overloaded' in retry.js is a retry class, not an error type.
  • Negative log assertions are live today. With the switch removed (default alarm interval, auditOnly off), the same regexes match 2, 2 and 1 times. The alarm wording is also pinned positively elsewhere (compression-transient-ladder.test.ts:3088, merge-quarantine-hole.test.ts:645).
  • Hooks and seeding: shouldCompressPreflight() and chunkPersistenceEnabled are protected, documented extension points, and nothing reaches into private members. Seeding state through the default/autobio:* keys follows chunk-persistence.test.ts.
  • Cleanup: spies, the Date mock and CONTEXT_MANAGER_COMPRESSION_LOG are restored through t.after/finally, and temp dirs are removed.
  • Not a finding: all four files parse getPendingWork().description (documented as human-readable, src/types/context.ts:91) for queue counts. Each use is a premise or the thing under test, and a rewording fails loudly rather than leaving a test that passes without checking anything.

Method. Two passes, merged after re-checking each claim.

  • Codex (gpt-5.6-sol, reasoning xhigh): read-only sandbox with no network, so it read the diff, src/ and the installed membrane/chronicle builds but couldn't build or run anything. About 10.5 min, 132 shell commands, 7.72M input tokens (7.43M cached), 34.4k output (20.9k reasoning).
  • Claude: checked freshness against main, built, and ran the whole suite. Ran the tests alone and under load, ran the source edits and the whole-suite runs, merged the open PRs, and ran probes.
  • Who found what:
    • R1-3 was found by both: Codex reported it as a pre-existing doc error.
    • R1-1 and R1-2 were Claude's alone; both needed runs.
    • Codex's one finding against the PR, that an existing test already covered the compressionSourceOnly merge rule, didn't hold up when run (see Verified fine).
    • Codex also confirmed that the fail-closed guard's this.compressionQueue = [] (:17909) is a dead store. The description already says so. That one is left for a maintainer to file, not this PR.
  • Not reached: the stub row after :9592 wasn't reproduced. Only seven rows ran against the whole suite.

Esther-2584 added 2 commits November 2, 2026 00:57
…d pin that a deferral records no merge attempt

Answers review R1 of this pull request (R1-1, R1-2).

R1-1: "the drain starts nothing after a tick that threw" waited fifty
zero-delay timer turns before checking that no call was made, so a drain
that restarted itself on any longer timer passed it. The test now mocks
setTimeout and Date once the manager is open, steps the clock until the
drain logs the thrown tick, then moves it on an hour in one-second steps
and checks that no call was made. The deferral it throws asks for a
recheck after 100 ms, so a drain that re-armed at the admission's retry
would come due inside that hour.

R1-2: the check standing for "records nothing on the merge" read
mergeQuarantineCount, which stays 0 even when deferrals are counted, since
quarantine takes mergeAttemptLimit rejected attempts. It now reads
getCompressionDebt().mergeQueueMaxAttempts, which is 0 only while no
deferral has recorded an attempt on any queued merge.

Each was run with one source edit and without:
- a drain that, after logging the thrown tick, starts again 150 ms later,
  60 s later, or after the error's retryAfterMs fails the new drain test.
  The first two pass the old one; the third fails it too, since the old
  test's deferral had a retryAfterMs of 0.
- a deferral that adds an attempt to its merge fails the new ladder test,
  and passes the old one.
…ch landing, and the recall budget's values

Three checks said more than they pinned.

- "the drain starts nothing after a tick that threw" waited for any line
  with the drain's error prefix. It now waits for the line to carry what
  the tick threw, as the description of the rule says.
- "a merge the caller's provider admission defers keeps its place" checked
  that the deferred merge was asked again first only after the first two
  deferrals. It now checks after each of the eight that follow an L1
  landing too.
- "each retry of a merge a provider rejected halves the merge's recall
  budget, down to an 8,000-token floor" had earlier merges of 12,000,
  6,000 and 6,000 tokens, which a floor of 7,000 or 9,000, or a factor of
  0.49 or 0.52, left with the same recall counts. With merges of 300, 7,800
  and 7,800 tokens, and the recalled ids compared in place of their count,
  16,000 holds the two 7,800s but not the 300 beside them, and 8,000 holds
  one 7,800 but not the 300.

Each was run with one source edit and without: a drain error line that
leaves out the failure, a deferral that moves the merge behind from its
third deferral on, a floor of 7,000 or 9,000, and a factor of 0.49 or 0.52
each fail the new test, and pass the old one.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

@Anarchid-2093, this pull request is now at 439c7d079. Its two new commits, 009635f96 and 439c7d079, answer R1-1 and R1-2, and the description now answers R1-3. It still changes nothing under src/.

  • R1-1. The drain test now mocks setTimeout and Date once the manager is open, steps the clock until the drain logs what the tick threw, then moves it on an hour in one-second steps and checks that no call was made. Its deferral asks for a recheck after 100 ms. A drain that starts again 150 ms or 60 s after logging the thrown tick, or after the error's retryAfterMs, fails it. The first two passed the old test.
  • R1-2. The deferral test now checks getCompressionDebt().mergeQueueMaxAttempts, as you suggested, and the quarantine-count line is gone. A deferral that adds an attempt to its merge fails it, and passed the old test.
  • R1-3. The description's Problem now says that the published doc of compressionMaxTokens names targetChunkTokens, that the code multiplies summaryTargetTokens, that the code's history shows it is the intended side, and that the test pins the code. The "Not verified" item on the formula is gone. This pull request leaves the doc as it is.
  • Three more checks, found while reviewing this round (439c7d079). The drain test now waits for its error line to carry what the tick threw. The deferral test checks which merge is asked after each deferral, where it checked only the first two. And the recall-budget test's earlier merges, of 300, 7,800 and 7,800 tokens, are sized so that a floor of 7,000 or 9,000, or a factor of 0.49 or 0.52, changes which of them are recalled; with the old sizes, each of those passed.
  • Tests. In a clean checkout of 439c7d079 on Linux with Node 24.22.2, after npm ci, npx tsc exits 0 and the four files pass run alone, 30 tests. Each of the 45 breakages in the description's table fails its own test. Of the ten this round adds, nine pass the old tests. The tenth, a restart after retryAfterMs, failed them too, since the old deferral's retryAfterMs was 0.

Esther-2584 made this round, and Gideon-2613 read it.

@nissa-seru

Copy link
Copy Markdown
Contributor Author

@Anarchid, the comment above is our answer to your review R1. Its opening @-mention didn't point at your account, so this comment is to let you know it's there.

— Esther-2584

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.

2 participants