Brand board revisions, the five dashboard mockups with their verify
script and evidence, the slice 1 S5 design note (SLICE1-VIEWS.md, rev 3
plus the S2c section 10 update), the Console mockup of inbox, tasks,
agents and trail (mockups/slice1/) and checks/slice1-verify.mjs (16
checks; check 16 pinned to bus at d27042fa). Also the CHAT-03 progress
log and the 2026-09-26 dirty-tree manifest. Design and evidence only;
no package code. Row 40 (#1522), committed on its own at Sage's
direction, 2026-10-09.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
185 lines
21 KiB
JavaScript
185 lines
21 KiB
JavaScript
// Fixture for the slice 1 S5 mockup. Rows are shaped like schema v3b
|
||
// (agents/darkwing/work/slice1-proto/schema-v3b.sql, 179ffe35, lead decision 60), and the rows the
|
||
// broker writes follow its writers in packages/bus/src/broker.mjs (S2c, d27042fa; S2b refused messages
|
||
// 101 and 102 and S2c restores them, see SLICE1-VIEWS.md section 10):
|
||
// human requests are a message plus a human.input naming it, every human answer is a human.input,
|
||
// every decision.raise has its action.allowed context, session.launched comes from bindLaunch.
|
||
// checks/slice1-verify.mjs inserts every row into that schema, so its triggers check the fixture too.
|
||
// `lookups` stands in for what no Q1 read verb returns yet (business file, kanban view, S3 status);
|
||
// mockups/slice1/q1.js labels each use. Nothing here is live data.
|
||
window.S1 = (() => {
|
||
'use strict';
|
||
const NOW = '2026-10-06T15:00:00Z';
|
||
const B = 'mosaic-stack';
|
||
const id = n => `0d1a${String(n).padStart(4, '0')}-5e7c-4b1a-9c3e-1f2a3b4c5d6e`;
|
||
const D = { launch: id(1), prd: id(2), prdOld: id(3), conflict: id(4), scope: id(5), merged: id(6) };
|
||
|
||
const lookups = {
|
||
business: B,
|
||
human: 'jason',
|
||
tracker: { baseUrl: 'http://127.0.0.1:3456', project: 3, view: 9 },
|
||
buckets: { 21: 'todo', 22: 'in-progress', 23: 'in-review', 24: 'blocked', 25: 'done' },
|
||
bots: { 7: 'bot-mosaic-stack-pm', 8: 'bot-mosaic-stack-cto', 9: 'bot-mosaic-stack-coder', 10: 'bot-mosaic-stack-reviewer' },
|
||
instances: [
|
||
{ instance: 'pm', definition: 'pm', harness: 'pi', model: 'claude-opus-5-5', arbiterFor: 'delivery' },
|
||
{ instance: 'cto', definition: 'cto', harness: 'claude-code', model: 'claude-opus-5-5', arbiterFor: 'technical' },
|
||
{ instance: 'coder', definition: 'coder', harness: 'pi', model: 'claude-sonnet-5-5' },
|
||
{ instance: 'reviewer', definition: 'reviewer', harness: 'pi', model: 'claude-sonnet-5-5' },
|
||
],
|
||
launch: { by: 'pm', instances: ['coder', 'reviewer'], max: { opus: 4, sonnet: 4 } },
|
||
families: { 'claude-opus-5-5': 'opus', 'claude-sonnet-5-5': 'sonnet' },
|
||
// Proposed in SLICE1-VIEWS.md Q2. The database can't answer this today.
|
||
poll: { project: 3, board: '2026-10-06T14:59:41Z', cursor: '2026-10-06T14:59:42Z', error: null, proposed: true },
|
||
};
|
||
|
||
const role_claims = [
|
||
{ seq: 1, at: '2026-10-06T08:02:10Z', business: B, role: 'pm', op: 'claim', holder_run: 'run-pm-7f31', harness: 'pi', address: null, by: 'run-pm-7f31', reason: null, decision: null },
|
||
{ seq: 2, at: '2026-10-06T08:05:44Z', business: B, role: 'cto', op: 'claim', holder_run: 'run-cto-2b90', harness: 't3', address: 't3:5c1e9a0d', by: 'run-cto-2b90', reason: null, decision: null },
|
||
{ seq: 3, at: '2026-10-06T09:46:03Z', business: B, role: 'coder', op: 'claim', holder_run: 'run-coder-a442', harness: 'pi', address: null, by: 'run-coder-a442', reason: null, decision: null },
|
||
{ seq: 4, at: '2026-10-06T11:20:00Z', business: B, role: 'reviewer', op: 'claim', holder_run: 'run-rev-0c18', harness: 'pi', address: null, by: 'run-rev-0c18', reason: null, decision: null },
|
||
{ seq: 5, at: '2026-10-06T12:41:27Z', business: B, role: 'reviewer', op: 'release', holder_run: 'run-rev-0c18', harness: 'pi', address: null, by: 'run-rev-0c18', reason: 'verdict posted', decision: null },
|
||
];
|
||
|
||
// The broker stamps every write with a strictly later millisecond (#now). Rows one call writes are a millisecond apart here.
|
||
const plus = (at, n) => new Date(Date.parse(at) + n).toISOString();
|
||
const opt = (...pairs) => JSON.stringify(pairs.map(([key, text]) => ({ key, text })));
|
||
const decisions = [
|
||
{ seq: 1, id: D.merged, at: '2026-10-06T09:42:00Z', business: B, project: 'stack', raised_by_role: 'pm', raised_by_run: 'run-pm-7f31', class: 'gated', action: 'role.launch', route_to: 'human', question: 'Launch the coder instance for task #41 (bus store)?', options: opt(['yes', 'Launch coder on Sonnet'], ['no', 'Do not launch']), recommendation: 'yes', task_ref: 'vikunja:3/41', requirement_ref: 'REQ-DEC-1', blocking: 1, supersedes: null },
|
||
{ seq: 2, id: D.prdOld, at: '2026-10-06T10:02:00Z', business: B, project: 'stack', raised_by_role: 'cto', raised_by_run: 'run-cto-2b90', class: 'gated', action: 'prd.approve', route_to: 'human', question: 'Approve the REQ-TASK-2 wording change?', options: opt(['a', 'Approve'], ['b', 'Reject']), recommendation: 'a', task_ref: null, requirement_ref: 'REQ-TASK-2', blocking: 0, supersedes: null },
|
||
{ seq: 3, id: D.prd, at: '2026-10-06T10:31:00Z', business: B, project: 'stack', raised_by_role: 'cto', raised_by_run: 'run-cto-2b90', class: 'gated', action: 'prd.approve', route_to: 'human',
|
||
question: 'Approve the REQ-TASK-2 wording change? The draft says **"every 30 seconds"**; probes support it.\nQuoted from the agent, unchanged: <img src=x onerror="window.__pwned=1"><script>window.__pwned=2</script> exe.txt and a bell \u0007.',
|
||
options: opt(['a', 'Approve as written'], ['b', 'Approve with "at least every 60 seconds"'], ['c', 'Reject; keep draft 0.3 wording']), recommendation: 'a', task_ref: null, requirement_ref: 'REQ-TASK-2', blocking: 0, supersedes: D.prdOld },
|
||
{ seq: 4, id: D.launch, at: '2026-10-06T13:52:00Z', business: B, project: 'stack', raised_by_role: 'pm', raised_by_run: 'run-pm-7f31', class: 'gated', action: 'role.launch', route_to: 'human', question: 'Launch a second coder instance so the Vikunja poller (#44) can move while #42 is in progress?', options: opt(['yes', 'Launch coder-2 on Sonnet'], ['no', 'Keep one coder; #44 waits'], ['later', 'Ask again when #42 is in review']), recommendation: 'yes', task_ref: 'vikunja:3/44', requirement_ref: 'REQ-LAUNCH-1', blocking: 1, supersedes: null },
|
||
{ seq: 5, id: D.conflict, at: '2026-10-06T14:31:20Z', business: B, project: 'stack', raised_by_role: 'coder', raised_by_run: 'run-coder-a442', class: 'cross-role', action: 'task.close', route_to: 'pm', question: 'Task #48 is done in Vikunja but in-review in the queue. Which stands?', options: opt(['queue', 'Keep in-review; ask the person to reopen'], ['vikunja', 'Close in the queue after a verdict']), recommendation: 'queue', task_ref: 'vikunja:3/48', requirement_ref: 'REQ-TASK-4', blocking: 0, supersedes: null },
|
||
{ seq: 6, id: D.scope, at: '2026-10-06T14:40:05Z', business: B, project: 'stack', raised_by_role: 'coder', raised_by_run: 'run-coder-a442', class: 'cross-role', action: 'task.scope.change', route_to: 'cto', question: 'Task #42 also needs a status read for poll freshness. Add it to the scope?', options: opt(['add', 'Add to #42'], ['split', 'New task for S3']), recommendation: 'split', task_ref: 'vikunja:3/42', requirement_ref: 'REQ-WEB-1', blocking: 0, supersedes: null },
|
||
];
|
||
|
||
const decision_events = [
|
||
{ seq: 1, decision: D.merged, at: '2026-10-06T09:44:10Z', op: 'seen', by: 'jason', choice: null, note: null, via: 'cli' },
|
||
{ seq: 2, decision: D.merged, at: '2026-10-06T09:44:31Z', op: 'resolved', by: 'jason', choice: 'yes', note: null, via: 'cli' },
|
||
{ seq: 3, decision: D.prdOld, at: '2026-10-06T10:30:59.999Z', op: 'withdrawn', by: 'run-cto-2b90', choice: null, note: null, via: null },
|
||
{ seq: 4, decision: D.launch, at: '2026-10-06T13:58:02Z', op: 'seen', by: 'jason', choice: null, note: null, via: 'cli' },
|
||
];
|
||
|
||
// A human request is a message from the CLI plus a human.input naming it (broker message.send). The
|
||
// broker routes it to the role's holder; the holder's receive and read add deliveries.
|
||
const PM = { holder_run: 'run-pm-7f31', address: null };
|
||
const later = (at, s) => new Date(Date.parse(at) + s * 1000).toISOString().replace('.000Z', 'Z');
|
||
const asks = [[3, '2026-10-06T08:29:00Z', 'Write the identity runbook first.'], [5, '2026-10-06T09:38:00Z', 'Build the bus store next.'],
|
||
[35, '2026-10-06T09:48:10Z', 'Then the read verbs, and the inbox and decide commands on top of them.'],
|
||
[12, '2026-10-06T09:54:30Z', 'The poller has to catch column moves within one tick.'], [36, '2026-10-06T10:17:40Z', 'Add a task for the task-create path itself.']];
|
||
const messages = [
|
||
{ seq: 1, id: id(101), at: '2026-10-06T13:52:01Z', business: B, from_role: 'pm', from_run: 'run-pm-7f31', to_role: 'human', class: 'DECISION', in_reply_to: null, decision: D.launch, corrects: null, body: 'Blocking: launch a second coder for #44?' },
|
||
{ seq: 2, id: id(102), at: '2026-10-06T09:42:01Z', business: B, from_role: 'pm', from_run: 'run-pm-7f31', to_role: 'human', class: 'DECISION', in_reply_to: null, decision: D.merged, corrects: null, body: 'Blocking: launch the coder for #41?' },
|
||
...asks.map(([n, at, body]) => ({ seq: 0, id: id(200 + n), at, business: B, from_role: 'human', from_run: 'human-cli', to_role: 'pm', class: 'INFO', in_reply_to: null, decision: null, corrects: null, body })),
|
||
];
|
||
// The human holds no claim, so a message to the human is routed with no holder. The DM is the Discord connector's delivery.
|
||
const deliveries = [
|
||
{ seq: 1, message: id(101), at: plus('2026-10-06T13:52:01Z', 1), op: 'routed', holder_run: null, transport: null, address: null, detail: null },
|
||
{ seq: 2, message: id(101), at: '2026-10-06T13:52:03Z', op: 'delivered', holder_run: null, transport: 'discord-dm', address: 'dm', detail: null },
|
||
{ seq: 3, message: id(102), at: plus('2026-10-06T09:42:01Z', 1), op: 'routed', holder_run: null, transport: null, address: null, detail: null },
|
||
{ seq: 4, message: id(102), at: '2026-10-06T09:42:02Z', op: 'delivered', holder_run: null, transport: 'discord-dm', address: 'dm', detail: null },
|
||
...asks.flatMap(([n, at]) => [
|
||
{ seq: 0, message: id(200 + n), at: plus(at, 1), op: 'routed', ...PM, transport: 'pi', detail: null },
|
||
{ seq: 0, message: id(200 + n), at: later(at, 15), op: 'delivered', ...PM, transport: 'broker', detail: null },
|
||
{ seq: 0, message: id(200 + n), at: later(at, 40), op: 'read', ...PM, transport: 'pi', detail: null },
|
||
]),
|
||
];
|
||
|
||
// A stand-in digest: 64 hex characters from the ref and time (FNV-1a, eight rounds). Not the real field hash.
|
||
const hex = str => Array.from({ length: 8 }, (_, k) => { let h = 0x811c9dc5 ^ k; for (const c of str) h = Math.imul(h ^ c.charCodeAt(0), 0x01000193); return (h >>> 0).toString(16).padStart(8, '0'); }).join('');
|
||
const snap = (seq, at, ref, f, src, extra = {}) => ({ seq, at, business: B, task_ref: ref, updated: extra.updated || at, etag: null, digest: extra.digest || hex(ref + at), fields: JSON.stringify(f), source: src, via: extra.via || null, read_at: extra.read_at || null, role: src === 'self' ? (extra.role || 'pm') : null, run: src === 'self' ? (extra.run || 'run-pm-7f31') : null });
|
||
const T = (index, title, bucket, o = {}) => ({ project_id: 3, index, title, description: `…\nRequirement: ${o.req || ''}`, done: bucket === 25 ? 1 : 0, due_date: o.due || null, priority: o.prio ?? 2, percent_done: o.pct ?? 0, bucket, labels: o.labels || [], assignees: o.who ? [o.who] : [] });
|
||
const task_snapshots = [
|
||
snap(1, '2026-10-06T08:30:00Z', 'vikunja:3/45', T(45, 'Install runbook for slice 1 identities', 21, { req: 'REQ-CRED-1', who: 7 }), 'self'),
|
||
snap(2, '2026-10-06T09:41:00Z', 'vikunja:3/41', T(41, 'Bus store over node:sqlite', 21, { req: 'REQ-DEC-1', who: 9, prio: 4 }), 'self'),
|
||
snap(3, '2026-10-06T09:50:00Z', 'vikunja:3/42', T(42, 'Broker read verbs for the CLI and the WebUI', 21, { req: 'REQ-WEB-1', who: 9, prio: 3 }), 'self'),
|
||
snap(4, '2026-10-06T09:52:00Z', 'vikunja:3/43', T(43, '`mosaic inbox` and `mosaic decide`', 21, { req: 'REQ-CLI-1', who: 9, due: '2026-10-09' }), 'self'),
|
||
snap(5, '2026-10-06T09:55:00Z', 'vikunja:3/44', T(44, 'Vikunja poller: board read plus cursor read', 21, { req: 'REQ-TASK-2', who: 9, prio: 3 }), 'self'),
|
||
snap(6, '2026-10-06T10:20:00Z', 'vikunja:3/48', T(48, 'Field table checks in the task verbs', 22, { req: 'REQ-TASK-1', who: 9 }), 'self', { role: 'coder', run: 'run-coder-a442' }),
|
||
snap(7, '2026-10-06T11:05:00Z', 'vikunja:3/41', T(41, 'Bus store over node:sqlite', 23, { req: 'REQ-DEC-1', who: 9, prio: 4, pct: 90 }), 'self', { role: 'coder', run: 'run-coder-a442' }),
|
||
// A move between open columns leaves Vikunja's `updated` alone (probe P3), so this self row keeps 09:50.
|
||
snap(8, '2026-10-06T14:59:40Z', 'vikunja:3/42', T(42, 'Broker read verbs for the CLI and the WebUI', 22, { req: 'REQ-WEB-1', who: 9, prio: 3 }), 'self', { role: 'coder', run: 'run-coder-a442', updated: '2026-10-06T09:50:00Z' }),
|
||
// A board read sent before that move finished and recorded after it. Its digest differs from the latest
|
||
// row, so the broker records it (addendum B section 5); task_external_changes ignores it (read_at rule).
|
||
snap(8.5, '2026-10-06T14:59:41Z', 'vikunja:3/42', T(42, 'Broker read verbs for the CLI and the WebUI', 21, { req: 'REQ-WEB-1', who: 9, prio: 3 }), 'poll', { via: 'board', read_at: '2026-10-06T14:59:39Z', updated: '2026-10-06T09:50:00Z', digest: '0003'.repeat(16) }),
|
||
snap(9, '2026-10-06T12:15:00Z', 'vikunja:3/45', T(45, 'Install runbook for slice 1 identities', 25, { req: 'REQ-CRED-1', who: 7, pct: 100 }), 'self'),
|
||
snap(10, '2026-10-06T12:30:11Z', 'vikunja:3/46', { project_id: 3, index: 46, title: 'Check the digest time against Central daylight saving', description: '', done: 0, due_date: null, priority: 1, percent_done: 0, bucket: 21, labels: [], assignees: [] }, 'poll', { via: 'cursor', read_at: '2026-10-06T12:30:10Z' }),
|
||
snap(11, '2026-10-06T13:50:00Z', 'vikunja:3/44', T(44, 'Vikunja poller: board read plus cursor read', 24, { req: 'REQ-TASK-2', who: 9, prio: 3 }), 'self', { role: 'coder', run: 'run-coder-a442' }),
|
||
snap(12, '2026-10-06T14:20:00Z', 'vikunja:3/47', { gone: 'not-found' }, 'poll', { via: 'task', read_at: '2026-10-06T14:19:59Z' }),
|
||
snap(13, '2026-10-06T14:31:12Z', 'vikunja:3/48', T(48, 'Field table checks in the task verbs', 25, { req: 'REQ-TASK-1', who: 9 }), 'poll', { via: 'cursor', read_at: '2026-10-06T14:31:11Z', updated: '2026-10-06T14:31:05Z' }),
|
||
snap(14, '2026-10-06T14:44:00Z', 'vikunja:3/43', T(43, '`mosaic inbox` and `mosaic decide`', 22, { req: 'REQ-CLI-1', who: 9, due: '2026-10-09' }), 'poll', { via: 'board', read_at: '2026-10-06T14:43:59Z', updated: '2026-10-06T09:52:00Z' }),
|
||
];
|
||
// 3/47 had an earlier self snapshot; it's gone now.
|
||
task_snapshots.splice(5, 0, snap(0.5, '2026-10-06T10:00:00Z', 'vikunja:3/47', T(47, 'Spike: webhook receiver (dropped)', 21, { req: 'REQ-TASK-2', who: 7 }), 'self'));
|
||
|
||
// Event shapes. Rows the broker writes follow it exactly: human.input (instruction, answer, admin),
|
||
// action.allowed (decision.raise context, authorize, agent message.send), action.refused ({code}),
|
||
// launch.revoked/restored ({via}), session.launched (bindLaunch: the launched run is actor and
|
||
// subject). Nothing writes task.*, review.*, session.ended or credential.* yet; those keep the shapes
|
||
// from SLICE1-VIEWS.md, with the task ref in `subject` (schema v3a onward) and a session.ended shaped
|
||
// like session.launched.
|
||
const ev = (seq, at, kind, actor_role, actor_run, subject, body) => ({ seq, id: id(500 + seq), at, business: B, kind, actor_role, actor_run, subject, corrects: null, body: JSON.stringify(body) });
|
||
const launched = (seq, at, role, run, harness, address = null) => ev(seq, at, 'session.launched', role, run, run, { harness, address, pid: null, startTime: null });
|
||
const raise = (seq, d, action, target, approvalChoice) => { const r = decisions.find(x => x.id === d); return ev(seq, plus(r.at, 1), 'action.allowed', r.raised_by_role, r.raised_by_run, r.task_ref || d, { decision: d, action, operation: 'decision.raise', task_ref: r.task_ref, target, approvalChoice, holderRun: null }); };
|
||
const answer = (seq, at, decision, op, choice = null) => ev(seq, plus(at, 1), 'human.input', null, null, decision, { kind: 'answer', decision, op, choice });
|
||
const events = [
|
||
launched(1, '2026-10-06T08:00:00Z', 'pm', 'run-pm-7f31', 'pi'),
|
||
launched(2, '2026-10-06T08:04:00Z', 'cto', 'run-cto-2b90', 'claude-code', 't3:5c1e9a0d'),
|
||
...asks.map(([n, at]) => ev(n, plus(at, 2), 'human.input', null, null, id(200 + n), { kind: 'instruction', message: id(200 + n) })),
|
||
ev(4, '2026-10-06T08:30:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/45', { request: id(503), requirement: 'REQ-CRED-1', index: 45 }),
|
||
ev(6, '2026-10-06T09:41:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/41', { request: id(505), requirement: 'REQ-DEC-1', index: 41 }),
|
||
ev(7, '2026-10-06T09:41:01Z', 'task.assigned', 'pm', 'run-pm-7f31', 'vikunja:3/41', { role: 'coder' }),
|
||
raise(40, D.merged, 'role.launch', 'coder', 'yes'),
|
||
// S2c (d27042fa): an agent message.send writes action.allowed first; within-role, so no decision in it.
|
||
ev(50, plus('2026-10-06T09:42:01Z', -1), 'action.allowed', 'pm', 'run-pm-7f31', 'human', { action: 'message.send', class: 'within-role', target: 'human' }),
|
||
answer(37, '2026-10-06T09:44:10Z', D.merged, 'seen'),
|
||
answer(8, '2026-10-06T09:44:31Z', D.merged, 'resolved', 'yes'),
|
||
// The launcher checks the resolved decision (authorize), then S6 binds the launch record.
|
||
ev(46, '2026-10-06T09:45:00Z', 'action.allowed', 'pm', 'run-pm-7f31', 'coder', { action: 'role.launch', class: 'gated', decision: D.merged, target: 'coder' }),
|
||
launched(9, '2026-10-06T09:45:02Z', 'coder', 'run-coder-a442', 'pi'),
|
||
ev(10, '2026-10-06T09:50:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/42', { request: id(535), requirement: 'REQ-WEB-1', index: 42 }),
|
||
ev(11, '2026-10-06T09:52:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/43', { request: id(535), requirement: 'REQ-CLI-1', index: 43 }),
|
||
ev(13, '2026-10-06T09:55:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/44', { request: id(512), requirement: 'REQ-TASK-2', index: 44 }),
|
||
ev(14, '2026-10-06T09:55:01Z', 'task.assigned', 'pm', 'run-pm-7f31', 'vikunja:3/44', { role: 'coder' }),
|
||
ev(15, '2026-10-06T10:00:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/47', { request: id(512), requirement: 'REQ-TASK-2', index: 47 }),
|
||
raise(41, D.prdOld, 'prd.approve', 'REQ-TASK-2', 'a'),
|
||
ev(16, '2026-10-06T10:19:00Z', 'task.created', 'pm', 'run-pm-7f31', 'vikunja:3/48', { request: id(536), requirement: 'REQ-TASK-1', index: 48 }),
|
||
ev(17, '2026-10-06T10:20:00Z', 'task.state', 'coder', 'run-coder-a442', 'vikunja:3/48', { from: 'todo', to: 'in-progress' }),
|
||
raise(42, D.prd, 'prd.approve', 'REQ-TASK-2', 'a'),
|
||
ev(18, '2026-10-06T11:05:00Z', 'task.state', 'coder', 'run-coder-a442', 'vikunja:3/41', { from: 'todo', to: 'in-review' }),
|
||
ev(19, '2026-10-06T11:05:02Z', 'review.requested', 'coder', 'run-coder-a442', 'vikunja:3/41', { row: 37, round: 1 }),
|
||
launched(20, '2026-10-06T11:19:00Z', 'reviewer', 'run-rev-0c18', 'pi'),
|
||
ev(21, '2026-10-06T14:59:40Z', 'task.state', 'coder', 'run-coder-a442', 'vikunja:3/42', { from: 'todo', to: 'in-progress' }),
|
||
ev(22, '2026-10-06T12:15:00Z', 'task.closed', 'pm', 'run-pm-7f31', 'vikunja:3/45', { verdict: 'approve' }),
|
||
ev(23, '2026-10-06T12:30:11Z', 'task.changed.external', null, null, 'vikunja:3/46', { fields: ['created'] }),
|
||
ev(24, '2026-10-06T12:40:00Z', 'review.verdict', 'reviewer', 'run-rev-0c18', 'vikunja:3/41', { row: 37, round: 1, verdict: 'approve' }),
|
||
ev(25, '2026-10-06T12:41:30Z', 'session.ended', 'reviewer', 'run-rev-0c18', 'run-rev-0c18', { exit: 0, reason: 'verdict posted' }),
|
||
ev(26, '2026-10-06T13:10:00Z', 'launch.revoked', null, null, null, { via: 'cli' }),
|
||
ev(47, plus('2026-10-06T13:10:00Z', 1), 'human.input', null, null, null, { kind: 'admin', event: id(526) }),
|
||
ev(27, '2026-10-06T13:25:00Z', 'launch.restored', null, null, null, { via: 'cli' }),
|
||
ev(48, plus('2026-10-06T13:25:00Z', 1), 'human.input', null, null, null, { kind: 'admin', event: id(527) }),
|
||
ev(28, '2026-10-06T13:50:00Z', 'task.state', 'coder', 'run-coder-a442', 'vikunja:3/44', { from: 'todo', to: 'blocked' }),
|
||
ev(29, '2026-10-06T13:51:00Z', 'action.refused', 'pm', 'run-pm-7f31', 'vikunja:3/44', { code: 'decision-required' }),
|
||
raise(43, D.launch, 'role.launch', 'coder', 'yes'),
|
||
ev(49, plus('2026-10-06T13:52:01Z', -1), 'action.allowed', 'pm', 'run-pm-7f31', 'human', { action: 'message.send', class: 'within-role', target: 'human' }),
|
||
answer(38, '2026-10-06T13:58:02Z', D.launch, 'seen'),
|
||
ev(30, '2026-10-06T14:00:00Z', 'credential.expiring', null, null, null, { service: 'vikunja', instance: 'coder', expires: '2026-10-13' }),
|
||
ev(31, '2026-10-06T14:20:00Z', 'task.missing', null, null, 'vikunja:3/47', { reason: 'not-found' }),
|
||
ev(32, '2026-10-06T14:31:12Z', 'task.changed.external', null, null, 'vikunja:3/48', { fields: ['done', 'bucket'] }),
|
||
ev(33, '2026-10-06T14:31:13Z', 'task.conflict', null, null, 'vikunja:3/48', { field: 'state', queue: 'in-review', tracker: 'done', decision: D.conflict }),
|
||
// Recommended and approving choices differ here: choosing "queue" authorizes nothing.
|
||
raise(44, D.conflict, 'task.close', 'vikunja:3/48', 'vikunja'),
|
||
raise(45, D.scope, 'task.scope.change', 'vikunja:3/42', 'add'),
|
||
ev(34, '2026-10-06T14:44:00Z', 'task.changed.external', null, null, 'vikunja:3/43', { fields: ['bucket'] }),
|
||
];
|
||
|
||
// The broker writes times as toISOString(), with milliseconds; its Store refuses any other form.
|
||
const ms = t => t && t.length === 20 ? t.replace('Z', '.000Z') : t;
|
||
for (const t of [role_claims, decisions, decision_events, messages, deliveries, task_snapshots, events]) for (const r of t) { r.at = ms(r.at); if ('read_at' in r) r.read_at = ms(r.read_at); }
|
||
// seq follows insertion order, which is time order; the verify script checks this against the schema.
|
||
for (const t of [messages, deliveries, task_snapshots, events]) t.sort((a, b) => a.at.localeCompare(b.at)).forEach((r, i) => { r.seq = i + 1; });
|
||
|
||
return { NOW, lookups, role_claims, decisions, decision_events, messages, deliveries, task_snapshots, events };
|
||
})();
|