@danypops/papyrus 0.41.0 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -11
- package/package.json +2 -2
- package/src/adapters/sqlite-artifact-scope-store.ts +18 -9
- package/src/adapters/sqlite-artifact-store.ts +7 -5
- package/src/adapters/sqlite-discussion-round-store.ts +26 -17
- package/src/adapters/sqlite-gate-runner.ts +1 -1
- package/src/adapters/sqlite-graph-projection-store.ts +14 -10
- package/src/adapters/sqlite-log-store.ts +36 -17
- package/src/adapters/sqlite-note-event-store.ts +20 -16
- package/src/adapters/sqlite-session-identity-store.ts +13 -7
- package/src/adapters/sqlite-task-event-store.ts +29 -21
- package/src/adapters/sqlite-task-focus-store.ts +36 -10
- package/src/adapters/sqlite-task-lease-store.ts +12 -6
- package/src/adapters/sqlite-task-scope-store.ts +25 -14
- package/src/artifact-relationship-view.ts +4 -4
- package/src/artifact-subtree.ts +4 -2
- package/src/authority-registry.ts +2 -1
- package/src/cli.ts +794 -354
- package/src/client.ts +6 -3
- package/src/constants.ts +34 -56
- package/src/daemon-state.ts +4 -12
- package/src/daemon.ts +31 -9
- package/src/db.ts +153 -105
- package/src/discussion-service.ts +109 -44
- package/src/domain/artifact-event.ts +18 -5
- package/src/domain/artifact.ts +3 -1
- package/src/domain/blueprint-definition.ts +268 -0
- package/src/domain/checklist.ts +20 -17
- package/src/domain/discussion.ts +37 -18
- package/src/domain/gate.ts +7 -7
- package/src/domain/log-entry.ts +1 -1
- package/src/domain/note-event.ts +20 -7
- package/src/domain/task-event.ts +17 -7
- package/src/domain-services.ts +362 -288
- package/src/graph-projection-service.ts +34 -8
- package/src/id-migration.ts +17 -4
- package/src/index.ts +16 -11
- package/src/log-service.ts +6 -5
- package/src/log.ts +19 -0
- package/src/modules/discuss.ts +63 -28
- package/src/modules/docs.ts +74 -17
- package/src/modules/graph-projection.ts +20 -9
- package/src/modules/logs.ts +34 -22
- package/src/modules/notes.ts +66 -28
- package/src/modules/playbooks.ts +93 -32
- package/src/modules/rules.ts +57 -15
- package/src/modules/session-identity.ts +6 -2
- package/src/modules/tasks.ts +142 -67
- package/src/note-service.ts +11 -7
- package/src/ops.ts +134 -69
- package/src/playbook-definition.ts +124 -39
- package/src/playbook-execution.ts +18 -25
- package/src/ports/artifact-scope-store.ts +1 -1
- package/src/ports/note-event-store.ts +6 -4
- package/src/ports/task-event-store.ts +9 -6
- package/src/ports/task-focus-store.ts +17 -4
- package/src/ports/task-lease-store.ts +9 -4
- package/src/ports/task-scope-store.ts +3 -1
- package/src/service.ts +148 -110
- package/src/session-identity-service.ts +10 -2
- package/src/task-context.ts +28 -16
- package/src/task-execution.ts +4 -12
- package/src/task-graph-view.ts +12 -12
- package/src/task-relationship-view.ts +1 -3
- package/src/task-service.ts +168 -73
- package/src/vehicle/artifact-trash-vehicle.ts +26 -14
- package/src/vehicle/artifact-vehicle-shared.ts +32 -13
- package/src/vehicle/docs-vehicle.ts +50 -18
- package/src/vehicle/notes-vehicle.ts +26 -8
- package/src/vehicle/papyrus-vehicle.ts +16 -8
- package/src/vehicle/playbooks-vehicle.ts +88 -19
- package/src/vehicle/rules-vehicle.ts +58 -21
- package/src/vehicle/tasks-vehicle.ts +366 -54
- package/src/version.ts +1 -1
- package/src/workflow-execution.ts +198 -109
- package/src/domain/skill-definition.ts +0 -270
- package/src/modules/skills.ts +0 -158
- package/src/vehicle/skills-vehicle.ts +0 -194
package/src/db.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* db.ts — enforced-schema SQLite store for Papyrus.
|
|
3
|
-
* Dual-runtime: bun:sqlite (Bun) / node:sqlite (Node/pi host).
|
|
4
|
-
* Four kinds (doc/task/rule/skill) are FK-enforced; relations are universal (any→any).
|
|
5
|
-
*/
|
|
6
|
-
import { createHash } from "node:crypto";
|
|
7
|
-
import { createRequire } from "node:module";
|
|
8
1
|
import { mkdirSync } from "node:fs";
|
|
9
|
-
import {
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname } from "node:path";
|
|
10
4
|
import { runMigrations, type SqliteMigrationRunner } from "@danypops/vehicle-server/storage";
|
|
11
5
|
import { SQLITE_BUSY_TIMEOUT_MS, SQLITE_SCHEMA_VERSION } from "./constants.ts";
|
|
12
6
|
|
|
@@ -291,7 +285,6 @@ const SEED_SQL = `
|
|
|
291
285
|
INSERT OR IGNORE INTO kinds VALUES ('doc','Knowledge — what we know (specs, decisions, research, designs)');
|
|
292
286
|
INSERT OR IGNORE INTO kinds VALUES ('task','Work — what we are doing (objectives, steps, checklists)');
|
|
293
287
|
INSERT OR IGNORE INTO kinds VALUES ('rule','Governance — when doing X, follow Y');
|
|
294
|
-
INSERT OR IGNORE INTO kinds VALUES ('skill','Parameterized workflow bundle — inputs and templates load tasks, rules, and docs');
|
|
295
288
|
INSERT OR IGNORE INTO kinds VALUES ('playbook','Reusable procedure — a trigger and an ordered list of steps an agent reads and follows, not a mechanically instantiated blueprint');
|
|
296
289
|
INSERT OR IGNORE INTO statuses VALUES ('draft','doc');
|
|
297
290
|
INSERT OR IGNORE INTO statuses VALUES ('active','doc');
|
|
@@ -304,20 +297,18 @@ INSERT OR IGNORE INTO statuses VALUES ('done','task');
|
|
|
304
297
|
INSERT OR IGNORE INTO statuses VALUES ('canceled','task');
|
|
305
298
|
INSERT OR IGNORE INTO statuses VALUES ('active','rule');
|
|
306
299
|
INSERT OR IGNORE INTO statuses VALUES ('deprecated','rule');
|
|
307
|
-
INSERT OR IGNORE INTO statuses VALUES ('active','skill');
|
|
308
|
-
INSERT OR IGNORE INTO statuses VALUES ('deprecated','skill');
|
|
309
300
|
INSERT OR IGNORE INTO statuses VALUES ('active','playbook');
|
|
310
301
|
INSERT OR IGNORE INTO statuses VALUES ('deprecated','playbook');
|
|
311
302
|
INSERT OR IGNORE INTO relation_names VALUES ('references','Source material (doc→doc, doc→task, doc→rule)');
|
|
312
303
|
INSERT OR IGNORE INTO relation_names VALUES ('implements','This work satisfies that (task→doc, task→rule)');
|
|
313
|
-
INSERT OR IGNORE INTO relation_names VALUES ('follows','This work obeys that (task→rule, task→
|
|
314
|
-
INSERT OR IGNORE INTO relation_names VALUES ('depends_on','DAG ordering (task→task)');
|
|
315
|
-
INSERT OR IGNORE INTO relation_names VALUES ('documents','Describes (doc→task, doc→rule, doc→
|
|
304
|
+
INSERT OR IGNORE INTO relation_names VALUES ('follows','This work obeys that (task→rule, task→playbook)');
|
|
305
|
+
INSERT OR IGNORE INTO relation_names VALUES ('depends_on','DAG ordering (task→task, playbook→playbook)');
|
|
306
|
+
INSERT OR IGNORE INTO relation_names VALUES ('documents','Describes (doc→task, doc→rule, doc→playbook)');
|
|
316
307
|
INSERT OR IGNORE INTO relation_names VALUES ('blocks','Blocking relationship (task→task, or an active Discussion doc→task)');
|
|
317
308
|
INSERT OR IGNORE INTO relation_names VALUES ('supersedes','Replaces (doc→doc, rule→rule)');
|
|
318
309
|
INSERT OR IGNORE INTO relation_names VALUES ('relates_to','Catch-all (any→any)');
|
|
319
310
|
INSERT OR IGNORE INTO relation_names VALUES ('gates','This rule gates that task (rule→task)');
|
|
320
|
-
INSERT OR IGNORE INTO relation_names VALUES ('triggers','This
|
|
311
|
+
INSERT OR IGNORE INTO relation_names VALUES ('triggers','This playbook run applies to that work (playbook→task)');
|
|
321
312
|
INSERT OR IGNORE INTO relation_names VALUES ('contains','Parent contains a nested artifact (any→any)');
|
|
322
313
|
INSERT OR IGNORE INTO relation_names VALUES ('part_of','Artifact belongs to a parent artifact (any→any)');
|
|
323
314
|
CREATE INDEX IF NOT EXISTS edges_to_id_idx ON edges(to_id);
|
|
@@ -376,10 +367,22 @@ export function migrationLedger(db: Db): ModuleMigrationRow[] {
|
|
|
376
367
|
// has no ledger table yet -- "nothing recorded" is the correct answer, not an error.
|
|
377
368
|
const tableExists = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'module_migrations'").get() != null;
|
|
378
369
|
if (!tableExists) return [];
|
|
379
|
-
const rows = db
|
|
380
|
-
module_id
|
|
370
|
+
const rows = db
|
|
371
|
+
.prepare("SELECT module_id, version, name, checksum, applied_at FROM module_migrations ORDER BY module_id, version")
|
|
372
|
+
.all() as Array<{
|
|
373
|
+
module_id: string;
|
|
374
|
+
version: number;
|
|
375
|
+
name: string;
|
|
376
|
+
checksum: string;
|
|
377
|
+
applied_at: string;
|
|
381
378
|
}>;
|
|
382
|
-
return rows.map((row) => ({
|
|
379
|
+
return rows.map((row) => ({
|
|
380
|
+
moduleId: row.module_id,
|
|
381
|
+
version: row.version,
|
|
382
|
+
name: row.name,
|
|
383
|
+
checksum: row.checksum,
|
|
384
|
+
appliedAt: row.applied_at,
|
|
385
|
+
}));
|
|
383
386
|
}
|
|
384
387
|
|
|
385
388
|
/**
|
|
@@ -410,10 +413,14 @@ function ensureCoreLedger(db: Db, alreadyAtCurrentSchema: boolean): void {
|
|
|
410
413
|
`);
|
|
411
414
|
inTransaction(db, () => {
|
|
412
415
|
for (const [index, entry] of CORE_LEDGER_VERSIONS.entries()) {
|
|
413
|
-
const existingRow = db
|
|
416
|
+
const existingRow = db
|
|
417
|
+
.prepare("SELECT checksum FROM module_migrations WHERE module_id = 'core' AND version = ?")
|
|
418
|
+
.get(entry.version) as { checksum: string } | null;
|
|
414
419
|
if (existingRow != null) {
|
|
415
420
|
if (existingRow.checksum !== entry.checksum) {
|
|
416
|
-
throw new Error(
|
|
421
|
+
throw new Error(
|
|
422
|
+
`module migration "core" version ${entry.version} checksum mismatch: the frozen definition for this version was edited after it was applied`,
|
|
423
|
+
);
|
|
417
424
|
}
|
|
418
425
|
continue;
|
|
419
426
|
}
|
|
@@ -423,16 +430,18 @@ function ensureCoreLedger(db: Db, alreadyAtCurrentSchema: boolean): void {
|
|
|
423
430
|
db.exec(SEED_SQL);
|
|
424
431
|
db.exec(`PRAGMA user_version = ${SQLITE_SCHEMA_VERSION}`);
|
|
425
432
|
}
|
|
426
|
-
db.prepare("INSERT INTO module_migrations (module_id, version, name, checksum, applied_at) VALUES ('core', ?, ?, ?, ?)")
|
|
427
|
-
|
|
433
|
+
db.prepare("INSERT INTO module_migrations (module_id, version, name, checksum, applied_at) VALUES ('core', ?, ?, ?, ?)").run(
|
|
434
|
+
entry.version,
|
|
435
|
+
entry.name,
|
|
436
|
+
entry.checksum,
|
|
437
|
+
new Date().toISOString(),
|
|
438
|
+
);
|
|
428
439
|
}
|
|
429
440
|
});
|
|
430
441
|
}
|
|
431
442
|
|
|
432
443
|
function bootstrapEmptyDatabase(db: Db): void {
|
|
433
|
-
const existing = db
|
|
434
|
-
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1")
|
|
435
|
-
.get();
|
|
444
|
+
const existing = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get();
|
|
436
445
|
if (existing) throw new Error("database schema is unversioned; refusing to migrate existing data during boot");
|
|
437
446
|
ensureCoreLedger(db, false);
|
|
438
447
|
}
|
|
@@ -527,7 +536,9 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
527
536
|
// older user_version to exercise this migration path must not fail with "duplicate column
|
|
528
537
|
// name" -- the same class of already-bootstrapped-fixture concern version 9's comment covers.
|
|
529
538
|
up: (db) => {
|
|
530
|
-
const existing = new Set(
|
|
539
|
+
const existing = new Set(
|
|
540
|
+
(db.prepare("PRAGMA table_info(discussion_rounds)").all() as Array<{ name: string }>).map((row) => row.name),
|
|
541
|
+
);
|
|
531
542
|
for (const column of ["options", "options_mode", "selected"]) {
|
|
532
543
|
if (!existing.has(column)) db.exec(`ALTER TABLE discussion_rounds ADD COLUMN ${column} TEXT`);
|
|
533
544
|
}
|
|
@@ -571,7 +582,9 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
571
582
|
// pattern as version 16's discuss-options -- a round with no per-option description (every
|
|
572
583
|
// round before this feature existed, and any that simply doesn't need one) stores NULL.
|
|
573
584
|
up: (db) => {
|
|
574
|
-
const existing = new Set(
|
|
585
|
+
const existing = new Set(
|
|
586
|
+
(db.prepare("PRAGMA table_info(discussion_rounds)").all() as Array<{ name: string }>).map((row) => row.name),
|
|
587
|
+
);
|
|
575
588
|
if (!existing.has("option_descriptions")) db.exec("ALTER TABLE discussion_rounds ADD COLUMN option_descriptions TEXT");
|
|
576
589
|
},
|
|
577
590
|
},
|
|
@@ -624,7 +637,10 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
624
637
|
WHEN NOT EXISTS (SELECT 1 FROM artifact_trash WHERE artifact_id = OLD.note_id AND purge_after <= strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
625
638
|
BEGIN SELECT RAISE(ABORT, 'note_events are append-only except during an explicit, elapsed-grace-period artifact trash purge'); END;
|
|
626
639
|
`);
|
|
627
|
-
const rows = db.prepare("SELECT id, extra FROM artifacts WHERE kind = 'doc' AND subtype = 'note'").all() as Array<{
|
|
640
|
+
const rows = db.prepare("SELECT id, extra FROM artifacts WHERE kind = 'doc' AND subtype = 'note'").all() as Array<{
|
|
641
|
+
id: string;
|
|
642
|
+
extra: string;
|
|
643
|
+
}>;
|
|
628
644
|
const insert = db.prepare(`
|
|
629
645
|
INSERT INTO note_events (note_id, occurred_at, event_type, actor, source, session_id, reason, related_id, disposition, event_schema_version)
|
|
630
646
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
|
|
@@ -632,20 +648,20 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
632
648
|
const strip = db.prepare("UPDATE artifacts SET extra = ? WHERE id = ?");
|
|
633
649
|
for (const row of rows) {
|
|
634
650
|
const extra = JSON.parse(row.extra) as Record<string, unknown>;
|
|
635
|
-
const noteHistory = extra
|
|
651
|
+
const noteHistory = extra.noteHistory;
|
|
636
652
|
if (Array.isArray(noteHistory)) {
|
|
637
653
|
for (const raw of noteHistory) {
|
|
638
654
|
const entry = raw as Record<string, unknown>;
|
|
639
655
|
insert.run(
|
|
640
656
|
row.id,
|
|
641
|
-
typeof entry
|
|
642
|
-
typeof entry
|
|
643
|
-
typeof entry
|
|
644
|
-
typeof entry
|
|
645
|
-
typeof entry
|
|
646
|
-
typeof entry
|
|
647
|
-
typeof entry
|
|
648
|
-
typeof entry
|
|
657
|
+
typeof entry.at === "string" ? entry.at : new Date().toISOString(),
|
|
658
|
+
typeof entry.action === "string" ? entry.action : "captured",
|
|
659
|
+
typeof entry.actor === "string" ? entry.actor : "system",
|
|
660
|
+
typeof entry.source === "string" ? entry.source : "unknown",
|
|
661
|
+
typeof entry.sessionId === "string" ? entry.sessionId : null,
|
|
662
|
+
typeof entry.reason === "string" ? entry.reason : null,
|
|
663
|
+
typeof entry.targetId === "string" ? entry.targetId : null,
|
|
664
|
+
typeof entry.disposition === "string" ? entry.disposition : null,
|
|
649
665
|
);
|
|
650
666
|
}
|
|
651
667
|
}
|
|
@@ -656,6 +672,36 @@ const FUTURE_MIGRATIONS: ReadonlyArray<PapyrusMigration> = [
|
|
|
656
672
|
}
|
|
657
673
|
},
|
|
658
674
|
},
|
|
675
|
+
{
|
|
676
|
+
version: 22,
|
|
677
|
+
name: "skill-to-playbook-data-migration",
|
|
678
|
+
// Part of the Skill→Playbook consolidation. Migration 18 already moved subtype-less Skill
|
|
679
|
+
// rows to kind=playbook; this moves everything still left under kind=skill (workflow and
|
|
680
|
+
// artifact-template rows alike, plus any bare stragglers), preserving subtype and status so
|
|
681
|
+
// a later task can still tell which authoring shape each migrated row came from. The
|
|
682
|
+
// 'skill'/'artifact-template' kinds/statuses type rows themselves are deliberately NOT
|
|
683
|
+
// dropped here: src/modules/skills.ts's createSkill (and the CLI/Vehicle/TUI surfaces still
|
|
684
|
+
// wired to it) still writes kind='skill' rows in its own live, not-yet-retired test suite --
|
|
685
|
+
// dropping the kinds row now would break that FK-enforced write path before its own
|
|
686
|
+
// retirement task removes it. Dropping the type rows is that later task's job.
|
|
687
|
+
up: (db) => {
|
|
688
|
+
db.exec(`UPDATE artifacts SET kind = 'playbook' WHERE kind = 'skill'`);
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
version: 23,
|
|
693
|
+
name: "retire-skill-kind",
|
|
694
|
+
// Final step of the Skill→Playbook consolidation. Every module/CLI/Vehicle/TUI surface
|
|
695
|
+
// that wrote kind='skill' is now retired (confirmed live: zero real callers anywhere), and
|
|
696
|
+
// migration 22 already moved every existing row off kind=skill. The type rows themselves
|
|
697
|
+
// are now genuinely dead -- safe to drop.
|
|
698
|
+
up: (db) => {
|
|
699
|
+
db.exec(`
|
|
700
|
+
DELETE FROM statuses WHERE kind = 'skill';
|
|
701
|
+
DELETE FROM kinds WHERE name = 'skill';
|
|
702
|
+
`);
|
|
703
|
+
},
|
|
704
|
+
},
|
|
659
705
|
];
|
|
660
706
|
|
|
661
707
|
/**
|
|
@@ -697,9 +743,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
697
743
|
// entire frozen chain, not merely fail to match any of its branches -- entering it and
|
|
698
744
|
// falling through to the final gap check would otherwise misreport a database correctly
|
|
699
745
|
// mid-way through FUTURE_MIGRATIONS as "no explicit migration path".
|
|
700
|
-
if (from < LEGACY_MIGRATION_CHAIN_TARGET_VERSION)
|
|
701
|
-
|
|
702
|
-
db
|
|
746
|
+
if (from < LEGACY_MIGRATION_CHAIN_TARGET_VERSION)
|
|
747
|
+
inTransaction(db, () => {
|
|
748
|
+
if (schemaVersion(db) === 1) {
|
|
749
|
+
db.exec(`
|
|
703
750
|
INSERT OR IGNORE INTO statuses VALUES ('todo','task');
|
|
704
751
|
INSERT OR IGNORE INTO statuses VALUES ('in-progress','task');
|
|
705
752
|
INSERT OR IGNORE INTO statuses VALUES ('review','task');
|
|
@@ -724,10 +771,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
724
771
|
DELETE FROM statuses WHERE kind = 'task' AND name IN ('pending', 'active', 'failed');
|
|
725
772
|
PRAGMA user_version = 2;
|
|
726
773
|
`);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
774
|
+
applied.push("task-lifecycle-and-focus");
|
|
775
|
+
}
|
|
776
|
+
if (schemaVersion(db) === 2) {
|
|
777
|
+
db.exec(`
|
|
731
778
|
CREATE TABLE task_events (
|
|
732
779
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
733
780
|
task_id TEXT NOT NULL REFERENCES artifacts(id),
|
|
@@ -750,10 +797,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
750
797
|
BEGIN SELECT RAISE(ABORT, 'task_events are append-only'); END;
|
|
751
798
|
PRAGMA user_version = 3;
|
|
752
799
|
`);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
800
|
+
applied.push("task-history");
|
|
801
|
+
}
|
|
802
|
+
if (schemaVersion(db) === 3) {
|
|
803
|
+
db.exec(`
|
|
757
804
|
CREATE TABLE task_scopes (
|
|
758
805
|
task_id TEXT PRIMARY KEY REFERENCES artifacts(id),
|
|
759
806
|
project_root TEXT,
|
|
@@ -773,18 +820,18 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
773
820
|
FROM artifacts WHERE kind = 'task';
|
|
774
821
|
PRAGMA user_version = 4;
|
|
775
822
|
`);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
823
|
+
applied.push("task-project-scope");
|
|
824
|
+
}
|
|
825
|
+
if (schemaVersion(db) === 4) {
|
|
826
|
+
db.exec(`
|
|
780
827
|
ALTER TABLE task_focus ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'paused'));
|
|
781
828
|
ALTER TABLE task_focus ADD COLUMN pause_reason TEXT;
|
|
782
829
|
PRAGMA user_version = 5;
|
|
783
830
|
`);
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
831
|
+
applied.push("task-focus-continuation");
|
|
832
|
+
}
|
|
833
|
+
if (schemaVersion(db) === 5) {
|
|
834
|
+
db.exec(`
|
|
788
835
|
INSERT OR IGNORE INTO relation_names VALUES ('reply_to','Append-only message replies to another message in the same thread');
|
|
789
836
|
INSERT OR IGNORE INTO relation_names VALUES ('discusses','Message or turn concerns a verified artifact');
|
|
790
837
|
CREATE TABLE discourse_threads (
|
|
@@ -826,10 +873,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
826
873
|
BEGIN SELECT RAISE(ABORT, 'discourse Context Mesh artifact type is immutable'); END;
|
|
827
874
|
PRAGMA user_version = 6;
|
|
828
875
|
`);
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
876
|
+
applied.push("discourse-context-mesh");
|
|
877
|
+
}
|
|
878
|
+
if (schemaVersion(db) === 6) {
|
|
879
|
+
db.exec(`
|
|
833
880
|
CREATE TABLE artifact_events (
|
|
834
881
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
835
882
|
artifact_id TEXT NOT NULL REFERENCES artifacts(id),
|
|
@@ -854,10 +901,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
854
901
|
BEGIN SELECT RAISE(ABORT, 'artifact_events are append-only'); END;
|
|
855
902
|
PRAGMA user_version = 7;
|
|
856
903
|
`);
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
904
|
+
applied.push("artifact-event-log");
|
|
905
|
+
}
|
|
906
|
+
if (schemaVersion(db) === 7) {
|
|
907
|
+
db.exec(`
|
|
861
908
|
CREATE TABLE task_focus_v7 (
|
|
862
909
|
scope TEXT PRIMARY KEY,
|
|
863
910
|
task_id TEXT NOT NULL REFERENCES artifacts(id),
|
|
@@ -870,15 +917,15 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
870
917
|
ALTER TABLE task_focus_v7 RENAME TO task_focus;
|
|
871
918
|
PRAGMA user_version = 8;
|
|
872
919
|
`);
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
920
|
+
applied.push("task-focus-session-scope");
|
|
921
|
+
}
|
|
922
|
+
if (schemaVersion(db) === 8) {
|
|
923
|
+
// IF NOT EXISTS here, unlike earlier migration branches: a fully-bootstrapped
|
|
924
|
+
// :memory: fixture (used by unrelated tests that only roll user_version back to
|
|
925
|
+
// simulate an older *file* database) already has every table the current bootstrap
|
|
926
|
+
// DDL declares, this one included -- so this branch must be safe to run whether or
|
|
927
|
+
// not that already happened, not assume a truly-old database created it first.
|
|
928
|
+
db.exec(`
|
|
882
929
|
CREATE TABLE IF NOT EXISTS graph_projection_checkpoints (
|
|
883
930
|
producer_id TEXT PRIMARY KEY,
|
|
884
931
|
last_sequence INTEGER NOT NULL,
|
|
@@ -894,10 +941,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
894
941
|
CREATE INDEX IF NOT EXISTS graph_projection_identities_artifact_idx ON graph_projection_identities(artifact_id);
|
|
895
942
|
PRAGMA user_version = 9;
|
|
896
943
|
`);
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
944
|
+
applied.push("graph-projection-protocol");
|
|
945
|
+
}
|
|
946
|
+
if (schemaVersion(db) === 9) {
|
|
947
|
+
db.exec(`
|
|
901
948
|
CREATE TABLE IF NOT EXISTS artifact_scopes (
|
|
902
949
|
artifact_id TEXT PRIMARY KEY REFERENCES artifacts(id),
|
|
903
950
|
project_root TEXT,
|
|
@@ -907,10 +954,10 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
907
954
|
CREATE INDEX IF NOT EXISTS artifact_scopes_project_idx ON artifact_scopes(project_root, artifact_id);
|
|
908
955
|
PRAGMA user_version = 10;
|
|
909
956
|
`);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
957
|
+
applied.push("docs-rules-skills-project-scope");
|
|
958
|
+
}
|
|
959
|
+
if (schemaVersion(db) === 10) {
|
|
960
|
+
db.exec(`
|
|
914
961
|
CREATE TABLE IF NOT EXISTS log_sources (
|
|
915
962
|
id TEXT PRIMARY KEY,
|
|
916
963
|
label TEXT NOT NULL,
|
|
@@ -934,18 +981,18 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
934
981
|
BEGIN SELECT RAISE(ABORT, 'log_entries are immutable once written; retention trimming is the only supported deletion path'); END;
|
|
935
982
|
PRAGMA user_version = 11;
|
|
936
983
|
`);
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
984
|
+
applied.push("log-domain");
|
|
985
|
+
}
|
|
986
|
+
if (schemaVersion(db) === 11) {
|
|
987
|
+
// Removes Discourse's Papyrus-embedded storage entirely: confirmed zero rows in every
|
|
988
|
+
// discourse_* table and zero Docs carrying the reserved context-thread/context-message
|
|
989
|
+
// subtypes in the real production database before this was written -- Discourse's real
|
|
990
|
+
// home is now the standalone @danypops/discourse package plus host adapters, and
|
|
991
|
+
// Papyrus's own copy never had a single real caller since it was built. IF EXISTS
|
|
992
|
+
// throughout: a database that never actually reached the v5->v6 discourse-context-mesh
|
|
993
|
+
// step in the first place (e.g. a test fixture that starts partway through the chain)
|
|
994
|
+
// must not fail here just because there was nothing to remove.
|
|
995
|
+
db.exec(`
|
|
949
996
|
DROP TRIGGER IF EXISTS discourse_artifact_type_immutable;
|
|
950
997
|
DROP TRIGGER IF EXISTS discourse_posts_artifact_type;
|
|
951
998
|
DROP TRIGGER IF EXISTS discourse_threads_artifact_type;
|
|
@@ -958,14 +1005,14 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
958
1005
|
DELETE FROM relation_names WHERE name IN ('reply_to', 'discusses');
|
|
959
1006
|
PRAGMA user_version = 12;
|
|
960
1007
|
`);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1008
|
+
applied.push("remove-discourse");
|
|
1009
|
+
}
|
|
1010
|
+
if (schemaVersion(db) === 12) {
|
|
1011
|
+
// See domain/session-identity.ts and verify-caller-identity-behind-papyrus-mutation-
|
|
1012
|
+
// attribution-koxt: first-touch capability binding for session_id, the one place it is
|
|
1013
|
+
// behavior-affecting today (Task Focus). Purely additive -- a session_id that never
|
|
1014
|
+
// registers here behaves exactly as before.
|
|
1015
|
+
db.exec(`
|
|
969
1016
|
CREATE TABLE IF NOT EXISTS session_identities (
|
|
970
1017
|
session_id TEXT PRIMARY KEY,
|
|
971
1018
|
secret_hash TEXT NOT NULL,
|
|
@@ -974,10 +1021,11 @@ export function migrateDb(db: Db): MigrationResult {
|
|
|
974
1021
|
);
|
|
975
1022
|
PRAGMA user_version = 13;
|
|
976
1023
|
`);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1024
|
+
applied.push("session-identity");
|
|
1025
|
+
}
|
|
1026
|
+
if (schemaVersion(db) !== LEGACY_MIGRATION_CHAIN_TARGET_VERSION)
|
|
1027
|
+
throw new Error(`no explicit migration path from database schema ${from}`);
|
|
1028
|
+
});
|
|
981
1029
|
|
|
982
1030
|
// Guarded by length: runMigrations treats an empty migrations array's "target version" as
|
|
983
1031
|
// 0 (see its own sorted.at(-1) ?? 0), which would misreport a database the legacy chain
|