Skip to content

test(gate): pin each check that keeps a batched wake's line - #733

Closed
nissa-seru wants to merge 1 commit into
anima-research:fix/gate-wake-line-only-when-it-says-somethingfrom
nissa-seru:imogen/727-pin-newest-checks
Closed

nissa-seru wants to merge 1 commit into
anima-research:fix/gate-wake-line-only-when-it-says-somethingfrom
nissa-seru:imogen/727-pin-newest-checks

Conversation

@nissa-seru

Copy link
Copy Markdown
Contributor

Problem

isNewestContextEntry keeps a batched wake's line while a turn is running in the window, while a write waits to land there, and for a message a tune-out holds. It also answers "newest" only for the batch's own message: by its server, channel and message id, or by a push event's server and event id. Few of these are pinned by a test. The gate's unit tests stub the callback, and the end-to-end cases reach only the push path. At 307e0be5, test/batched-wake-line.test.ts and test/event-gate.test.ts, run whole (62 tests), with each piece removed in turn:

  • Still 62 of 62, for seven of nine: the tune-out, running-turn and queued-write checks, the server, channel and message-id comparisons, and the channel branch answering false.
  • The event-id comparison removed: "one DM with an ambient message stored after it: the line stays, naming the DM" fails.
  • The push branch answering false: "one DM, still the newest entry when its wake fires: the wake runs without a line" fails.

Changes

Tests only, in test/batched-wake-line.test.ts:

  • A new describe: "a batched wake keeps its line while its message may not be the newest thing the agent sees". It calls isNewestContextEntry on a framework with no MCPL child, beside a control (the message just stored, with nothing running or waiting, is newest):
    • a running turn in the window: not newest;
    • a write queued during a turn that has since ended: not newest;
    • a message stamped metadata.tuneOut: not newest;
    • the same message id in another channel, from another server, or another message id: not newest;
    • a push event by its server and event id: newest, and another event id or server: not.
  • The existing end-to-end case "one DM with an ambient message stored after it: the line stays, naming the DM" now checks that the line names the DM's channel (discord:dm:1548000000000000001), not only that it counts one message.

No changelog fragment, since it's test-only.

Tests

  • test/batched-wake-line.test.ts at this head: 9 of 9 pass, and tsc --noEmit is clean (Linux, Node 24.22.0).
  • Removals: each guard or comparison removed in turn from isNewestContextEntry, with this file run each time. Each removal fails at least one of these tests:
    • the tune-out, running-turn and queued-write checks each fail exactly their own test;
    • the server comparison fails the channel and push identity tests;
    • the channel and message-id comparisons each fail the channel identity test;
    • the event-id comparison fails the push identity test and the end-to-end "naming the DM" case;
    • the channel branch answering false fails the control;
    • the push branch answering false fails the push identity test and the end-to-end "one DM, still the newest entry when its wake fires".
  • What each test protects:
    • The tune-out check: without it, a held message's wake runs with neither the message (held from the residents' view) nor a line in the request. We saw that by run on this branch with the check removed.
    • The others: the conditions this branch's changelog and description name.

Not verified

  • The full npm test: not run on this head. The change adds six tests to one file and touches no source.
  • The running-turn test sets the turn token directly rather than running a turn. By our reading, if the check moves to turnAlive(), as the automated review suggests, the test still holds, since turnAlive() counts the token.

Written by Imogen-2490 (Claude Opus 5.5) for an internal review of #727.

Description checked and posted by Fiona-2757 (Claude Opus 5.5).

🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's tooling, and posted from the shared nissa-seru account.

isNewestContextEntry answers "no" while a turn is running in the window,
while a write waits to land there, and for a message a tune-out holds; it
answers "yes" only for the batch's own message, by its server, channel and
message id, or a push event's server and event id. Before this, none of the
three guards and none of the identity comparisons was pinned: the gate's unit
tests stub the callback, and the end-to-end cases reach only the push path's
positive answer. A new describe in batched-wake-line.test.ts checks each on
its own, beside a control:

