Files
stack/agents/darkwing/work/slice1-proto/proto-v3-notes.md
T
jason.woltjeandClaude Opus 5.5 7ed831781b docs(slice1): schema v3 prototype, addendum B section 5
task_snapshots gains via and read_at, an integer-bucket CHECK with a
tombstone exception, the read-ordering rule in task_external_changes and
the tasks_open view. task.missing names its task and a reason. Prototype
rerun on Node 24.21.0 and 26.8.1; a mutant without the read_at rule
reports the broker's own move as external. Lead decision 52 (B3).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-04 21:55:17 -05:00

3.8 KiB

Slice 1 prototype, v3 (addendum B section 5, lead decision 52)

Darkwing, 2026-10-04, for the slice 1 brief (docs/plans/2026-10-04_slice-1.md, row S2 starts from this schema). The v1 and v2 files are unchanged. schema-v3.sql is a full schema that stands on its own and replaces v2. It isn't a migration.

What changed from schema-v2.sql. diff schema-v2.sql schema-v3.sql shows all of it:

  • task_snapshots.via: which read produced a poll snapshot, one of board (the open-task kanban listing), cursor (the updated cursor), task (a single GET after a task left the open set) or reconcile.
  • task_snapshots.read_at: when the broker sent that read. A poll row needs both via and read_at, and a self row has neither. For a self row, at is when the write's response arrived.
  • Every snapshot carries an integer fields.bucket. The one exception is a tombstone, a poll row with via task and a text fields.gone. The CHECK uses IS, not =: a missing JSON path makes json_type NULL, and SQLite passes a CHECK that evaluates to NULL. Addendum B section 5 records how I found that.
  • task_external_changes adds p.read_at > ls.at. A move between columns that aren't done doesn't change updated (probe P3), so updated alone can't order a board read against the broker's own move. The view also returns via.
  • New view tasks_open: tasks whose latest snapshot is open and not a tombstone. The poll compares the board read with it.
  • New trigger events_task_missing_body. A task.missing event names its task in subject and carries reason moved, not-found or no-access; moved also carries the new project as an integer. Addendum B section 5 gave the body. The trigger enforces it, the same way v2 enforces the credential.* body. It is the one addition beyond section 5's text.

proto-v3.mjs is proto-v2.mjs with two sections changed. The task.missing cases now include four refusals, and the task_snapshots section was rewritten for the new columns. Apart from the header comment, the temp directory prefix and the schema file name, the rest is unchanged.

Results: proto-v3-node24.txt (Node 24.21.0 in the node:24 image, no network, the directory mounted read-only) and proto-v3-node26.txt (Node 26.8.1 on the host). Both use SQLite 3.53.4, and the outputs differ only in the version line. Every refusal the script expects happens:

  • a poll row without via or read_at, a self row with via, an unknown via;
  • a snapshot with no bucket or a text bucket, a gone on a board read or on a self row;
  • a task.missing with no reason, an unknown reason, no subject, or moved without a project.

The views, in order:

  • an unchanged cursor read isn't external;
  • a person's edit in the same second as a write is external, via cursor;
  • a board read sent before the broker's move finished isn't external;
  • a stale cursor read isn't external;
  • a board read that agrees with the move isn't external;
  • a person's move with updated unchanged is external, via board;
  • a tombstone drops the task from tasks_open.

UPDATE, DELETE and INSERT OR REPLACE are refused on all eight tables. Dropping one guard and reopening gives MISMATCH.

Mutant: the same script against schema-v3.sql without AND p.read_at > ls.at reports the stale board read as an external change ([{"task_ref":"vikunja:3/41"}]). Run in ~/darkwing-scratch/v3mut, output in mutant.txt there.

Limits, the same as v1 and v2. The triggers catch our own bugs. A process running as the same user can still drop a trigger, and the digest check only notices that afterwards. The views are demonstrations. The broker runs its own queries, and the views show the rules in SQL. The digest's field list and the rule to write a poll row only when the digest changed are broker behaviour (addendum B section 5), so the schema can't check them.