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

76 lines
3.8 KiB
Markdown

# 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.