Files
jason.woltje 9b7bf11afb feat(tasks): U4a, a task store built into each organization (#1873)
PRD 1.20 REQ-TASK-1, REQ-PLUG-1, REQ-WS-8. Each organization keeps its
tasks in its own bus through the new task-tracker plug-in kind. The
default module is the built-in store; Vikunja is the optional module
and a Vikunja organization keeps today's path.

- bus schema 3u to 3v (migration 3u-3v.sql): store_tasks, append-only,
  one row per write, each citing its events row; seven guard triggers.
- broker: the eight role verbs on the store with the slice 1 field
  table checked twice (module, then a before/after field diff); the
  person's tasks.create/edit/archive/unarchive/delete as human.input
  events; the tasks read returns store rows in board order (group,
  position), filtered and paged like tracker rows.
- runtime: each business gets its module at start (organization file
  plugin, else vikunja with a tracker entry, else store); a store
  business with a Vikunja tracker refuses the boot (tracker-config).
- plugins: task-tracker kind with store and vikunja modules.
- cli: the launcher's tracker flag covers store organizations; trail
  prints store writes.
- pins: schema.test counts, the bus migration tests, stack-release #811
  and T46 (store_tasks on the always list; COUNTED unchanged),
  budget-supervisor accepts 3v.

Rebased onto SYS1 by the lead: SYS1 took 3t-3u, so this step is 3u-3v.
2026-11-09 19:25:44 -06:00

51 lines
3.3 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { formatAgents, formatInbox, formatTasks, formatTrail } from "../src/format.mjs";
test("empty views say so", () => {
assert.equal(formatInbox([]), "inbox: empty\n");
assert.equal(formatAgents([]), "agents: no role is claimed\n");
assert.equal(formatTasks([]), "tasks: none\n");
});
// Row 277 (#1782, C2): the held thread claims follow the role lines, each marked `thread`.
test("agents adds a thread line for each held thread claim, after the roles", () => {
const roles = [{ role: "coder", holder_run: "coder-run", harness: "pi", at: "2026-10-22T05:00:00Z" }];
const threads = [{ thread: "t-1", instance: "assistant", holder_run: "assistant-run", harness: "pi", at: "2026-10-22T05:01:00Z" }];
assert.equal(
formatAgents(roles, threads),
"coder coder-run pi since 2026-10-22T05:00:00Z\nthread t-1 assistant assistant-run pi since 2026-10-22T05:01:00Z\n",
);
assert.equal(formatAgents([], threads), "agents: no role is claimed\nthread t-1 assistant assistant-run pi since 2026-10-22T05:01:00Z\n");
assert.equal(formatAgents(roles), formatAgents(roles, []));
});
test("the trail keeps the broker's order and names a decision's task without its rows", () => {
const rows = [
{ at: "2026-10-08T10:00:00Z", table: "decisions", seq: 4, id: "d-1", class: "gated", action: "deploy", route_to: "human", blocking: 1, question: "Ship?", task_ref: "vikunja:1/7" },
{ at: "2026-10-08T09:00:00Z", table: "events", seq: 2, kind: "decision.raised", actor_role: "coder" },
{ at: "2026-10-08T11:00:00Z", table: "decision_events", seq: 1, op: "resolved", choice: "yes", by: "jason", via: "cli" },
];
const lines = formatTrail("d-1", rows).trimEnd().split("\n");
assert.deepEqual(lines.slice(1, 4).map((l) => l.split(" ")[1]), ["decisions#4", "events#2", "decision_events#1"]);
assert.match(lines[1], /gated deploy → human \(blocking\): Ship\?/);
assert.match(lines[3], /resolved choice yes by jason via cli/);
assert.deepEqual(lines.slice(-2), ["task: vikunja:1/7", "follow with: mosaic trail vikunja:1/7"]);
assert.doesNotMatch(formatTrail("vikunja:1/7", rows), /follow with/);
});
test("tasks print the tracker fields the snapshot carries", () => {
assert.equal(formatTasks([{ task_ref: "vikunja:1/7", fields: { title: "Build S4", done: false } }]), "vikunja:1/7 open Build S4\n");
});
test("U4a: store tasks print like tracker tasks, and their trail rows name the write", () => {
assert.equal(formatTasks([{ task_ref: "task:3", source: "store", fields: { title: "Plan", state: "in-progress", done: false } }]), "task:3 in-progress Plan\n");
const rows = [
{ at: "2026-11-09T10:00:00Z", table: "store_tasks", seq: 1, op: "create", verb: "task.create", revision: 1, by_role: "pm", by_human: null, fields: { title: "Plan" } },
{ at: "2026-11-09T11:00:00Z", table: "store_tasks", seq: 2, op: "archive", verb: "tasks.archive", revision: 2, by_role: null, by_human: "jason", fields: { title: "Plan" } },
];
const lines = formatTrail("task:3", rows).trimEnd().split("\n");
assert.equal(lines[1], "2026-11-09T10:00:00Z store_tasks#1 create task.create r1 by pm Plan");
assert.equal(lines[2], "2026-11-09T11:00:00Z store_tasks#2 archive tasks.archive r2 by jason Plan");
});