row 271 (264d, #1776): schema step 3l to 3m, session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274)
This commit is contained in:
1 parent
82ff4ca105
commit
74b5e36386
4 files changed
+95
-5
No files matched your search
@@ -29,7 +29,12 @@ Answer, the operation envelope:
|
|||||||
"state": "clearing", "start": null, "refusal": null, "at": "..." }
|
"state": "clearing", "start": null, "refusal": null, "at": "..." }
|
||||||
```
|
```
|
||||||
|
|
||||||
- `successor` is bound when the request is accepted, before any asynchronous work.
|
- `successor` is bound when the request is accepted, before any asynchronous work. It is a
|
||||||
|
lowercase UUID, `xxxxxxxx-xxxx-8xxx-Vxxx-xxxxxxxxxxxx` with V one of `8`, `9`, `a`, `b` (RFC 9562
|
||||||
|
version 8, variant 10). It's the first 32 hex digits of sha256(business, thread, old session, op)
|
||||||
|
with those two nibbles set. It matches the UUID test every session selector uses
|
||||||
|
(cli/src/thread.mjs:41), so 264a's selectors and deep links take it unchanged (Huey's question,
|
||||||
|
gizmoduck-0274).
|
||||||
- `state`:
|
- `state`:
|
||||||
- `clearing`: accepted. Sends and launches to the old session are fenced, and the drain or stop
|
- `clearing`: accepted. Sends and launches to the old session are fenced, and the drain or stop
|
||||||
is in progress.
|
is in progress.
|
||||||
@@ -139,8 +144,9 @@ alone can't show that no child is running.
|
|||||||
- Check owner and grant (the `session.create` path), the revision, and that processing isn't
|
- Check owner and grant (the `session.create` path), the revision, and that processing isn't
|
||||||
halted.
|
halted.
|
||||||
- Bind the successor before any async work:
|
- Bind the successor before any async work:
|
||||||
- The id is deterministic, from a sha256 of (business, thread, old session, op), shaped to
|
- The id is deterministic, from a sha256 of (business, thread, old session, op), shaped as a
|
||||||
`ID_PATTERN` (vocabulary.mjs:199).
|
lowercase version 8 UUID (the contract above). 264a's ids are UUIDs, and every selector
|
||||||
|
checks the UUID shape, so draft 2's `ID_PATTERN` shape is replaced.
|
||||||
- The name is `<old name>-N`, with N the next free number in that thread. If a user already
|
- The name is `<old name>-N`, with N the next free number in that thread. If a user already
|
||||||
made a session called `<old>-N`, the next free N is taken.
|
made a session called `<old>-N`, the next free N is taken.
|
||||||
- Both go on the `requested` row, so a repeat of the op returns the same id and name.
|
- Both go on the `requested` row, so a repeat of the op returns the same id and name.
|
||||||
|
|||||||
@@ -478,3 +478,40 @@ CREATE TABLE gitea_grants (
|
|||||||
PRIMARY KEY (business, agent),
|
PRIMARY KEY (business, agent),
|
||||||
FOREIGN KEY (business, agent) REFERENCES gitea_users(business, agent)
|
FOREIGN KEY (business, agent) REFERENCES gitea_users(business, agent)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
CREATE TABLE session_ops (
|
||||||
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
at TEXT NOT NULL,
|
||||||
|
business TEXT NOT NULL,
|
||||||
|
thread TEXT NOT NULL REFERENCES threads(id),
|
||||||
|
session TEXT NOT NULL REFERENCES sessions(id),
|
||||||
|
op TEXT NOT NULL,
|
||||||
|
stage TEXT NOT NULL CHECK (stage IN ('requested','drained','uncertain','exited','retired','failed','cancelled','start-held','start-started','start-refused')),
|
||||||
|
attempt INTEGER NOT NULL CHECK (CASE WHEN stage GLOB 'start-*' THEN attempt >= 1 ELSE attempt = 0 END),
|
||||||
|
work_attempt INTEGER CHECK (work_attempt IS NULL OR (stage GLOB 'start-*' AND work_attempt >= 1)),
|
||||||
|
successor TEXT NOT NULL,
|
||||||
|
successor_name TEXT NOT NULL,
|
||||||
|
run TEXT,
|
||||||
|
generation INTEGER CHECK (generation IS NULL OR generation >= 1),
|
||||||
|
digest TEXT,
|
||||||
|
refusal TEXT,
|
||||||
|
UNIQUE (thread, op, stage, attempt)
|
||||||
|
) STRICT;
|
||||||
|
CREATE TABLE message_cancels (
|
||||||
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
at TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL UNIQUE REFERENCES messages(id),
|
||||||
|
session TEXT NOT NULL REFERENCES sessions(id),
|
||||||
|
op TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX session_ops_session ON session_ops (session, seq);
|
||||||
|
CREATE TRIGGER session_ops_no_update BEFORE UPDATE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_no_delete BEFORE DELETE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_no_replace BEFORE INSERT ON session_ops WHEN EXISTS (SELECT 1 FROM session_ops WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_one_open BEFORE INSERT ON session_ops
|
||||||
|
WHEN NEW.stage = 'requested' AND EXISTS (SELECT 1 FROM session_ops o WHERE o.session = NEW.session
|
||||||
|
AND o.seq = (SELECT max(seq) FROM session_ops WHERE thread = o.thread AND op = o.op)
|
||||||
|
AND o.stage IN ('requested','drained','uncertain','exited'))
|
||||||
|
BEGIN SELECT RAISE(ABORT, 'session has an open clear'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_update BEFORE UPDATE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_delete BEFORE DELETE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_replace BEFORE INSERT ON message_cancels WHEN EXISTS (SELECT 1 FROM message_cancels WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
-- Row 271 (264d, #1776, T1 item 7 D7/D8): clear context. session_ops is the clear journal, one row
|
||||||
|
-- per stage, and message_cancels holds a receipt for each message a clear cancelled. Only new
|
||||||
|
-- tables, an index and their triggers.
|
||||||
|
CREATE TABLE session_ops (
|
||||||
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
at TEXT NOT NULL,
|
||||||
|
business TEXT NOT NULL,
|
||||||
|
thread TEXT NOT NULL REFERENCES threads(id),
|
||||||
|
session TEXT NOT NULL REFERENCES sessions(id),
|
||||||
|
op TEXT NOT NULL,
|
||||||
|
stage TEXT NOT NULL CHECK (stage IN ('requested','drained','uncertain','exited','retired','failed','cancelled','start-held','start-started','start-refused')),
|
||||||
|
attempt INTEGER NOT NULL CHECK (CASE WHEN stage GLOB 'start-*' THEN attempt >= 1 ELSE attempt = 0 END),
|
||||||
|
work_attempt INTEGER CHECK (work_attempt IS NULL OR (stage GLOB 'start-*' AND work_attempt >= 1)),
|
||||||
|
successor TEXT NOT NULL,
|
||||||
|
successor_name TEXT NOT NULL,
|
||||||
|
run TEXT,
|
||||||
|
generation INTEGER CHECK (generation IS NULL OR generation >= 1),
|
||||||
|
digest TEXT,
|
||||||
|
refusal TEXT,
|
||||||
|
UNIQUE (thread, op, stage, attempt)
|
||||||
|
) STRICT;
|
||||||
|
CREATE TABLE message_cancels (
|
||||||
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
at TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL UNIQUE REFERENCES messages(id),
|
||||||
|
session TEXT NOT NULL REFERENCES sessions(id),
|
||||||
|
op TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
CREATE INDEX session_ops_session ON session_ops (session, seq);
|
||||||
|
CREATE TRIGGER session_ops_no_update BEFORE UPDATE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_no_delete BEFORE DELETE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_no_replace BEFORE INSERT ON session_ops WHEN EXISTS (SELECT 1 FROM session_ops WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
|
||||||
|
CREATE TRIGGER session_ops_one_open BEFORE INSERT ON session_ops
|
||||||
|
WHEN NEW.stage = 'requested' AND EXISTS (SELECT 1 FROM session_ops o WHERE o.session = NEW.session
|
||||||
|
AND o.seq = (SELECT max(seq) FROM session_ops WHERE thread = o.thread AND op = o.op)
|
||||||
|
AND o.stage IN ('requested','drained','uncertain','exited'))
|
||||||
|
BEGIN SELECT RAISE(ABORT, 'session has an open clear'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_update BEFORE UPDATE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_delete BEFORE DELETE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
|
CREATE TRIGGER message_cancels_no_replace BEFORE INSERT ON message_cancels WHEN EXISTS (SELECT 1 FROM message_cancels WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
|
||||||
@@ -13,8 +13,8 @@ import { join, isAbsolute } from 'node:path';
|
|||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
|
|
||||||
const ddl = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8');
|
const ddl = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8');
|
||||||
export const SCHEMA_SHA256 = 'cd68512212ce8bf8f5631138b2e63a340fa7b940088430649bb7784af7df162a';
|
export const SCHEMA_SHA256 = '129a76db0fe9973cdea5a87f274941b4111c86730fba8a0cbe16e61a534525d0';
|
||||||
export const SCHEMA_VERSION = '3l';
|
export const SCHEMA_VERSION = '3m';
|
||||||
// Ordered, named offline steps. New schema changes append a step and keep prior DDL pinned.
|
// Ordered, named offline steps. New schema changes append a step and keep prior DDL pinned.
|
||||||
const legacyDDL = readFileSync(new URL('./migrations/3b.sql', import.meta.url), 'utf8');
|
const legacyDDL = readFileSync(new URL('./migrations/3b.sql', import.meta.url), 'utf8');
|
||||||
const LEGACY_SHA256 = '179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65';
|
const LEGACY_SHA256 = '179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65';
|
||||||
@@ -72,6 +72,11 @@ const migrations = [{
|
|||||||
from: '3k',
|
from: '3k',
|
||||||
to: '3l',
|
to: '3l',
|
||||||
sql: readFileSync(new URL('./migrations/3k-3l.sql', import.meta.url), 'utf8'),
|
sql: readFileSync(new URL('./migrations/3k-3l.sql', import.meta.url), 'utf8'),
|
||||||
|
}, {
|
||||||
|
// Row 271 (#1776): clear context. The clear journal and the cancelled-message receipts. Only new tables.
|
||||||
|
from: '3l',
|
||||||
|
to: '3m',
|
||||||
|
sql: readFileSync(new URL('./migrations/3l-3m.sql', import.meta.url), 'utf8'),
|
||||||
}];
|
}];
|
||||||
const timedTables = [
|
const timedTables = [
|
||||||
'events',
|
'events',
|
||||||
@@ -89,6 +94,8 @@ const timedTables = [
|
|||||||
'thread_opens',
|
'thread_opens',
|
||||||
'instance_names',
|
'instance_names',
|
||||||
'usage_records',
|
'usage_records',
|
||||||
|
'session_ops',
|
||||||
|
'message_cancels',
|
||||||
];
|
];
|
||||||
const hex = (value) => createHash('sha256').update(value).digest('hex');
|
const hex = (value) => createHash('sha256').update(value).digest('hex');
|
||||||
if (hex(ddl) !== SCHEMA_SHA256) throw Error('schema-source-mismatch');
|
if (hex(ddl) !== SCHEMA_SHA256) throw Error('schema-source-mismatch');
|
||||||
|
|||||||
Reference in new issue
Block a user