Files
stack/packages/bus/tests/comment-cursors.test.mjs
jason.woltjeandClaude Opus 5.5 3a4afdc03a bus: comment cursors survive a restart, schema 3b to 3c (row 49, #1535)
The broker stores the highest comment id seen per business and task in an
append-only table, task_comment_cursors. The tasks adapter restores those
ids before its startup reconcile, and the first reconcile reads each
task's comment list even with no cached count, so a comment posted while
the bus was down is found. Cursor rows, snapshots and comment events
commit in one transaction. migrate-schema.mjs takes a stopped store from
3b to 3c offline; the store refuses a 3b database until it runs.
scripts/land-work/read.mjs reads SCHEMA_VERSION from store.mjs.

The live bus is 3b. From this commit on, land-work refuses it with
bus-schema until Mos migrates in the schema window
(agents/sage/work/window-plan/schema-chain-3c-3e.md).

Candidate 87ef1321a1a08be64a20499889fc24b5549b47d6d33abfd86db95f62fb3cb6d0,
Rocko packet agents/rocko/work/queue-49/r1 (ea5e70fa8), base 7b166a0f1.
Filbert approved in 28579 and Darkwing in 28587. Landing gate at
ebbbf62da: 24 suites green, 0 failed. 14 package suites passed 1598
tests (bus 178, tasks 97, business 92, cli 195, webui 112). The 10
script suites passed (land-work 84, queue 156). Follow-ups: P19 tasks 300
(duplicate cursor) and 304 (push a task's cursor only when consider runs
for it).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 12:43:56 -05:00

51 lines
2.8 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Store } from '../src/store.mjs';
import { Broker } from '../src/broker.mjs';
function fixture(t) {
const root = mkdtempSync(join(tmpdir(), 'comment-cursors-'));
const store = new Store(root);
const roles = { pm: { authority: { withinRole: ['message.send'], crossRole: [] } } };
const b = new Broker({ store, businesses: {
demo: { id: 'demo', human: 'jason', arbiters: { technical: 'pm', delivery: 'pm' }, roles },
other: { id: 'other', human: 'jason', arbiters: { technical: 'pm', delivery: 'pm' }, roles },
} });
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
return { store, b, record: (commentCursors, extra = {}) => b.recordTask({ business: 'demo', commentCursors, ...extra }) };
}
test('comment cursors isolate businesses, move only forward and remain append-only', t => {
const { store, b, record } = fixture(t);
record([{ task_id: 7, last_comment: 11 }]);
record([{ task_id: 7, last_comment: 15 }]);
b.recordTask({ business: 'other', commentCursors: [{ task_id: 7, last_comment: 2 }] });
assert.deepEqual(b.taskView('demo', 'comments').cursors.map(c => ({ ...c })), [{ task_id: 7, last_comment: 15 }]);
assert.deepEqual(b.taskView('other', 'comments').cursors.map(c => ({ ...c })), [{ task_id: 7, last_comment: 2 }]);
for (const last_comment of [11, 15]) assert.throws(() => record([{ task_id: 7, last_comment }]), /must advance/);
for (const sql of [
'UPDATE task_comment_cursors SET last_comment=20 WHERE seq=1',
'DELETE FROM task_comment_cursors WHERE seq=1',
"INSERT OR REPLACE INTO task_comment_cursors VALUES(1,'demo',7,20)",
]) assert.throws(() => store.run(sql), /append-only/);
assert.equal(store.get('SELECT count(*) n FROM task_comment_cursors').n, 3);
});
test('only trusted sync writes validated cursors; events and cursor share rollback', t => {
const { store, b, record } = fixture(t);
const cap = b.bindLaunch({ business: 'demo', role: 'pm', run: 'pm-run', harness: 'pi' });
for (const x of [null, {}, { task_id: 0, last_comment: 1 }, { task_id: 1, last_comment: 0 },
{ task_id: 1, last_comment: 1.5 }, { task_id: 1, last_comment: 2 ** 53 },
{ task_id: 1, last_comment: 1, extra: true }]) {
assert.throws(() => record([x]), e => e.code === 'invalid-request');
}
assert.throws(() => record([{ task_id: 1, last_comment: 1 }], { cap }), e => e.code === 'agent-required');
assert.throws(() => record([{ task_id: 1, last_comment: 1 }], {
events: [{ kind: 'human.input', body: {} }],
}), e => e.code === 'reserved-event');
assert.equal(store.get('SELECT count(*) n FROM task_comment_cursors').n, 0);
});