Files
stack/packages/tasks/tests/sync.test.mjs
T
jason.woltjeandClaude Opus 5.5 e918a915a8 tasks, bus, harness, seat: the approved requirement ids as data (#1584)
Row 90, round 2. Dewey built it, Filbert approved it (comment 28068) on
manifest 95611274 (21 paths, base b8539480). The approved PRD's
requirement ids live in packages/tasks/requirements.json. task_create
refuses an id outside it, and the PM session gets a list_requirements
tool. Round 2 adds requirements.mjs to the sandbox's RUNNER_FILES
(decision 127). Sage gate on c047d028 plus the patch, 23:50:16Z to
23:54:26Z: tasks 89, bus 111, harness 66, webui 101, cli 123, seat 33,
business 60, control-board 124, queue 150, conversation 182, discord 178,
ledger 78, runs 43, mosaic 69, all fail 0. Every scripts/test-*.sh
exited 0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 18:55:49 -05:00

213 lines
9.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { world, sleep } from './world.mjs';
const ready = async (t, edit) => {
const w = await world(t, edit);
await w.adapter.start('demo');
assert.equal(w.adapter.status()[0].state, 'ready');
return w;
};
const create = (w, title = 'a task') =>
w.call(w.caps.pm ?? w.agent('pm'), 'task.create', { title, request: w.instruction(), requirement: 'REQ-TASK-1' });
const external = (w, subject) => w.events('task.changed.external').filter((e) => subject === undefined || e.subject === subject);
// A fake whose clock runs an hour behind, so every task sits outside the cursor window and only the
// board or a task read can see it.
const behind = { fake: { now: () => Date.now() - 3600000 } };
test('an edit in the UI is recorded once, with the fields that changed', async (t) => {
const w = await ready(t);
const { task_ref } = await create(w);
await w.ui('PATCH', '/tasks/1', { title: 'renamed', priority: 2 });
await sleep(20);
assert.deepEqual(await w.adapter.tick('demo'), { snapshots: 1, events: 1 });
const [e] = external(w, task_ref);
assert.deepEqual(e.body.changed, ['title', 'priority']);
assert.equal(e.body.via, 'cursor');
assert.equal(e.actor_role, null);
const s = w.snapshots(task_ref).at(-1);
assert.equal(s.source, 'poll');
assert.equal(s.fields.title, 'renamed');
assert.equal(s.digest, e.body.digest);
// The overlap window reads the task again; the digest matches, so nothing new is recorded.
await sleep(20);
assert.deepEqual(await w.adapter.tick('demo'), { snapshots: 0, events: 0 });
assert.equal(w.broker.taskView('demo', 'current', task_ref).digest, e.body.digest);
});
test('a move between open buckets is seen on the board, though updated does not change', async (t) => {
const w = await ready(t, behind);
const { task_ref } = await create(w);
const updated = w.fake.task(1).updated;
await w.ui('PUT', `/projects/${w.project}/views/${w.installed.view}/buckets/${w.installed.buckets.blocked}/tasks`, { task_id: 1 });
assert.equal(w.fake.task(1).updated, updated);
await sleep(20);
await w.adapter.tick('demo');
const [e] = external(w, task_ref);
assert.equal(e.body.via, 'board');
assert.deepEqual(e.body.changed, ['bucket']);
assert.deepEqual(
w.broker.taskView('demo', 'open').map((r) => ({ ...r })),
[{ task_ref, bucket: w.installed.buckets.blocked }],
);
});
test("a person's comment is counted and a bot's is not", async (t) => {
const w = await ready(t);
const { task_ref } = await create(w);
await w.call(w.caps.pm, 'task.assign', { task_ref, role: 'coder' });
await w.call(w.agent('coder'), 'task.update.assigned', { task_ref, comment: 'from the coder' });
await w.ui('POST', '/tasks/1/comments', { comment: 'from a person' });
await sleep(20);
await w.adapter.tick('demo');
const [e] = external(w, task_ref);
assert.equal(e.body.comments, 1);
assert.deepEqual(e.body.changed, []);
// A comment changes no digest field, so it is an event without a snapshot.
assert.equal(w.snapshots(task_ref).at(-1).source, 'self');
await sleep(20);
await w.adapter.tick('demo');
assert.equal(external(w, task_ref).length, 1);
});
test('the hourly reconcile catches a comment through comment_count', async (t) => {
const w = await ready(t);
const { task_ref } = await create(w);
await sleep(20);
await w.adapter.reconcile('demo');
await w.ui('POST', '/tasks/1/comments', { comment: 'late' });
await sleep(20);
await w.adapter.reconcile('demo');
const [e] = external(w, task_ref);
assert.equal(e.body.via, 'reconcile');
assert.equal(e.body.comments, 1);
// The tick after it reads the same comment list and finds nothing newer.
await sleep(20);
await w.adapter.tick('demo');
assert.equal(external(w, task_ref).length, 1);
});
test('a task closed in the UI leaves the open view with its done bucket', async (t) => {
for (const edit of [{}, behind]) {
const w = await ready(t, edit);
const { task_ref } = await create(w);
await w.ui('PATCH', '/tasks/1', { done: true });
await sleep(20);
await w.adapter.tick('demo');
const [e] = external(w, task_ref);
// The cursor sees a recent close; an old one is found by reading the task the board dropped.
assert.equal(e.body.via, edit.fake ? 'task' : 'cursor');
assert.deepEqual(e.body.changed.sort(), ['bucket', 'done']);
assert.equal(w.snapshots(task_ref).at(-1).fields.bucket, w.installed.buckets.done);
assert.deepEqual(w.broker.taskView('demo', 'open'), []);
await sleep(20);
assert.deepEqual(await w.adapter.tick('demo'), { snapshots: 0, events: 0 });
}
});
test('a task that leaves the board is recorded as deleted, moved or out of reach', async (t) => {
const w = await ready(t);
const shared = w.fake.project('Shared', w.owner);
w.fake.share(shared, w.bots.sync, 0);
const hidden = w.fake.project('Launchpad', w.owner);
const refs = [];
for (const title of ['deleted', 'moved', 'hidden']) refs.push((await create(w, title)).task_ref);
await w.ui('DELETE', '/tasks/1');
w.fake.moveToProject(2, shared);
w.fake.moveToProject(3, hidden);
await sleep(20);
await w.adapter.tick('demo');
assert.deepEqual(
w.events('task.missing').map((e) => [e.subject, e.body]),
[
[refs[0], { reason: 'not-found' }],
[refs[1], { reason: 'moved', project: shared }],
[refs[2], { reason: 'no-access' }],
],
);
assert.deepEqual(w.snapshots(refs[1]).at(-1).fields, { gone: 'moved', project: shared });
// The hidden project's title never reaches the bus.
assert.ok(!JSON.stringify(w.store.all('SELECT * FROM events')).includes('Launchpad'));
assert.deepEqual(w.broker.taskView('demo', 'open'), []);
await sleep(20);
assert.deepEqual(await w.adapter.tick('demo'), { snapshots: 0, events: 0 });
assert.deepEqual(await w.adapter.reconcile('demo'), { snapshots: 0, events: 0 });
await assert.rejects(w.call(w.caps.pm, 'task.assign', { task_ref: refs[0], role: 'coder' }), (e) => e.code === 'task-not-found');
});
test('a poll that read before a verb wrote does not overwrite the verb', async (t) => {
let skew = 0;
const w = await ready(t, { adapter: { clock: () => Date.now() + skew } });
const { task_ref } = await create(w);
// The broker's clock can run a few ms ahead of the poll's; a read at or before the self snapshot
// is not newer evidence, and the overlap window rereads it next tick.
const at = w.broker.taskView('demo', 'current', task_ref).at;
await w.ui('PATCH', '/tasks/1', { title: 'raced' });
skew = Date.parse(at) - 1 - Date.now();
await w.adapter.tick('demo');
skew = 0;
assert.equal(external(w, task_ref).length, 0);
await sleep(20);
await w.adapter.tick('demo');
assert.deepEqual(external(w, task_ref)[0].body.changed, ['title']);
});
test('a tracker fault during a tick is reported and the next tick catches up', async (t) => {
const w = await ready(t);
await create(w);
await w.ui('PATCH', '/tasks/1', { title: 'while down' });
w.fake.fault('GET', /\/buckets\/tasks$/, 503);
await assert.rejects(w.adapter.tick('demo'), (e) => e.code === 'tracker-unavailable');
await sleep(20);
await w.adapter.tick('demo');
assert.equal(external(w).length, 1);
});
test('a malformed answer refuses the tick with tracker-shape', async (t) => {
const w = await ready(t);
await create(w);
// A 200 whose body is an error object, not the board.
w.fake.fault('GET', /\/buckets\/tasks$/, 200, { apply: true });
await assert.rejects(w.adapter.tick('demo'), (e) => e.code === 'tracker-shape');
});
test('no token value reaches the database, the log or a refusal', async (t) => {
const { readdirSync, readFileSync, statSync } = await import('node:fs');
const { join } = await import('node:path');
const w = await ready(t);
const { task_ref } = await create(w);
await w.call(w.caps.pm, 'task.assign', { task_ref, role: 'coder' });
await w.call(w.agent('coder'), 'task.update.assigned', { task_ref, state: 'in-progress', comment: 'x' });
await w.ui('PATCH', '/tasks/1', { title: 'y' });
w.fake.fault('PUT', /\/tasks$/, 500);
await assert.rejects(w.call(w.caps.coder, 'task.update.assigned', { task_ref, state: 'blocked' }));
await sleep(20);
await w.adapter.tick('demo');
await w.adapter.reconcile('demo');
w.store.all('PRAGMA wal_checkpoint(TRUNCATE)');
const files = [];
const walk = (d) => {
for (const f of readdirSync(d)) {
const p = join(d, f);
if (statSync(p).isDirectory()) walk(p);
else files.push(p);
}
};
walk(w.root);
assert.ok(files.length > 0);
const values = Object.values(w.tokens);
for (const f of files) {
const bytes = readFileSync(f, 'latin1');
for (const v of values) assert.ok(!bytes.includes(v), `a token value is in ${f}`);
}
for (const v of values) assert.ok(!JSON.stringify(w.log).includes(v));
// A token pasted into a verb argument is refused by the broker before the adapter sees it.
await assert.rejects(
w.call(w.caps.coder, 'task.update.assigned', { task_ref, comment: w.tokens.coder }),
(e) => e.code !== undefined && !e.message.includes(w.tokens.coder),
);
});
// The first look at a task counts only comments inside the window, so a restart doesn't replay
// every comment ever made as new.
test('the first look at a task counts only comments inside the window', async (t) => {
let shift = -2 * 3600000;
const w = await ready(t, { fake: { now: () => Date.now() + shift } });
const { task_ref } = await create(w);
await w.ui('POST', '/tasks/1/comments', { comment: 'two hours ago' });
shift = 0;
await w.ui('POST', '/tasks/1/comments', { comment: 'just now' });
await sleep(20);
await w.adapter.tick('demo');
const [e] = external(w, task_ref);
assert.equal(e.body.comments, 1);
});