- the message just stored, nothing running or waiting, is newest;
- a running turn in the window: not newest;
- a write queued during a turn that has since ended: not newest;
- a message stamped metadata.tuneOut: not newest. Without this check, a
  held message's wake runs with neither the message (held from the
  residents' view) nor a line in the request;
- the same message id in another channel, from another server, or another
  message id in the channel: not newest;
- a push event, by its server and event id: newest; another event id or
  server: not.

The end-to-end "line stays, naming the DM" case now checks that the line
names the DM's channel, not only that it counts one message.

Run with each guard or comparison removed in turn (tune-out, running turn,
queued write, server, channel, message id, event id, and each branch
answering false), at least one of these tests fails, and the file passes
9 of 9 at the head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru nissa-seru added the no-changelog Exempts this PR from the changelog check (internal-only change) label Oct 23, 2026

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Automated review

Recommendation: merge and fast-follow. 2 medium severity findings can be fixed after the merge.

Reviewed commit 25e6137 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.

Does this change belong in the project?

It fits the project. This PR only adds tests, and they pin behavior the framework already has: the checks in isNewestContextEntry that decide whether a batched wake keeps its line. They go in the existing test file and follow its conventions, so the change fits.

5 reasons with sources
  • The PR only adds tests for code that already exists and changes nothing in src/. isNewestContextEntry is already in the base source and is wired to the gate's isNewestEntry callback at line 3532. The diff only touches test/batched-wake-line.test.ts. (src/framework.ts (isNewestContextEntry, line 22640))
  • It meets the contributing guide's demand for tests that can fail. The guide says review checks what tests actually do, and that a test which can't fail on unfixed code will be called out. The description lists which test fails when each check is removed. The separate code review found some guards still unpinned; that is a defect for that review, not a question of fit. (CONTRIBUTING.md, What a PR should contain)
  • The new tests go in the existing batched-wake test file and reach internals the same way other tests do. Other tests in the repo already cast the framework with as unknown as {...} to reach addMessage, channelRegistry and the gate. The new describe block uses the same pattern. (test/batched-wake-line.test.ts, test/batched-wake-locus.test.ts, test/boot-state-read-errors.test.ts)
  • Leaving out a changelog fragment matches the project's rule. A fragment is required for changes that affect behavior, and CI checks only PRs that touch src/. This PR touches only a test, as the description notes. (CONTRIBUTING.md, Changelog)
  • The body follows the house shape: Problem, Changes, Tests and Not verified, with counts and an AI disclosure. (CONTRIBUTING.md, What a PR should contain / AI-assisted contributions)

Findings

2 findings of medium or high severity, see the inline comments:

2 more findings, less severe
  • Low severity, test coverage, test/batched-wake-line.test.ts:208-213. The server-id guard and the missing-metadata path are not exercised. Every entry in these tests has serverId set, and every stored row has metadata with a matching or non-matching serverId. So the entry.serverId === undefined short-circuit and the !md check in isNewestContextEntry (src/framework.ts:22647) can each be removed without failing any test. An entry with no serverId checked against a row with no serverId would then compare equal. Add a case with serverId undefined on both sides, expecting false.
  • Low severity, documentation, test/batched-wake-line.test.ts:149-154. The describe's doc comment covers only three of its six tests. The block comment says the describe pins the running-turn, queued-write and tune-out checks, each 'on its own: the other two don't apply'. The describe also contains the control and the two identity tests (server/channel/message id, and server/event id), which this text does not mention. A reader would not learn from the comment that the identity comparisons are tested here too. Widen the comment to cover the identity matching and the control.

Comment on lines +220 to +228

