wyrm-mcp 7.2.0 → 7.2.2

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.
Files changed (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. package/package.json +4 -2
@@ -1,22 +1,4 @@
1
- /**
2
- * Wyrm Database Migrations — Versioned schema management
3
- *
4
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
5
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
6
- *
7
- * Each migration is a function that receives a better-sqlite3 Database instance.
8
- * Migrations run in order and are tracked in the schema_versions table.
9
- * Once applied, a migration is never re-run.
10
- */
11
- /**
12
- * All migrations in order. Append new migrations to the end — never modify existing ones.
13
- */
14
- export const migrations = [
15
- {
16
- version: 1,
17
- description: 'Baseline schema — projects, sessions, quests, context, data_lake, skills, FTS5',
18
- up: (db) => {
19
- db.exec(`
1
+ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, quests, context, data_lake, skills, FTS5",up:e=>{e.exec(`
20
2
  -- Core tables
21
3
  CREATE TABLE IF NOT EXISTS projects (
22
4
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -215,14 +197,7 @@ export const migrations = [
215
197
  VALUES('delete', old.id, old.key, old.value);
216
198
  INSERT INTO data_lake_fts(rowid, key, value) VALUES (new.id, new.key, new.value);
217
199
  END;
218
- `);
219
- },
220
- },
221
- {
222
- version: 2,
223
- description: 'Add usage_events table for analytics tracking',
224
- up: (db) => {
225
- db.exec(`
200
+ `)}},{version:2,description:"Add usage_events table for analytics tracking",up:e=>{e.exec(`
226
201
  CREATE TABLE IF NOT EXISTS usage_events (
227
202
  id INTEGER PRIMARY KEY AUTOINCREMENT,
228
203
  tool_name TEXT NOT NULL,
@@ -235,34 +210,7 @@ export const migrations = [
235
210
  timestamp TEXT DEFAULT (datetime('now'))
236
211
  );
237
212
  CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage_events(timestamp);
238
- `);
239
- },
240
- },
241
- {
242
- version: 3,
243
- description: 'Add visibility and created_by columns for future multi-user support',
244
- up: (db) => {
245
- // Add visibility + created_by to tables that will need them.
246
- // Using ALTER TABLE with defaults so existing rows get sensible values.
247
- const tables = ['sessions', 'quests', 'context', 'data_lake'];
248
- for (const table of tables) {
249
- // Check if column already exists (idempotent)
250
- const cols = db.prepare(`PRAGMA table_info(${table})`).all();
251
- const colNames = cols.map(c => c.name);
252
- if (!colNames.includes('visibility')) {
253
- db.exec(`ALTER TABLE ${table} ADD COLUMN visibility TEXT DEFAULT 'private'`);
254
- }
255
- if (!colNames.includes('created_by')) {
256
- db.exec(`ALTER TABLE ${table} ADD COLUMN created_by TEXT DEFAULT 'local'`);
257
- }
258
- }
259
- },
260
- },
261
- {
262
- version: 4,
263
- description: 'Knowledge graph — entities, aliases, relationships with FTS5',
264
- up: (db) => {
265
- db.exec(`
213
+ `)}},{version:3,description:"Add visibility and created_by columns for future multi-user support",up:e=>{const E=["sessions","quests","context","data_lake"];for(const t of E){const i=e.prepare(`PRAGMA table_info(${t})`).all().map(T=>T.name);i.includes("visibility")||e.exec(`ALTER TABLE ${t} ADD COLUMN visibility TEXT DEFAULT 'private'`),i.includes("created_by")||e.exec(`ALTER TABLE ${t} ADD COLUMN created_by TEXT DEFAULT 'local'`)}}},{version:4,description:"Knowledge graph \u2014 entities, aliases, relationships with FTS5",up:e=>{e.exec(`
266
214
  -- Entities: named things that can be linked together
267
215
  CREATE TABLE IF NOT EXISTS entities (
268
216
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -291,7 +239,7 @@ export const migrations = [
291
239
  CREATE INDEX IF NOT EXISTS idx_entity_aliases_entity ON entity_aliases(entity_id);
292
240
  CREATE INDEX IF NOT EXISTS idx_entity_aliases_alias ON entity_aliases(alias);
293
241
 
294
- -- Relationships between entities directed, typed, with provenance
242
+ -- Relationships between entities \u2014 directed, typed, with provenance
295
243
  CREATE TABLE IF NOT EXISTS relationships (
296
244
  id INTEGER PRIMARY KEY AUTOINCREMENT,
297
245
  project_id INTEGER NOT NULL,
@@ -337,14 +285,7 @@ export const migrations = [
337
285
  INSERT INTO entities_fts(rowid, name, type, metadata)
338
286
  VALUES (new.id, new.name, new.type, new.metadata);
339
287
  END;
340
- `);
341
- },
342
- },
343
- {
344
- version: 5,
345
- description: 'Memory artifacts — distilled knowledge for intelligence amplification',
346
- up: (db) => {
347
- db.exec(`
288
+ `)}},{version:5,description:"Memory artifacts \u2014 distilled knowledge for intelligence amplification",up:e=>{e.exec(`
348
289
  -- Memory artifacts: distilled problem-solution records, lessons, patterns, anti-patterns.
349
290
  -- Used by wyrm_context_build to assemble task-specific memory briefs for AI models.
350
291
  CREATE TABLE IF NOT EXISTS memory_artifacts (
@@ -399,14 +340,7 @@ export const migrations = [
399
340
  INSERT INTO memory_artifacts_fts(rowid, problem, constraints, validated_fix, why_it_worked, tags)
400
341
  VALUES (new.id, new.problem, new.constraints, new.validated_fix, new.why_it_worked, new.tags);
401
342
  END;
402
- `);
403
- },
404
- },
405
- {
406
- version: 6,
407
- description: 'Intelligence amplification — ground truths, reasoning scaffolds, distillation review queue',
408
- up: (db) => {
409
- db.exec(`
343
+ `)}},{version:6,description:"Intelligence amplification \u2014 ground truths, reasoning scaffolds, distillation review queue",up:e=>{e.exec(`
410
344
  -- Ground truths: authoritative, versioned project facts.
411
345
  -- Use is_current=1 for the live value. Superseded rows kept for history.
412
346
  CREATE TABLE IF NOT EXISTS ground_truths (
@@ -476,33 +410,12 @@ export const migrations = [
476
410
  -- needs_review=1 means it was auto-created by wyrm_distill and awaits approval
477
411
  ALTER TABLE memory_artifacts ADD COLUMN needs_review INTEGER DEFAULT 0;
478
412
  CREATE INDEX IF NOT EXISTS idx_artifacts_review ON memory_artifacts(project_id, needs_review);
479
- `);
480
- },
481
- },
482
- {
483
- version: 7,
484
- description: 'Ground truth TTL — temporal decay support for staleness detection',
485
- up: (db) => {
486
- db.exec(`
413
+ `)}},{version:7,description:"Ground truth TTL \u2014 temporal decay support for staleness detection",up:e=>{e.exec(`
487
414
  ALTER TABLE ground_truths ADD COLUMN ttl_days INTEGER CHECK(ttl_days IS NULL OR ttl_days > 0);
488
- `);
489
- },
490
- },
491
- {
492
- version: 8,
493
- description: 'Memory artifact decay — access tracking for auto-prune',
494
- up: (db) => {
495
- db.exec(`
415
+ `)}},{version:8,description:"Memory artifact decay \u2014 access tracking for auto-prune",up:e=>{e.exec(`
496
416
  ALTER TABLE memory_artifacts ADD COLUMN last_accessed_at TEXT DEFAULT (datetime('now'));
497
417
  ALTER TABLE memory_artifacts ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0;
498
- `);
499
- },
500
- },
501
- {
502
- version: 9,
503
- description: 'v3.9.0 — failure patterns, agent presence, causality, symbol index, tool analytics',
504
- up: (db) => {
505
- db.exec(`
418
+ `)}},{version:9,description:"v3.9.0 \u2014 failure patterns, agent presence, causality, symbol index, tool analytics",up:e=>{e.exec(`
506
419
  -- ============================================================
507
420
  -- Tier 1.1: Counter-pattern detection
508
421
  -- Records edits/commands that failed so the predictive push can
@@ -649,16 +562,9 @@ export const migrations = [
649
562
  CREATE INDEX IF NOT EXISTS idx_toolcall_name ON tool_call_log(tool_name, called_at DESC);
650
563
  CREATE INDEX IF NOT EXISTS idx_toolcall_project ON tool_call_log(project_id, called_at DESC);
651
564
  CREATE INDEX IF NOT EXISTS idx_toolcall_success ON tool_call_log(success, called_at DESC);
652
- `);
653
- },
654
- },
655
- {
656
- version: 10,
657
- description: 'v4.0.0 — federated sync, hash-chained audit, sub-agent embedding (wyrm_ask), session-rehydration helpers',
658
- up: (db) => {
659
- db.exec(`
565
+ `)}},{version:10,description:"v4.0.0 \u2014 federated sync, hash-chained audit, sub-agent embedding (wyrm_ask), session-rehydration helpers",up:e=>{e.exec(`
660
566
  -- ============================================================
661
- -- Tier 2.8: Federated team Wyrm selective sharing
567
+ -- Tier 2.8: Federated team Wyrm \u2014 selective sharing
662
568
  -- A single "is_shared" flag on the major user-facing tables.
663
569
  -- A team server pulls only is_shared=1 rows during sync.
664
570
  -- ============================================================
@@ -708,7 +614,7 @@ export const migrations = [
708
614
  ON sync_log(remote_origin, direction, synced_at DESC);
709
615
 
710
616
  -- ============================================================
711
- -- Tier 3.9: Compliance audit trail hash-chained event log
617
+ -- Tier 3.9: Compliance audit trail \u2014 hash-chained event log
712
618
  -- Each row references the hash of the previous row. Tampering with
713
619
  -- any historical entry breaks the chain. Combined with optional
714
620
  -- Ed25519 signing this provides a SOC2/HIPAA-grade audit surface.
@@ -730,7 +636,7 @@ export const migrations = [
730
636
  CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_log(actor, logged_at DESC);
731
637
 
732
638
  -- ============================================================
733
- -- Tier 3.10: Sub-agent embedding wyrm_ask query log
639
+ -- Tier 3.10: Sub-agent embedding \u2014 wyrm_ask query log
734
640
  -- Tracks every wyrm_ask invocation: query, context window, model,
735
641
  -- response, latency, tokens. Cost tracking + answer quality
736
642
  -- analysis. Doubles as a public-facing audit if wyrm_ask is
@@ -753,16 +659,9 @@ export const migrations = [
753
659
  );
754
660
  CREATE INDEX IF NOT EXISTS idx_llm_project ON llm_query_log(project_id, asked_at DESC);
755
661
  CREATE INDEX IF NOT EXISTS idx_llm_model ON llm_query_log(model, asked_at DESC);
756
- `);
757
- },
758
- },
759
- {
760
- version: 11,
761
- description: 'v5.0.0 — goals, agent loop, outbound MCP clients, action log',
762
- up: (db) => {
763
- db.exec(`
662
+ `)}},{version:11,description:"v5.0.0 \u2014 goals, agent loop, outbound MCP clients, action log",up:e=>{e.exec(`
764
663
  -- ============================================================
765
- -- v5.0.0: Goals persistent objectives Wyrm pursues
664
+ -- v5.0.0: Goals \u2014 persistent objectives Wyrm pursues
766
665
  -- ============================================================
767
666
  CREATE TABLE IF NOT EXISTS goals (
768
667
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -810,7 +709,7 @@ export const migrations = [
810
709
  CREATE INDEX IF NOT EXISTS idx_goal_iter_goal ON goal_iterations(goal_id, iteration_num);
811
710
 
812
711
  -- ============================================================
813
- -- v5.0.0: Outbound MCP clients Wyrm calls other servers
712
+ -- v5.0.0: Outbound MCP clients \u2014 Wyrm calls other servers
814
713
  -- Config rows for MCP servers Wyrm should connect to (stdio).
815
714
  -- ============================================================
816
715
  CREATE TABLE IF NOT EXISTS mcp_client_configs (
@@ -818,7 +717,7 @@ export const migrations = [
818
717
  server_name TEXT UNIQUE NOT NULL, -- 'github', 'slack', 'phantomdragon', etc.
819
718
  command TEXT NOT NULL, -- 'npx' or absolute path
820
719
  args TEXT, -- JSON array of args
821
- env TEXT, -- JSON object of env vars (NEVER store secrets here in plaintext for prod but local dev is fine)
720
+ env TEXT, -- JSON object of env vars (NEVER store secrets here in plaintext for prod \u2014 but local dev is fine)
822
721
  enabled INTEGER NOT NULL DEFAULT 1,
823
722
  added_at TEXT DEFAULT (datetime('now')),
824
723
  last_used_at TEXT,
@@ -839,7 +738,7 @@ export const migrations = [
839
738
  CREATE INDEX IF NOT EXISTS idx_ext_call_server ON external_call_log(server_name, called_at DESC);
840
739
 
841
740
  -- ============================================================
842
- -- v5.0.0: Agent actions the audit-trail of autonomous loop ticks
741
+ -- v5.0.0: Agent actions \u2014 the audit-trail of autonomous loop ticks
843
742
  -- Higher-level than goal_iterations; one row per wyrm-loop tick.
844
743
  -- ============================================================
845
744
  CREATE TABLE IF NOT EXISTS agent_actions (
@@ -854,14 +753,7 @@ export const migrations = [
854
753
  );
855
754
  CREATE INDEX IF NOT EXISTS idx_agent_actions_goal ON agent_actions(goal_id, ran_at DESC);
856
755
  CREATE INDEX IF NOT EXISTS idx_agent_actions_actor ON agent_actions(actor, ran_at DESC);
857
- `);
858
- },
859
- },
860
- {
861
- version: 12,
862
- description: 'session_seen_artifacts (spec 014) + token-budget telemetry columns',
863
- up: (db) => {
864
- db.exec(`
756
+ `)}},{version:12,description:"session_seen_artifacts (spec 014) + token-budget telemetry columns",up:e=>{e.exec(`
865
757
  -- session_seen_artifacts: per-session dedup so wyrm_context_build
866
758
  -- doesn't re-emit fully-rendered content the AI already saw this
867
759
  -- session. Pruned by wyrm_maintenance after WYRM_SEEN_TTL_DAYS
@@ -878,14 +770,7 @@ export const migrations = [
878
770
  ON session_seen_artifacts(session_id, shown_at DESC);
879
771
  CREATE INDEX IF NOT EXISTS idx_session_seen_prune
880
772
  ON session_seen_artifacts(shown_at);
881
- `);
882
- },
883
- },
884
- {
885
- version: 13,
886
- description: 'design_tokens + design_references — creative workflow tables (5.9.0)',
887
- up: (db) => {
888
- db.exec(`
773
+ `)}},{version:13,description:"design_tokens + design_references \u2014 creative workflow tables (5.9.0)",up:e=>{e.exec(`
889
774
  CREATE TABLE IF NOT EXISTS design_tokens (
890
775
  id INTEGER PRIMARY KEY AUTOINCREMENT,
891
776
  project_id INTEGER NOT NULL,
@@ -936,14 +821,7 @@ export const migrations = [
936
821
  INSERT INTO design_references_fts(rowid, title, notes, tags)
937
822
  VALUES (new.id, new.title, new.notes, new.tags);
938
823
  END;
939
- `);
940
- },
941
- },
942
- {
943
- version: 14,
944
- description: 'token_savings_log + cross_project_visibility — Wyrm 6.0 substrate (spec 018)',
945
- up: (db) => {
946
- db.exec(`
824
+ `)}},{version:14,description:"token_savings_log + cross_project_visibility \u2014 Wyrm 6.0 substrate (spec 018)",up:e=>{e.exec(`
947
825
  -- Token-savings telemetry: every Wyrm tool that has a counterfactual
948
826
  -- logs an estimate so the statusline and digest can show real ROI.
949
827
  CREATE TABLE IF NOT EXISTS token_savings_log (
@@ -966,36 +844,10 @@ export const migrations = [
966
844
  ON token_savings_log(session_id, timestamp DESC);
967
845
  CREATE INDEX IF NOT EXISTS idx_savings_category
968
846
  ON token_savings_log(category, timestamp DESC);
969
- `);
970
- // Cross-project visibility flags on every searchable kind.
971
- // Default 'within' — no existing data leaks across projects.
972
- const tables = [
973
- 'ground_truths',
974
- 'memory_artifacts',
975
- 'quests',
976
- 'decision_edges', // was 'decisions' (a table that never existed) — fixed in v17; kept correct here for fresh installs
977
- 'design_references',
978
- 'design_tokens',
979
- ];
980
- for (const t of tables) {
981
- try {
982
- const cols = db.prepare(`PRAGMA table_info(${t})`).all();
983
- if (cols.some((c) => c.name === 'cross_project_visibility'))
984
- continue;
985
- db.exec(`
847
+ `);const E=["ground_truths","memory_artifacts","quests","decision_edges","design_references","design_tokens"];for(const t of E)try{if(e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name==="cross_project_visibility"))continue;e.exec(`
986
848
  ALTER TABLE ${t} ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
987
849
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
988
- `);
989
- }
990
- catch { /* table may not exist on older installs; safe to skip */ }
991
- }
992
- },
993
- },
994
- {
995
- version: 15,
996
- description: 'Live Memory v6.4 — append-only events log + wyrm_meta (device identity)',
997
- up: (db) => {
998
- db.exec(`
850
+ `)}catch{}}},{version:15,description:"Live Memory v6.4 \u2014 append-only events log + wyrm_meta (device identity)",up:e=>{e.exec(`
999
851
  -- Node-local KV (stable device id for cross-device event identity, etc.)
1000
852
  CREATE TABLE IF NOT EXISTS wyrm_meta (
1001
853
  key TEXT PRIMARY KEY,
@@ -1004,7 +856,7 @@ export const migrations = [
1004
856
 
1005
857
  -- Append-only event log (Live Memory v6.4). The existing tables stay the
1006
858
  -- system of record; this is a derived stream for live propagation. Writes
1007
- -- emit here as a FAILURE-ISOLATED side effect (see events.ts) a broken
859
+ -- emit here as a FAILURE-ISOLATED side effect (see events.ts) \u2014 a broken
1008
860
  -- events table can never regress the canonical write. 'cursor' is the
1009
861
  -- LOCAL subscription cursor (monotonic per node); cross-device identity is
1010
862
  -- (origin_device, origin_seq), with replication via INSERT OR IGNORE.
@@ -1025,19 +877,7 @@ export const migrations = [
1025
877
  );
1026
878
  CREATE INDEX IF NOT EXISTS idx_events_project_cursor ON events(project_id, cursor);
1027
879
  CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind, created_at);
1028
- `);
1029
- },
1030
- },
1031
- {
1032
- version: 16,
1033
- description: 'Rebuild FTS tables with porter stemming (override≈overrides≈overriding) for better recall',
1034
- up: (db) => {
1035
- // The base-table triggers reference these FTS tables BY NAME, so recreating
1036
- // them with the same name + the porter tokenizer keeps the triggers valid —
1037
- // no need to drop/recreate triggers. `rebuild` repopulates from the
1038
- // external-content base table. Migrations run single-threaded at startup,
1039
- // so there's no concurrent write between DROP and CREATE.
1040
- db.exec(`
880
+ `)}},{version:16,description:"Rebuild FTS tables with porter stemming (override\u2248overrides\u2248overriding) for better recall",up:e=>{e.exec(`
1041
881
  DROP TABLE IF EXISTS quests_fts;
1042
882
  CREATE VIRTUAL TABLE quests_fts USING fts5(
1043
883
  title, description, content='quests', content_rowid='id',
@@ -1065,62 +905,13 @@ export const migrations = [
1065
905
  tokenize='porter unicode61'
1066
906
  );
1067
907
  INSERT INTO skills_fts(skills_fts) VALUES('rebuild');
1068
- `);
1069
- },
1070
- },
1071
- {
1072
- version: 17,
1073
- description: 'Backfill cross_project_visibility on decision_edges (migration 14 named a non-existent "decisions" table)',
1074
- up: (db) => {
1075
- // Migration 14 looped over a table called 'decisions' that never existed —
1076
- // the real table is 'decision_edges' — so it silently never got the
1077
- // cross_project_visibility column. Add it here (guarded + idempotent) so
1078
- // already-migrated DBs converge with fresh installs (which now get it in v14).
1079
- try {
1080
- const cols = db.prepare(`PRAGMA table_info(decision_edges)`).all();
1081
- if (!cols.some((c) => c.name === 'cross_project_visibility')) {
1082
- db.exec(`
908
+ `)}},{version:17,description:'Backfill cross_project_visibility on decision_edges (migration 14 named a non-existent "decisions" table)',up:e=>{try{e.prepare("PRAGMA table_info(decision_edges)").all().some(t=>t.name==="cross_project_visibility")||e.exec(`
1083
909
  ALTER TABLE decision_edges ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
1084
910
  CHECK (cross_project_visibility IN ('within', 'org', 'public'));
1085
- `);
1086
- }
1087
- }
1088
- catch { /* table may not exist on a very old install; safe to skip */ }
1089
- },
1090
- },
1091
- {
1092
- version: 18,
1093
- description: 'GOD-SKILL SPEC v2 — skill tier/governs/composes + spec-kit registry (specs table)',
1094
- up: (db) => {
1095
- // --- Skill governance columns (additive, safe defaults) ---
1096
- // tier: atomic|mega|god — apex skill tier routing. Default 'atomic' so
1097
- // every pre-existing skill keeps its current (leaf) behaviour.
1098
- // governs: JSON string array of skill names this node routes DOWN to
1099
- // (god → megas, mega → atomics). Default '[]'.
1100
- // composes: JSON string array of skill names this node pulls in laterally.
1101
- // Default '[]'.
1102
- // All three are guarded so re-running against a partially-migrated DB
1103
- // (or a fresh install that already has them) is a no-op.
1104
- const skillCols = db.prepare(`PRAGMA table_info(skills)`).all();
1105
- const hasSkillCol = (c) => skillCols.some((x) => x.name === c);
1106
- if (!hasSkillCol('tier')) {
1107
- db.exec(`
911
+ `)}catch{}}},{version:18,description:"GOD-SKILL SPEC v2 \u2014 skill tier/governs/composes + spec-kit registry (specs table)",up:e=>{const E=e.prepare("PRAGMA table_info(skills)").all(),t=s=>E.some(i=>i.name===s);t("tier")||e.exec(`
1108
912
  ALTER TABLE skills ADD COLUMN tier TEXT NOT NULL DEFAULT 'atomic'
1109
913
  CHECK (tier IN ('atomic', 'mega', 'god'));
1110
- `);
1111
- }
1112
- if (!hasSkillCol('governs')) {
1113
- db.exec(`ALTER TABLE skills ADD COLUMN governs TEXT NOT NULL DEFAULT '[]';`);
1114
- }
1115
- if (!hasSkillCol('composes')) {
1116
- db.exec(`ALTER TABLE skills ADD COLUMN composes TEXT NOT NULL DEFAULT '[]';`);
1117
- }
1118
- db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_tier ON skills(tier);`);
1119
- // --- Spec-kit registry (GHOSTMESH spec-kit specs → Wyrm-native) ---
1120
- // One row per registered spec directory, linked to a project. The
1121
- // generated quests carry a deterministic tag signature so wyrm_spec_register
1122
- // is idempotent (re-run updates the same quests instead of duplicating).
1123
- db.exec(`
914
+ `),t("governs")||e.exec("ALTER TABLE skills ADD COLUMN governs TEXT NOT NULL DEFAULT '[]';"),t("composes")||e.exec("ALTER TABLE skills ADD COLUMN composes TEXT NOT NULL DEFAULT '[]';"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_tier ON skills(tier);"),e.exec(`
1124
915
  CREATE TABLE IF NOT EXISTS specs (
1125
916
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1126
917
  project_id INTEGER NOT NULL,
@@ -1135,86 +926,11 @@ export const migrations = [
1135
926
  );
1136
927
  CREATE INDEX IF NOT EXISTS idx_specs_project ON specs(project_id);
1137
928
  CREATE INDEX IF NOT EXISTS idx_specs_dir ON specs(spec_dir);
1138
- `);
1139
- },
1140
- },
1141
- {
1142
- version: 19,
1143
- description: 'Grove sync policy: projects.sync_policy gate (private by default) for cloud + federation isolation',
1144
- up: (db) => {
1145
- // Grove-level replication gate. A project's rows may leave the local box
1146
- // only if its grove opts in:
1147
- // 'private' (default): never replicates by any channel.
1148
- // 'cloud': may replicate to the operator's OWN cloud backup
1149
- // (per-row cross_project_visibility still applies).
1150
- // 'team': may also federate to a team Wyrm
1151
- // (per-row is_shared still applies).
1152
- // This is a SECOND, grove-level gate on top of the existing per-row flags,
1153
- // so one accidental flag flip can never alone leak a private grove.
1154
- const pcols = db.prepare(`PRAGMA table_info(projects)`).all();
1155
- if (!pcols.some((c) => c.name === 'sync_policy')) {
1156
- db.exec(`
929
+ `)}},{version:19,description:"Grove sync policy: projects.sync_policy gate (private by default) for cloud + federation isolation",up:e=>{e.prepare("PRAGMA table_info(projects)").all().some(o=>o.name==="sync_policy")||e.exec(`
1157
930
  ALTER TABLE projects ADD COLUMN sync_policy TEXT NOT NULL DEFAULT 'private'
1158
931
  CHECK (sync_policy IN ('private', 'cloud', 'team'));
1159
- `);
1160
- }
1161
- // Backward-compatible backfill, CONSERVATIVE: a project that already had
1162
- // cloud-eligible rows (cross_project_visibility org/public) keeps its cloud
1163
- // backup ability (cloud is per-operator + encrypted, low risk, no silent
1164
- // regression). We deliberately DO NOT auto-promote any grove to 'team':
1165
- // federation is the actual team-exposure axis, so it is ALWAYS an explicit
1166
- // opt-in (via `wyrm grove policy <proj> team`). This guarantees a grove that
1167
- // merely had an is_shared row in the past is never silently moved off
1168
- // 'private' on upgrade. Everything not cloud-eligible stays 'private'.
1169
- const hasCol = (t, c) => {
1170
- try {
1171
- return db.prepare(`PRAGMA table_info(${t})`).all().some((x) => x.name === c);
1172
- }
1173
- catch {
1174
- return false;
1175
- }
1176
- };
1177
- const distinctPids = (sql) => {
1178
- try {
1179
- return db.prepare(sql).all().map((r) => r.pid);
1180
- }
1181
- catch {
1182
- return [];
1183
- }
1184
- };
1185
- const cloudIds = new Set();
1186
- for (const t of ['ground_truths', 'memory_artifacts', 'quests', 'design_tokens', 'design_references']) {
1187
- if (!hasCol(t, 'cross_project_visibility') || !hasCol(t, 'project_id'))
1188
- continue;
1189
- for (const pid of distinctPids(`SELECT DISTINCT project_id AS pid FROM ${t}
1190
- WHERE cross_project_visibility IN ('org', 'public') AND project_id IS NOT NULL`))
1191
- cloudIds.add(pid);
1192
- }
1193
- const setCloud = db.prepare(`UPDATE projects SET sync_policy = 'cloud' WHERE id = ? AND sync_policy = 'private'`);
1194
- for (const id of cloudIds)
1195
- setCloud.run(id);
1196
- },
1197
- },
1198
- {
1199
- version: 20,
1200
- description: 'v7 F2 (T008) — run-native provenance: runs + run_agents tables, agent_id/run_id attribution on 9 tables, failure_patterns.quarantine_scope',
1201
- up: (db) => {
1202
- // ============================================================
1203
- // v7 BROOD FR-1: run-native provenance core.
1204
- // Additive + guarded (Article VI): new tables use IF NOT EXISTS, every
1205
- // ALTER is PRAGMA-checked, and the only UPDATE is a one-time backfill
1206
- // INSIDE its column guard. A v6.x DB opens under this migration with
1207
- // 100% of its rows; nothing is renamed, dropped, or rewritten.
1208
- // ============================================================
1209
- // ---- runs: one row per orchestrated fleet run ----
1210
- // run_id is a ULID (src/ulid.ts: 26-char Crockford base32, monotonic,
1211
- // crypto-random) so run ids sort lexicographically in creation order.
1212
- // parent_run_id nests runs (orchestrator spawning sub-orchestrators).
1213
- // debrief_artifact_id links the run's debrief memory_artifact when the
1214
- // orchestrator files one. No FK from run_agents.agent_id to
1215
- // agent_presence: presence rows are TTL-pruned heartbeats, while run
1216
- // membership is durable provenance that must outlive them.
1217
- db.exec(`
932
+ `);const t=(o,a)=>{try{return e.prepare(`PRAGMA table_info(${o})`).all().some(r=>r.name===a)}catch{return!1}},s=o=>{try{return e.prepare(o).all().map(a=>a.pid)}catch{return[]}},i=new Set;for(const o of["ground_truths","memory_artifacts","quests","design_tokens","design_references"])if(!(!t(o,"cross_project_visibility")||!t(o,"project_id")))for(const a of s(`SELECT DISTINCT project_id AS pid FROM ${o}
933
+ WHERE cross_project_visibility IN ('org', 'public') AND project_id IS NOT NULL`))i.add(a);const T=e.prepare("UPDATE projects SET sync_policy = 'cloud' WHERE id = ? AND sync_policy = 'private'");for(const o of i)T.run(o)}},{version:20,description:"v7 F2 (T008) \u2014 run-native provenance: runs + run_agents tables, agent_id/run_id attribution on 9 tables, failure_patterns.quarantine_scope",up:e=>{e.exec(`
1218
934
  CREATE TABLE IF NOT EXISTS runs (
1219
935
  run_id TEXT PRIMARY KEY,
1220
936
  parent_run_id TEXT,
@@ -1239,98 +955,14 @@ export const migrations = [
1239
955
  FOREIGN KEY (run_id) REFERENCES runs(run_id) ON DELETE CASCADE
1240
956
  );
1241
957
  CREATE INDEX IF NOT EXISTS idx_run_agents_agent ON run_agents(agent_id);
1242
- `);
1243
- // ---- nullable agent_id/run_id attribution on the 9 provenance tables ----
1244
- // Soft references on purpose (no FK to runs): attribution is provenance
1245
- // metadata — pruning or never-creating a runs row must never block or
1246
- // cascade-delete canonical memory rows.
1247
- //
1248
- // On `events`, attribution EXTENDS the existing `actor` column — NOT a
1249
- // parallel concept. `actor` stays the human display identity exactly as
1250
- // 6.x wrote it; `agent_id` (machine identity) and `run_id` land
1251
- // alongside. Precedence at read sites that surface attribution
1252
- // (src/attribution.ts owns this rule):
1253
- // 1. agent_id — machine identity (v7 fleet writes)
1254
- // 2. actor — display identity (6.x writes, human labels)
1255
- // 3. 'legacy' — neither set (a pre-v7 row); read-time only, never
1256
- // written back. audit_log.actor follows the same rule.
1257
- //
1258
- // quest_claims already has agent_id (NOT NULL, migration 9) — the guard
1259
- // skips it and only run_id is added there. All other tables gain both.
1260
- const hasCol = (t, c) => {
1261
- try {
1262
- return db.prepare(`PRAGMA table_info(${t})`).all()
1263
- .some((x) => x.name === c);
1264
- }
1265
- catch {
1266
- return false;
1267
- }
1268
- };
1269
- const ATTRIBUTED_TABLES = [
1270
- 'memory_artifacts', 'failure_patterns', 'decision_edges',
1271
- 'ground_truths', 'quests', 'sessions', 'quest_claims',
1272
- 'events', 'audit_log',
1273
- ];
1274
- for (const t of ATTRIBUTED_TABLES) {
1275
- if (!hasCol(t, 'agent_id'))
1276
- db.exec(`ALTER TABLE ${t} ADD COLUMN agent_id TEXT;`);
1277
- if (!hasCol(t, 'run_id'))
1278
- db.exec(`ALTER TABLE ${t} ADD COLUMN run_id TEXT;`);
1279
- }
1280
- // Partial indexes for the run-scoped hot paths (failure quarantine,
1281
- // run-tagged event fan-out, debrief assembly). Partial so the 6.x
1282
- // all-NULL bulk costs nothing.
1283
- db.exec(`
958
+ `);const E=(s,i)=>{try{return e.prepare(`PRAGMA table_info(${s})`).all().some(T=>T.name===i)}catch{return!1}},t=["memory_artifacts","failure_patterns","decision_edges","ground_truths","quests","sessions","quest_claims","events","audit_log"];for(const s of t)E(s,"agent_id")||e.exec(`ALTER TABLE ${s} ADD COLUMN agent_id TEXT;`),E(s,"run_id")||e.exec(`ALTER TABLE ${s} ADD COLUMN run_id TEXT;`);e.exec(`
1284
959
  CREATE INDEX IF NOT EXISTS idx_failure_run ON failure_patterns(run_id) WHERE run_id IS NOT NULL;
1285
960
  CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id) WHERE run_id IS NOT NULL;
1286
961
  CREATE INDEX IF NOT EXISTS idx_artifacts_run ON memory_artifacts(run_id) WHERE run_id IS NOT NULL;
1287
- `);
1288
- // ---- failure_patterns quarantine scope (run|project|global) ----
1289
- // The v7 spec names this column `scope`, but failure_patterns.scope has
1290
- // existed since migration 9 with DIFFERENT semantics: it is the signature
1291
- // MATCH dimension ('file'|'symbol'|'command'|'prompt') baked into every
1292
- // stored signature by FailurePatterns.signature(). Renaming or
1293
- // repurposing it would be a destructive rewrite (Article VI) and corrupt
1294
- // every existing signature, so the run|project|global QUARANTINE/
1295
- // authority scope lands as `quarantine_scope`.
1296
- //
1297
- // Default 'project' preserves 6.x semantics: a 6.x failure recorded with
1298
- // a project_id was authoritative for its whole project the moment it was
1299
- // written (check() filters by project_id). The guarded one-time backfill
1300
- // marks project_id-IS-NULL rows 'global' because 6.x check() matched
1301
- // those rows from EVERY project (`OR project_id IS NULL`) — without it,
1302
- // v7 quarantine enforcement would silently narrow their reach.
1303
- if (!hasCol('failure_patterns', 'quarantine_scope')) {
1304
- db.exec(`
962
+ `),E("failure_patterns","quarantine_scope")||(e.exec(`
1305
963
  ALTER TABLE failure_patterns ADD COLUMN quarantine_scope TEXT NOT NULL DEFAULT 'project'
1306
964
  CHECK (quarantine_scope IN ('run', 'project', 'global'));
1307
- `);
1308
- db.exec(`UPDATE failure_patterns SET quarantine_scope = 'global' WHERE project_id IS NULL;`);
1309
- }
1310
- db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_quarantine ON failure_patterns(quarantine_scope, resolved);`);
1311
- },
1312
- },
1313
- {
1314
- version: 21,
1315
- description: 'v7 F2 (T015) — failure_confirmations: distinct-agent confirmation ledger behind run-quarantine auto-promotion',
1316
- up: (db) => {
1317
- // ============================================================
1318
- // v7 BROOD FR-2 (T015): run-scoped failure quarantine promotion.
1319
- // One row per (failure, agent) that recorded/confirmed the same failure
1320
- // signature — the evidence ledger behind the debrief-INDEPENDENT
1321
- // "≥2 distinct agent_id confirmations → promote to project scope" rule
1322
- // (src/failure-patterns.ts noteConfirmation()).
1323
- //
1324
- // Additive + guarded (Article VI): IF NOT EXISTS only — re-running this
1325
- // up() is a no-op and a v6.x DB opens with 100% of its rows untouched.
1326
- //
1327
- // PK (failure_id, agent_id) makes COUNT(*) per failure_id the DISTINCT
1328
- // agent count by construction: the same agent re-recording N times is
1329
- // ONE confirmation. run_id records where a confirmation came from (T017
1330
- // analytics input); it is NOT part of the identity. The FK cascades with
1331
- // the failure row — confirmations are evidence ABOUT a canonical row,
1332
- // never canonical memory themselves (a delete() must not orphan them).
1333
- db.exec(`
965
+ `),e.exec("UPDATE failure_patterns SET quarantine_scope = 'global' WHERE project_id IS NULL;")),e.exec("CREATE INDEX IF NOT EXISTS idx_failure_quarantine ON failure_patterns(quarantine_scope, resolved);")}},{version:21,description:"v7 F2 (T015) \u2014 failure_confirmations: distinct-agent confirmation ledger behind run-quarantine auto-promotion",up:e=>{e.exec(`
1334
966
  CREATE TABLE IF NOT EXISTS failure_confirmations (
1335
967
  failure_id INTEGER NOT NULL,
1336
968
  agent_id TEXT NOT NULL,
@@ -1339,42 +971,7 @@ export const migrations = [
1339
971
  PRIMARY KEY (failure_id, agent_id),
1340
972
  FOREIGN KEY (failure_id) REFERENCES failure_patterns(id) ON DELETE CASCADE
1341
973
  );
1342
- `);
1343
- },
1344
- },
1345
- {
1346
- version: 22,
1347
- description: 'v7 F2 (T017) — failure_blocks: prevented-repeat ledger behind wyrm_stats view=failures',
1348
- up: (db) => {
1349
- // ============================================================
1350
- // v7 BROOD FR-2 (T017): prevented-repeat analytics.
1351
- // One row per BLOCKED failure_check verdict (blocked:true) — the unit of
1352
- // "a repeat was prevented". Attribution columns name the CHECKING
1353
- // agent/run (the agent that was about to repeat the failure), NOT the
1354
- // recorder — failure_patterns.agent_id/run_id already name the recorder.
1355
- // failure_id is the verdict's top blocker (matches[0]).
1356
- //
1357
- // STORAGE CHOICE, documented (the task offered counter-table vs
1358
- // events-derived):
1359
- // - NOT events-derived: emitEvent is failure-isolated best-effort AND
1360
- // the events log is retention-pruned (WYRM_EVENT_RETAIN_DAYS /
1361
- // maxPerProject in wyrm_maintenance) — analytics derived from it
1362
- // would silently undercount over time (same rationale that kept the
1363
- // T015 confirmation ledger off the events log). failure_check is
1364
- // also a READ that emits no events today; emitting one per check
1365
- // would bloat the private event stream for one integer of analytics.
1366
- // - An APPEND-ONLY ledger (not an UPSERTed counter row): one INSERT
1367
- // per blocked verdict is exactly correct under multi-process fleet
1368
- // concurrency with no read-modify-write race, and read-time
1369
- // COUNT/GROUP BY is deterministic (Article III: pure SQL, zero LLM).
1370
- // - Private by construction: this is not an events surface — block
1371
- // rows never ride SSE/replication (failures stay is_shared=0).
1372
- //
1373
- // Additive + guarded (Article VI): IF NOT EXISTS only — re-running this
1374
- // up() is a no-op and a v6.x DB opens with 100% of its rows untouched.
1375
- // FK cascades with the failure row (the migration-21 precedent): blocks
1376
- // are analytics ABOUT a canonical row, never canonical memory.
1377
- db.exec(`
974
+ `)}},{version:22,description:"v7 F2 (T017) \u2014 failure_blocks: prevented-repeat ledger behind wyrm_stats view=failures",up:e=>{e.exec(`
1378
975
  CREATE TABLE IF NOT EXISTS failure_blocks (
1379
976
  id INTEGER PRIMARY KEY AUTOINCREMENT,
1380
977
  failure_id INTEGER NOT NULL,
@@ -1384,41 +981,7 @@ export const migrations = [
1384
981
  blocked_at TEXT DEFAULT (datetime('now')),
1385
982
  FOREIGN KEY (failure_id) REFERENCES failure_patterns(id) ON DELETE CASCADE
1386
983
  );
1387
- `);
1388
- // "N repeats blocked this run" is the hot read — partial index in the
1389
- // style of migration 20's idx_failure_run/idx_events_run.
1390
- db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_blocks_run ON failure_blocks(run_id) WHERE run_id IS NOT NULL;`);
1391
- db.exec(`CREATE INDEX IF NOT EXISTS idx_failure_blocks_failure ON failure_blocks(failure_id);`);
1392
- },
1393
- },
1394
- {
1395
- version: 23,
1396
- description: 'v7 F3 (T028) — run_briefs: byte-stable fleet session_prime brief cache, one row per (run_id, role)',
1397
- up: (db) => {
1398
- // ============================================================
1399
- // v7 BROOD FR-5 (T028): fleet-mode session_prime.
1400
- // The FIRST prime of a (run_id, role) compiles the role-sliced brief
1401
- // and CAS-inserts it here (INSERT OR IGNORE on the PK, then every
1402
- // caller — winner and losers alike — reads the row back), so 12
1403
- // simultaneous primes across 12 PROCESSES return the byte-identical
1404
- // cached prefix (spec §7 criterion 8). An in-memory cache cannot give
1405
- // that guarantee: fleet agents are separate stdio server processes
1406
- // sharing only this SQLite file.
1407
- //
1408
- // body_json is the full structured session_prime body (the T026
1409
- // sections[] shape) — the text channel re-derives from it through the
1410
- // T019 renderer, so text and structuredContent cannot drift.
1411
- //
1412
- // run_id is a SOFT reference to runs (no FK), the migration-20
1413
- // precedent: a prime may legitimately race `wyrm_run action=start`
1414
- // (the F2 "orchestrator already exported WYRM_RUN_ID" orphan shape),
1415
- // and a cache row must never block or cascade with run lifecycle.
1416
- // Retention: wyrm_maintenance prunes by created_at (T029, same
1417
- // WYRM_EVENT_RETAIN_DAYS knob as the event log).
1418
- //
1419
- // Additive + guarded (Article VI): IF NOT EXISTS only — re-running
1420
- // this up() is a no-op and a v6.x DB opens with 100% of its rows.
1421
- db.exec(`
984
+ `),e.exec("CREATE INDEX IF NOT EXISTS idx_failure_blocks_run ON failure_blocks(run_id) WHERE run_id IS NOT NULL;"),e.exec("CREATE INDEX IF NOT EXISTS idx_failure_blocks_failure ON failure_blocks(failure_id);")}},{version:23,description:"v7 F3 (T028) \u2014 run_briefs: byte-stable fleet session_prime brief cache, one row per (run_id, role)",up:e=>{e.exec(`
1422
985
  CREATE TABLE IF NOT EXISTS run_briefs (
1423
986
  run_id TEXT NOT NULL,
1424
987
  role TEXT NOT NULL DEFAULT '',
@@ -1427,198 +990,20 @@ export const migrations = [
1427
990
  created_at TEXT DEFAULT (datetime('now')),
1428
991
  PRIMARY KEY (run_id, role)
1429
992
  );
1430
- `);
1431
- },
1432
- },
1433
- {
1434
- version: 24,
1435
- description: 'v7 F3 (T029) — claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes',
1436
- up: (db) => {
1437
- // ============================================================
1438
- // v7 BROOD FR-5 (T029): claims/presence run attribution completed.
1439
- // quest_claims got run_id in migration 20; `role` lands here so an
1440
- // orchestrator auditing the claim board sees WHICH fleet role holds
1441
- // each lease. agent_presence persists rows (TTL heartbeats, table
1442
- // since migration 9) so it gains BOTH run_id and role — presence
1443
- // dashboards become per-run. All three are stamped at write time from
1444
- // the ambient actor envelope + run_agents membership (presence.ts);
1445
- // NULL outside a run (the migration-20 soft-ref rule: never an FK —
1446
- // attribution metadata must never block or cascade canonical rows).
1447
- //
1448
- // Claims stay atomic via the existing PK-conflict CAS — this migration
1449
- // adds attribution columns only, no locking changes (the spec is
1450
- // explicit: there is no race to fix).
1451
- //
1452
- // Additive + guarded (Article VI): PRAGMA-checked ALTERs, IF NOT
1453
- // EXISTS indexes — a v6.x DB opens with 100% of its rows; pre-v7 rows
1454
- // carry NULL and read as 'legacy' at display time only.
1455
- const hasCol = (t, c) => {
1456
- try {
1457
- return db.prepare(`PRAGMA table_info(${t})`).all()
1458
- .some((x) => x.name === c);
1459
- }
1460
- catch {
1461
- return false;
1462
- }
1463
- };
1464
- if (!hasCol('quest_claims', 'role'))
1465
- db.exec(`ALTER TABLE quest_claims ADD COLUMN role TEXT;`);
1466
- if (!hasCol('agent_presence', 'run_id'))
1467
- db.exec(`ALTER TABLE agent_presence ADD COLUMN run_id TEXT;`);
1468
- if (!hasCol('agent_presence', 'role'))
1469
- db.exec(`ALTER TABLE agent_presence ADD COLUMN role TEXT;`);
1470
- // Partial indexes in the migration-20 style: the all-NULL 6.x bulk
1471
- // costs nothing; wyrm_run status + per-run presence views are the
1472
- // hot reads.
1473
- db.exec(`
993
+ `)}},{version:24,description:"v7 F3 (T029) \u2014 claims/presence hardening: role on quest_claims, run_id/role on agent_presence, run-scoped partial indexes",up:e=>{const E=(t,s)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===s)}catch{return!1}};E("quest_claims","role")||e.exec("ALTER TABLE quest_claims ADD COLUMN role TEXT;"),E("agent_presence","run_id")||e.exec("ALTER TABLE agent_presence ADD COLUMN run_id TEXT;"),E("agent_presence","role")||e.exec("ALTER TABLE agent_presence ADD COLUMN role TEXT;"),e.exec(`
1474
994
  CREATE INDEX IF NOT EXISTS idx_quest_claims_run ON quest_claims(run_id) WHERE run_id IS NOT NULL;
1475
995
  CREATE INDEX IF NOT EXISTS idx_presence_run ON agent_presence(run_id) WHERE run_id IS NOT NULL;
1476
- `);
1477
- },
1478
- },
1479
- {
1480
- version: 25,
1481
- description: 'Skills portability — store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate',
1482
- up: (db) => {
1483
- // ============================================================
1484
- // SKILLS CLOUD SYNC: carry skill BODIES across machines.
1485
- // The `skills` table has only ever held metadata + a `skill_path`
1486
- // pointer into ~/.copilot/skills/<slug>/SKILL.md — a pointer that is
1487
- // meaningless on another machine. To make a registered skill portable
1488
- // (this Fedora box → a Mac mini) we store the SKILL.md text itself.
1489
- //
1490
- // Additive + guarded (Article VI), the migration 20/24 style: every
1491
- // ALTER is PRAGMA-checked, nothing is renamed/dropped/rewritten, and a
1492
- // v7.0.1 DB opens under this migration with 100% of its rows preserved.
1493
- //
1494
- // content — the full SKILL.md text (NULL until backfilled;
1495
- // a skill whose file is unreadable stays NULL).
1496
- // content_sha256 — sha256 of `content`, for idempotent backfill
1497
- // (skip unchanged) and export dedup.
1498
- // content_updated_at — when `content` last changed (sync/merge aid).
1499
- //
1500
- // CRITICAL — FTS: skills_fts is an EXTERNAL-CONTENT FTS5 over
1501
- // (name, description, tags) ONLY (content='skills'). Adding plain base
1502
- // columns does NOT change the FTS table's indexed columns, so the
1503
- // external-content contract and its insert/delete/update triggers are
1504
- // untouched. (Verified by the post-migration FTS search test.)
1505
- const hasCol = (t, c) => {
1506
- try {
1507
- return db.prepare(`PRAGMA table_info(${t})`).all()
1508
- .some((x) => x.name === c);
1509
- }
1510
- catch {
1511
- return false;
1512
- }
1513
- };
1514
- if (!hasCol('skills', 'content'))
1515
- db.exec(`ALTER TABLE skills ADD COLUMN content TEXT;`);
1516
- if (!hasCol('skills', 'content_sha256'))
1517
- db.exec(`ALTER TABLE skills ADD COLUMN content_sha256 TEXT;`);
1518
- if (!hasCol('skills', 'content_updated_at'))
1519
- db.exec(`ALTER TABLE skills ADD COLUMN content_updated_at TEXT;`);
1520
- // ---- cloud-sync eligibility, private-by-default (Article I/IX) ----
1521
- // Skills are global (no project_id), so the grove (project) sync_policy
1522
- // gate cannot apply per-skill. We instead make skills sync the SAME way
1523
- // ground_truths/memory_artifacts do: a per-row `cross_project_visibility`
1524
- // marker, DEFAULT 'within' (operator-private). The sync engine's visClause
1525
- // (`cross_project_visibility IN ('org','public')`) is the egress gate —
1526
- // a brand-new skills column defaulting to 'within' means NO skill leaves
1527
- // this machine until the operator explicitly promotes it. `is_shared`
1528
- // (default 0) lands too so the team-federation gate has its flag.
1529
- // This adds skills to the existing privacy lattice without weakening the
1530
- // default, exactly per the constitution's private-by-default rule.
1531
- if (!hasCol('skills', 'cross_project_visibility')) {
1532
- db.exec(`ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';`);
1533
- }
1534
- if (!hasCol('skills', 'is_shared')) {
1535
- db.exec(`ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;`);
1536
- }
1537
- // Partial index for the egress hot path (collectChangedRows filters on
1538
- // promoted rows), migration 20/24 style — the all-'within' default bulk
1539
- // costs nothing.
1540
- db.exec(`CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';`);
1541
- },
1542
- },
1543
- {
1544
- version: 26,
1545
- description: 'Reverse-bridge rejection tombstones — remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted',
1546
- up: (db) => {
1547
- // ============================================================
1548
- // REVERSE-BRIDGE REJECTION MEMORY (security pass #2, finding #1).
1549
- //
1550
- // detectEdits() always re-emits the operator prose OUTSIDE the Wyrm
1551
- // markers as an 'outside' edit on EVERY sweep — there is no diff against
1552
- // a prior state. Re-queueing is suppressed only by existsBySig() against
1553
- // the live review queue. So if the operator REVIEWS and REJECTS (deletes)
1554
- // a reverse-bridge candidate, the sig disappears from the DB and the next
1555
- // sweep re-extracts + re-queues the identical prose — a review-queue loop
1556
- // for content the operator already said no to.
1557
- //
1558
- // Fix: a tombstone of rejected `rb:` sigs. wyrm_review's reject path
1559
- // records the sig here; makeBridgeDeps.existsBySig() additionally consults
1560
- // it, so a once-rejected outside-prose edit is remembered as rejected and
1561
- // never re-offered. Purely local, per-project, never replicated.
1562
- //
1563
- // Additive + guarded (Article VI), migration 20/24/25 style: a brand-new
1564
- // table, nothing renamed/dropped, a v7.0.x DB opens with 100% of its rows.
1565
- db.exec(`
996
+ `)}},{version:25,description:"Skills portability \u2014 store SKILL.md content (+ sha/ts) in the registry and make skills cloud-sync-eligible behind the per-row visibility gate",up:e=>{const E=(t,s)=>{try{return e.prepare(`PRAGMA table_info(${t})`).all().some(i=>i.name===s)}catch{return!1}};E("skills","content")||e.exec("ALTER TABLE skills ADD COLUMN content TEXT;"),E("skills","content_sha256")||e.exec("ALTER TABLE skills ADD COLUMN content_sha256 TEXT;"),E("skills","content_updated_at")||e.exec("ALTER TABLE skills ADD COLUMN content_updated_at TEXT;"),E("skills","cross_project_visibility")||e.exec("ALTER TABLE skills ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within';"),E("skills","is_shared")||e.exec("ALTER TABLE skills ADD COLUMN is_shared INTEGER NOT NULL DEFAULT 0;"),e.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(cross_project_visibility) WHERE cross_project_visibility != 'within';")}},{version:26,description:"Reverse-bridge rejection tombstones \u2014 remember a once-rejected outside-prose sig so the next sweep never re-queues content the operator already deleted",up:e=>{e.exec(`
1566
997
  CREATE TABLE IF NOT EXISTS reverse_bridge_tombstones (
1567
998
  project_id INTEGER NOT NULL,
1568
999
  sig TEXT NOT NULL,
1569
1000
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
1570
1001
  PRIMARY KEY (project_id, sig)
1571
1002
  );
1572
- `);
1573
- },
1574
- },
1575
- ];
1576
- /**
1577
- * Run all pending migrations on the database.
1578
- * Creates the schema_versions table if it doesn't exist.
1579
- * Returns the list of migrations that were applied.
1580
- */
1581
- export function runMigrations(db) {
1582
- // Create the version tracking table (always safe to re-run)
1583
- db.exec(`
1003
+ `)}}];function d(e){e.exec(`
1584
1004
  CREATE TABLE IF NOT EXISTS schema_versions (
1585
1005
  version INTEGER PRIMARY KEY,
1586
1006
  description TEXT NOT NULL,
1587
1007
  applied_at TEXT DEFAULT (datetime('now'))
1588
1008
  );
1589
- `);
1590
- const currentVersion = db.prepare('SELECT COALESCE(MAX(version), 0) as v FROM schema_versions').get().v;
1591
- // Always apply in ascending version order, regardless of array order in
1592
- // `migrations` (defensive — v3.9 added a migration that was authored out
1593
- // of array order). Sorted copy so we don't mutate the exported array.
1594
- const pending = migrations
1595
- .filter(m => m.version > currentVersion)
1596
- .slice()
1597
- .sort((a, b) => a.version - b.version);
1598
- if (pending.length === 0)
1599
- return [];
1600
- const applied = [];
1601
- const insertVersion = db.prepare('INSERT INTO schema_versions (version, description) VALUES (?, ?)');
1602
- for (const migration of pending) {
1603
- // Each migration runs in its own transaction for atomicity
1604
- const runOne = db.transaction(() => {
1605
- migration.up(db);
1606
- insertVersion.run(migration.version, migration.description);
1607
- });
1608
- runOne();
1609
- applied.push(migration);
1610
- }
1611
- return applied;
1612
- }
1613
- /**
1614
- * Get the current schema version.
1615
- */
1616
- export function getSchemaVersion(db) {
1617
- try {
1618
- return db.prepare('SELECT COALESCE(MAX(version), 0) as v FROM schema_versions').get().v;
1619
- }
1620
- catch {
1621
- return 0; // schema_versions table doesn't exist yet
1622
- }
1623
- }
1624
- //# sourceMappingURL=migrations.js.map
1009
+ `);const E=e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v,t=n.filter(T=>T.version>E).slice().sort((T,o)=>T.version-o.version);if(t.length===0)return[];const s=[],i=e.prepare("INSERT INTO schema_versions (version, description) VALUES (?, ?)");for(const T of t)e.transaction(()=>{T.up(e),i.run(T.version,T.description)})(),s.push(T);return s}function N(e){try{return e.prepare("SELECT COALESCE(MAX(version), 0) as v FROM schema_versions").get().v}catch{return 0}}export{N as getSchemaVersion,n as migrations,d as runMigrations};