Files
stack/agents/darkwing/work/slice1-proto/proto-v2.mjs
T
jason.woltjeandClaude Opus 5.5 f2b8c92a84 docs(plans): slice 1 prototype v2 records; lead decision 50
Darkwing's schema-v2 adds task_snapshots, decisions.blocking and a
closed events.kind list. Sage reran proto-v2 on Node 26 with matching
output. Decision 50 accepts the five choices beyond addendum A.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 14:59:33 -05:00

92 lines
8.7 KiB
JavaScript

// Slice 1 prototype, v2 schema (addendum A, item A3). Same pattern as proto.mjs.
import { DatabaseSync } from "node:sqlite";
import { readFileSync, mkdtempSync } from "node:fs";
import { join } from "node:path"; import { tmpdir } from "node:os";
import { createHash } from "node:crypto";
const f = join(mkdtempSync(join(tmpdir(), "s1v2-")), "bus.sqlite");
let db = new DatabaseSync(f, { timeout: 5000 });
db.exec(readFileSync(new URL("./schema-v2.sql", import.meta.url), "utf8"));
let t = 0; const now = () => new Date(Date.UTC(2026, 9, 4, 12, 0, t++)).toISOString();
const tryit = (label, fn) => { try { fn(); console.log("ok ", label); } catch (e) { console.log("refuse", label, "->", e.message.replace(/\s+/g, " ").slice(0, 90)); } };
const show = (label, sql) => console.log("view ", label, "->", JSON.stringify(db.prepare(sql).all()));
const hex = (s) => createHash("sha256").update(s).digest("hex");
const schemaDigest = (d) => hex(d.prepare("SELECT type, name, sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY type, name").all().map((r) => `${r.type}|${r.name}|${r.sql}`).join("\n"));
console.log("-- open-time schema check");
db.prepare("INSERT INTO meta (key, value) VALUES ('schema_digest', ?)").run(schemaDigest(db));
const check = () => db.prepare("SELECT value FROM meta WHERE key = 'schema_digest'").get().value === schemaDigest(db) ? "match" : "MISMATCH";
console.log("check ", "after create ->", check());
console.log("-- decisions.blocking");
const dec = db.prepare("INSERT INTO decisions (id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation,task_ref,blocking) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
const opts = JSON.stringify([{ key: "A", text: "rotate" }, { key: "B", text: "wait" }]);
tryit("raise without blocking", () => db.exec(`INSERT INTO decisions (id,at,business,raised_by_role,raised_by_run,class,action,route_to,question,options,recommendation) VALUES ('d-0','${now()}','mosaic-stack','coder','run-C','gated','credential.mint','human','?','${opts}','A')`));
tryit("raise with blocking 2", () => dec.run("d-1", now(), "mosaic-stack", "coder", "run-C", "gated", "credential.mint", "human", "Rotate?", opts, "A", "vikunja:3/41", 2));
tryit("raise blocking without task_ref", () => dec.run("d-2", now(), "mosaic-stack", "coder", "run-C", "gated", "credential.mint", "human", "Rotate?", opts, "A", null, 1));
tryit("raise blocking gated with task_ref", () => dec.run("d-3", now(), "mosaic-stack", "coder", "run-C", "gated", "credential.mint", "human", "Rotate coder vikunja token?", opts, "A", "vikunja:3/41", 1));
tryit("raise non-blocking gated", () => dec.run("d-4", now(), "mosaic-stack", "pm", "run-P", "gated", "deploy", "human", "Deploy?", opts, "B", null, 0));
tryit("raise blocking cross-role", () => dec.run("d-5", now(), "mosaic-stack", "coder", "run-C", "cross-role", "task.scope.change", "pm", "Widen scope?", opts, "B", "vikunja:3/41", 1));
show("urgent_inbox", "SELECT id, task_ref FROM urgent_inbox");
tryit("resolve d-3 with A", () => db.prepare("INSERT INTO decision_events (decision,at,op,by,choice,via) VALUES (?,?,?,?,?,?)").run("d-3", now(), "resolved", "jason", "A", "cli"));
show("urgent_inbox after resolve", "SELECT id FROM urgent_inbox");
console.log("-- events: closed kinds and the new kinds");
const ev = db.prepare("INSERT INTO events (id,at,business,kind,actor_role,actor_run,subject,body) VALUES (?,?,?,?,?,?,?,?)");
let n = 0; const e = (kind, role, run, body, subject = null) => ev.run(`e-${++n}`, now(), "mosaic-stack", kind, role, run, subject, JSON.stringify(body));
tryit("unknown kind task.deleted", () => e("task.deleted", "pm", "run-P", {}));
tryit("credential.expiring vikunja coder", () => e("credential.expiring", null, null, { service: "vikunja", instance: "coder", expires: "2026-10-11" }));
tryit("credential.expired vikunja coder", () => e("credential.expired", null, null, { service: "vikunja", instance: "coder", decision: "d-3" }));
tryit("credential.changed gitea pm", () => e("credential.changed", null, null, { service: "gitea", instance: "pm", stat: { inode: 1, size: 41 } }));
tryit("credential.changed without instance", () => e("credential.changed", null, null, { service: "gitea" }));
tryit("credential.expiring service github", () => e("credential.expiring", null, null, { service: "github", instance: "pm" }));
tryit("task.missing", () => e("task.missing", null, null, { reconcile: "r-1" }, "vikunja:3/40"));
tryit("digest.sent", () => e("digest.sent", null, null, { decisions: ["d-4"], transport: "discord-dm" }));
tryit("launch.revoked by pm run", () => e("launch.revoked", "pm", "run-P", {}));
tryit("launch.revoked by human", () => e("launch.revoked", null, null, { via: "cli" }));
show("launch_state", "SELECT business, state FROM launch_state");
tryit("launch.restored by human", () => e("launch.restored", null, null, { via: "cli" }));
show("launch_state", "SELECT business, state FROM launch_state");
console.log("-- task_snapshots");
const snap = db.prepare("INSERT INTO task_snapshots (at,business,task_ref,updated,etag,digest,fields,source,role,run) VALUES (?,?,?,?,?,?,?,?,?,?)");
const s = (ref, updated, fields, source, role = null, run = null) => snap.run(now(), "mosaic-stack", ref, updated, `"${hex(JSON.stringify(fields)).slice(0, 8)}"`, hex(JSON.stringify(fields)), JSON.stringify(fields), source, role, run);
const X = { title: "Add broker push", bucket: "in-progress" }, Y = { ...X, title: "Add broker push (Jason edit)" }, Z = { ...Y, bucket: "in-review" }, W = { title: "Add broker push", bucket: "todo" };
tryit("self without role and run", () => s("vikunja:3/41", "2026-10-04T12:00:00Z", X, "self"));
tryit("poll with a role", () => s("vikunja:3/41", "2026-10-04T12:00:00Z", X, "poll", "pm", "run-P"));
tryit("bad task_ref", () => s("PROJ-41", "2026-10-04T12:00:00Z", X, "poll"));
tryit("bad digest", () => snap.run(now(), "mosaic-stack", "vikunja:3/41", "x", null, "abc", "{}", "poll", null, null));
tryit("self X by coder, updated 12:00:00", () => s("vikunja:3/41", "2026-10-04T12:00:00Z", X, "self", "coder", "run-C"));
tryit("poll X (unchanged)", () => s("vikunja:3/41", "2026-10-04T12:00:00Z", X, "poll"));
show("external after poll X", "SELECT task_ref FROM task_external_changes");
tryit("poll Y, same second (person edit)", () => s("vikunja:3/41", "2026-10-04T12:00:00Z", Y, "poll"));
show("external after poll Y", "SELECT task_ref, seq FROM task_external_changes");
tryit("self Z by coder, updated 12:00:05", () => s("vikunja:3/41", "2026-10-04T12:00:05Z", Z, "self", "coder", "run-C"));
tryit("stale poll W, updated 11:59:00", () => s("vikunja:3/41", "2026-10-04T11:59:00Z", W, "poll"));
show("external after self Z, stale poll W", "SELECT task_ref FROM task_external_changes");
tryit("poll on vikunja:3/42 with no self row", () => s("vikunja:3/42", "2026-10-04T12:01:00Z", W, "poll"));
show("external, task 42", "SELECT task_ref FROM task_external_changes");
console.log("-- append-only on every table");
const keys = { meta: "key = 'schema_digest'", events: "id = 'e-2'", role_claims: "1", decisions: "id = 'd-3'", decision_events: "1", messages: "1", deliveries: "1", task_snapshots: "seq = 1" };
db.exec("INSERT INTO role_claims (at,business,role,op,holder_run,harness,by) VALUES ('x','mosaic-stack','pm','claim','run-P','pi','run-P')");
db.exec("INSERT INTO messages (id,at,business,from_role,from_run,to_role,class,decision,body) VALUES ('m-1','x','mosaic-stack','pm','run-P','human','RESULT','d-3','rotate')");
db.exec("INSERT INTO deliveries (message,at,op,transport) VALUES ('m-1','x','delivered','discord-dm')");
for (const [tbl, where] of Object.entries(keys)) {
const row = db.prepare(`SELECT * FROM ${tbl} WHERE ${where} LIMIT 1`).get();
const cols = Object.keys(row);
const col = cols.find((c) => !["seq", "id", "key"].includes(c));
tryit(`${tbl} UPDATE`, () => db.exec(`UPDATE ${tbl} SET ${col} = ${col} WHERE ${where}`));
tryit(`${tbl} DELETE`, () => db.exec(`DELETE FROM ${tbl} WHERE ${where}`));
tryit(`${tbl} INSERT OR REPLACE`, () => db.prepare(`INSERT OR REPLACE INTO ${tbl} (${cols.join(",")}) VALUES (${cols.map(() => "?").join(",")})`).run(...cols.map((c) => row[c])));
}
console.log("-- tamper: drop a guard, reopen");
db.close(); db = new DatabaseSync(f, { timeout: 5000 });
console.log("check ", "on reopen ->", check());
db.exec("DROP TRIGGER task_snapshots_no_update");
db.close(); db = new DatabaseSync(f, { timeout: 5000 });
console.log("check ", "after DROP TRIGGER ->", check());
const count = (type) => db.prepare("SELECT count(*) n FROM sqlite_master WHERE type = ?").get(type).n;
console.log("node", process.versions.node, "| sqlite", db.prepare("SELECT sqlite_version() v").get().v, "| journal:", db.prepare("PRAGMA journal_mode").get().journal_mode, "| tables:", count("table") - 1, "| triggers:", count("trigger"), "| views:", count("view"));