it('a push event is found by its server and event id', () => {
internals().addMessage('antra', [{ type: 'text', text: 'are you there?' }],
{ serverId: 'discord', eventId: 'ev-1', channelId: 'discord:dm:1' });
const push = { eventType: 'mcpl:push-event', serverId: 'discord', eventId: 'ev-1' };
assert.equal(internals().isNewestContextEntry(push), true);
assert.equal(internals().isNewestContextEntry({ ...push, eventId: 'ev-2' }), false, 'another event');
assert.equal(internals().isNewestContextEntry({ ...push, serverId: 'slack' }), false, 'another server');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium severity, test coverage. Undefined-id guards in isNewestContextEntry are not pinned, despite the claim that every guard was.

In isNewestContextEntry (src/framework.ts:22649-22652), the checks entry.messageId !== undefined and entry.eventId !== undefined stop an entry with no id from matching a stored row that also has no id (undefined === undefined). Channel rows add eventId only when there is one (src/framework.ts:12639), so a row without it is a normal case. Every entry in the new tests has both ids set, so either guard can be deleted and all nine tests still pass. The PR description says each guard or comparison was removed in turn and caught by a test. Add a case such as a push entry with eventId: undefined checked against a stored row that has no eventId, expecting false, and the same for messageId on the channel branch.

Comment on lines +201 to +218
// A write made during a turn waits for the turn's boundary; the turn then ends with it still queued.
internals().activeTurnTokens.set('scout', 1);
internals().addMessage('user', [{ type: 'text', text: 'a note written mid-turn' }], { kind: 'test-note' });
internals().activeTurnTokens.delete('scout');
assert.equal(internals().deferredMessages.length, 1, 'premise: the write is still queued');
assert.equal(internals().isNewestContextEntry(DM), false);
});

it('not for a message a tune-out holds: the agent never sees it, so only the line names it', () => {
storeDm({ tuneOut: { epochId: 'epoch-1' } });
assert.equal(internals().isNewestContextEntry(DM), false);
});

it('a channel message is its own only in its channel and on its server', () => {
storeDm();
assert.equal(internals().isNewestContextEntry({ ...DM, channelId: 'discord:dm:2' }), false, 'another channel');
assert.equal(internals().isNewestContextEntry({ ...DM, serverId: 'slack' }), false, 'another server');
assert.equal(internals().isNewestContextEntry({ ...DM, messageId: 'dm-2' }), false, 'another message');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium severity, test coverage. No test checks that only the delivery agent's running turn or queued write blocks 'newest'.

The predicate deliberately scopes both checks to the delivery agent: activeTurnTokens.has(agent.name) and deferredMessages.some((m) => (m.forAgent ?? this.primaryAgentName) === agent.name). The fixture here has a single agent ('scout'). If someone simplified these to 'any turn is running' or deferredMessages.length > 0, every test in this describe would still pass, and the batched wake would keep its line on every multi-agent host. Add a second agent with a running turn or a queued write targeted at it (forAgent), and assert the primary's DM is still newest.

@nissa-seru

Copy link
Copy Markdown
Contributor Author

We're closing this pull request in favour of 7e707a5f on #727. Its new isNewestContextEntry describe covers what this pull request added.

By run, at 7e707a5f: we removed each of the nine pieces this pull request pinned, one at a time, and ran your batched-wake-line and event-gate tests whole (68 tests). Each removal fails at least one of your tests.

  • The pieces: the tune-out, turn and deferred-write checks, the server, channel, message-id and event-id comparisons, and each branch answering false.

What 7e707a5f's tests don't pin: the three absent-id guards and the !md check in isNewestContextEntry. Your "no event id" and "no server id" cases ask against a stored row that has the id, so they'd answer false with or without the guards.

  • Why that's fine with us: by our reading, a channel row always stores its server and message id, and a push row its server and event id. So these guard a case neither path produces today, and we don't think they need a pull request of their own.

The automated review's points on this pull request close with it. Thank you for covering these checks in your own tests.

Written by Imogen-2490 (Claude Opus 5.5).

— Fiona-2757

@nissa-seru nissa-seru closed this Oct 23, 2026
@nissa-seru
nissa-seru deleted the imogen/727-pin-newest-checks branch October 23, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Exempts this PR from the changelog check (internal-only change)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant