LearnNewsExamplesServices
Frontmatter
id17204
titleA divergent-path segment-load test can false-red on correct production
stateClosed
labels
bugaitesting
assigneesneo-opus-ada
createdAtAug 15, 2026, 9:18 PM
updatedAtAug 16, 2026, 1:23 AM
githubUrlhttps://github.com/neomjs/neo/issues/17204
authorneo-opus-ada
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 16, 2026, 1:23 AM

A divergent-path segment-load test can false-red on correct production

Closed Backlog/active-chunk-16 bugaitesting
neo-opus-ada
neo-opus-ada commented on Aug 15, 2026, 9:18 PM

Context

Split out of #17188 after @neo-gpt's review of PR #17198 falsified the assumption both halves shared. That ticket now covers only the same-target pair, which was a provably inert deletion. This is the half that cannot be fixed test-side.

The distinction was established at source, not argued: MailboxService.mjs:2705

const candidateState = idFilter ? null : await getMailboxGraphProjectionRepairCandidates(),

The same-target pair passes target for both callers, so both await getMailboxGraphProjectionRepairCandidates() — whose coalescing promise is process-wide rather than keyed (:1573). They are joined upstream of the segment load and their relative timing is not free. This pair is different: one caller passes ids, takes the idFilter branch, skips the scan entirely, and diverges before the join.

The Problem

test/playwright/unit/ai/services/memory-core/MailboxService.spec.mjs:1298"concurrent global and explicit repairs share one physical segment load (#16767)"

await readEntered;
for (let turn = 0; turn < 3; turn++) {
    await new Promise(resolve => setImmediate(resolve));
}
expect(payloadReads, 'different ids in one segment must join before either read completes').toBe(1);
releasePayloadRead();

The single-flight map entry is dropped when caller one's read resolves (MailboxService.mjs:1216-1220), which happens after releasePayloadRead(). Because the two callers diverge before the join, a legitimately late caller two can reach its segment-load decision after that deletion, legitimately miss the pending promise, open its own read, and fail the exact-count assertion on correct production.

That is a false RED, and under workers: 4 it surfaces as a flaky. Per #15861's amended AC-2 a flaky is a positive detection that disqualifies the sample — so this costs a probe sample exactly as much as a real defect would. It is not a coverage hole; it is a false-positive source aimed at the one signal the probe reads.

The Architectural Reality — why no test-side fix exists

Three candidate anchors, all falsified rather than assumed:

candidate why it fails
the marker readFile getGraphMarkerFileStats (messageWalStore.mjs:334-351) uses fs.stat, not readFile — invisible to the stub
readJsonlEntries (the only marker path that does read) skipped on the cache hit that two concurrent unchanged callers produce (:479-495)
the readdir in listMessageWalSegmentKeys measured: the counter is already at 2 at turn 0, while caller two's read decision lands around turn 2 — the anchor fires two turns early

There is a structural reason the list is not merely incomplete: when caller two joins, it produces no side effect at all. Joining is silence by construction, and the only observable difference — the second payload read — is the failure signal. No observation can distinguish "joined" from "not yet arrived".

getMessageWalCandidateSegmentLoad (MailboxService.mjs:1187) is module-private, not exported, so a deterministic rendezvous requires a production seam. #17188 placed production behaviour out of scope, which is why this is its own ticket rather than a widening of that one.

Also checked and cleared, so it is not re-investigated: projectionStatsCache (messageWalStore.mjs:23) is module-scope keyed by dir, which looks like cross-file worker state in exactly the class #15861 hunts. It is not — messageWal.dirTest derives from the active test's memoryWal.dir, isolating the key by construction (configBase.mjs:544-549).

The Fix

Decide the seam, then build the rendezvous. The decision is the deliverable; the code is small once it is made.

Two shapes, and picking is the work:

  1. Export a test-visible join signal from production. getMessageWalCandidateSegmentLoad already returns {joined, pending, signature} — the fact the test needs exists and is discarded at the module boundary. Exporting a counter or an event makes the rendezvous exact. Cost: a production surface that exists only for a test, which is the thing #17188 deliberately avoided.
  2. Restructure so arrival is awaited rather than observed. Drive the second caller from a point where its progress is a promise the test already holds, rather than racing two public calls and watching. Cheaper if it works; it is unproven that it can, given the join is silent.

Not an option: widening the turn budget. For this assertion a larger budget makes the false RED less frequent rather than absent, which converts a reproducible finding into a rare one — the #17186 trap, in the direction that hides it.

Acceptance Criteria

  • The seam decision is recorded with the rejected alternative and why, before any code lands
  • Caller two's arrival at its segment-load decision is deterministic, not observed through a proxy — and if a new anchor is proposed instead, it is proved to fire at or after the decision, since the readdir candidate failed by firing two turns early
  • Specificity proved, not just sensitivity: correct production passes the repaired test across repeated consecutive runs. @neo-gpt's distinction is the reason this AC exists — a broken-reuse red proof shows the arm can fire, never that it does not fire when the code is right
  • Sensitivity retained: with the single-flight forced to never reuse its pending promise, the test still fails
  • Both proved individuallytest.describe.configure({mode: 'serial'}) at line 25 makes a combined run report 1 did not run for the second test, which proves nothing about it
  • If a production surface is added, it is justified in the ticket rather than taken as a default, and its own test coverage lands with it

Out of Scope

  • The same-target pair — its wait is removed in #17188 / PR #17198, on the separate ground that both callers are joined upstream.
  • :1381 — repaired in #17186.
  • Widening the budget. Named above as the trap rather than an alternative.
  • retries: 2. Whether the suite should retry at all is #15861's question.

Avoided Traps

  • Assuming the anchor list is incomplete rather than the approach wrong. Three candidates failed for three different reasons, and the fourth would fail for the same structural one: a join emits nothing.
  • Reading a red proof as sufficient. Sensitivity and specificity are different properties; this ticket's whole subject is a test that fires when it should not, which a sensitivity proof cannot see.
  • Treating a false RED as harmless because it is loud. Under workers: 4 it is a flaky, and a flaky disqualifies a #15861 sample — so it costs exactly what a real defect costs.
  • Deleting the wait along with the assertion. Tempting once the assertion looks redundant, and wrong here: the wait is the only thing narrowing the false-RED window, even though it cannot close it.

Evidence class

L2 — the divergence is read at MailboxService.mjs:2705; the anchor falsifications are reproduced in-process, with the readdir timing measured by an instrumented loop.

Related

#17188 (the same-target half, split from this) · PR #17198 · #17186 (the fixed-turn-budget repair that started the arc) · #15861 (the workers: 4 re-land this blocks) · #17192 (the fifth defect — a different mechanism) · PR #17183

Blocks #15861.

Handoff Retrieval Hints

  • query_raw_memories("MailboxService divergent global explicit segment load rendezvous false red joining is silent")
  • Falsification anchor: MailboxService.mjs:2705idFilter ? null : await getMailboxGraphProjectionRepairCandidates(). The ids path skipping the scan is the entire reason this pair is not coupled like its sibling.
  • The three dead anchors are enumerated above; do not re-propose them without explaining why the join stops being silent.

Origin Session ID: 00348bc3-c011-4035-90a3-f0eb62b8c95c

Live latest-open sweep: latest 20 open issues at 2026-08-15T19:15Z plus a targeted segment load rendezvous divergent single-flight MailboxService search over 100 open+closed — no equivalent. A2A in-flight claim sweep: 10 most recent across all read-states — no competing claim. Structure-map gate: N/A, no .mjs file introduced or relocated.

tobiu referenced in commit 8e3216a - "fix(memory-core): rendezvous on the join decision instead of guessing at it (#17204) (#17216) on Aug 16, 2026, 1:23 AM
tobiu closed this issue on Aug 16, 2026, 1:23 AM