wyrm-mcp 7.3.2 → 7.3.3
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 -14
- package/dist/activation.js +59 -1
- package/dist/agent-daemon.js +281 -4
- package/dist/agent-loop.js +332 -7
- package/dist/analytics.js +236 -13
- package/dist/attribution.js +49 -1
- package/dist/audit.js +457 -2
- package/dist/auto-capture.js +138 -3
- package/dist/auto-orchestrator.js +325 -1
- package/dist/autoconfig.d.ts +50 -0
- package/dist/autoconfig.d.ts.map +1 -1
- package/dist/autoconfig.js +1115 -39
- package/dist/autoconfig.js.map +1 -1
- package/dist/buddy-runner.js +109 -1
- package/dist/buddy.js +564 -14
- package/dist/build-flags.js +15 -1
- package/dist/capabilities.js +183 -3
- package/dist/capture.js +56 -1
- package/dist/causality.js +148 -8
- package/dist/cli.js +281 -20
- package/dist/cloud/cli.js +541 -5
- package/dist/cloud/client.js +221 -1
- package/dist/cloud/crypto.js +85 -1
- package/dist/cloud/machine-id.js +113 -2
- package/dist/cloud/recovery.js +60 -1
- package/dist/cloud/sync-engine.js +543 -7
- package/dist/cloud-backup.js +579 -5
- package/dist/cloud-profile.js +138 -1
- package/dist/cloud-sync-entrypoint.js +47 -1
- package/dist/cloud-sync.js +309 -2
- package/dist/connectors/bridge-source.d.ts +46 -0
- package/dist/connectors/bridge-source.d.ts.map +1 -0
- package/dist/connectors/bridge-source.js +77 -0
- package/dist/connectors/bridge-source.js.map +1 -0
- package/dist/connectors/index.d.ts +24 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +69 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/ingest.d.ts +16 -0
- package/dist/connectors/ingest.d.ts.map +1 -0
- package/dist/connectors/ingest.js +116 -0
- package/dist/connectors/ingest.js.map +1 -0
- package/dist/connectors/types.d.ts +99 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +17 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/constellation.js +168 -12
- package/dist/content-signature.js +45 -1
- package/dist/context-build-budgeted.js +144 -4
- package/dist/context-ranking.js +69 -1
- package/dist/crypto.js +179 -1
- package/dist/daemon-write-endpoint.js +290 -1
- package/dist/daemon-writer.js +406 -2
- package/dist/database.js +1278 -53
- package/dist/deprecations.js +162 -2
- package/dist/design.js +141 -13
- package/dist/event-replication.js +112 -1
- package/dist/events-sse.js +43 -7
- package/dist/events.js +238 -6
- package/dist/failure-patterns.d.ts +107 -0
- package/dist/failure-patterns.d.ts.map +1 -1
- package/dist/failure-patterns.js +924 -43
- package/dist/failure-patterns.js.map +1 -1
- package/dist/federation.js +236 -12
- package/dist/goals.js +101 -13
- package/dist/golden.js +355 -3
- package/dist/handlers/agent.js +165 -4
- package/dist/handlers/alias-adapters.js +129 -1
- package/dist/handlers/aliases.js +171 -1
- package/dist/handlers/audit.js +87 -1
- package/dist/handlers/boundary.js +221 -1
- package/dist/handlers/capture.js +1114 -73
- package/dist/handlers/causality.js +119 -9
- package/dist/handlers/cloud.js +382 -85
- package/dist/handlers/companion.js +459 -28
- package/dist/handlers/datalake.js +187 -7
- package/dist/handlers/dispatch-context.js +22 -0
- package/dist/handlers/entity.js +256 -25
- package/dist/handlers/events.js +335 -16
- package/dist/handlers/failure.d.ts.map +1 -1
- package/dist/handlers/failure.js +408 -13
- package/dist/handlers/failure.js.map +1 -1
- package/dist/handlers/goals.js +296 -4
- package/dist/handlers/intelligence.js +681 -126
- package/dist/handlers/invoicing.js +70 -1
- package/dist/handlers/mcpclient.js +137 -6
- package/dist/handlers/orchestration.js +125 -40
- package/dist/handlers/output-schemas.js +24 -1
- package/dist/handlers/presence.js +99 -3
- package/dist/handlers/project.js +182 -28
- package/dist/handlers/prompts.js +157 -6
- package/dist/handlers/quest.js +224 -4
- package/dist/handlers/recall.js +237 -13
- package/dist/handlers/registry.js +167 -1
- package/dist/handlers/resources.js +288 -1
- package/dist/handlers/review.js +74 -11
- package/dist/handlers/run.js +498 -16
- package/dist/handlers/search.js +338 -15
- package/dist/handlers/session.js +643 -31
- package/dist/handlers/share.js +184 -8
- package/dist/handlers/shims.js +464 -1
- package/dist/handlers/skill.js +449 -67
- package/dist/handlers/survivors.js +120 -1
- package/dist/handlers/symbols.js +109 -8
- package/dist/handlers/syncops.js +302 -4
- package/dist/handlers/types.js +27 -1
- package/dist/harvest.js +191 -5
- package/dist/hours.js +156 -7
- package/dist/http-auth.js +321 -3
- package/dist/http-fast.js +1302 -22
- package/dist/icons.js +47 -1
- package/dist/importers.js +268 -1
- package/dist/index.js +840 -2
- package/dist/indexer.js +145 -4
- package/dist/intelligence.js +261 -31
- package/dist/internal-dispatch.js +212 -3
- package/dist/keyset.js +110 -1
- package/dist/knowledge-graph.js +176 -12
- package/dist/license.js +441 -2
- package/dist/logger.js +199 -2
- package/dist/maintenance.js +148 -2
- package/dist/mcp-client.js +262 -6
- package/dist/memory-artifacts.js +596 -32
- package/dist/migrate-prompt.js +124 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +799 -42
- package/dist/migrations.js.map +1 -1
- package/dist/performance.js +228 -1
- package/dist/presence.js +140 -11
- package/dist/priority-embed.js +164 -5
- package/dist/providers/embedding-provider.js +196 -1
- package/dist/readonly-gate.js +29 -1
- package/dist/receipt.js +43 -1
- package/dist/rehydration.js +157 -9
- package/dist/reindex.js +88 -1
- package/dist/render-target.js +544 -21
- package/dist/render.js +280 -4
- package/dist/repl-guard.js +173 -1
- package/dist/replication-daemon-entrypoint.js +31 -1
- package/dist/replication-daemon.js +262 -2
- package/dist/rerank.js +142 -1
- package/dist/resilience.js +591 -1
- package/dist/reverse-bridge.js +360 -5
- package/dist/security.js +244 -1
- package/dist/session-seen.js +51 -3
- package/dist/setup.js +260 -1
- package/dist/skill-author.js +168 -5
- package/dist/spec-kit.js +191 -1
- package/dist/sqlite-busy.js +154 -1
- package/dist/statusline.js +315 -11
- package/dist/sub-agent.js +262 -13
- package/dist/summarizer.js +139 -13
- package/dist/symbols.js +283 -7
- package/dist/sync.js +359 -5
- package/dist/tasks-dispatch.js +84 -1
- package/dist/tasks.js +282 -1
- package/dist/token-budget.js +143 -1
- package/dist/tool-analytics.js +129 -7
- package/dist/tool-annotations.js +365 -1
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +75 -1
- package/dist/trace-harvest.js +244 -6
- package/dist/types.js +30 -1
- package/dist/ui-dashboard.js +50 -41
- package/dist/ulid.js +81 -1
- package/dist/usage-tracker.js +66 -1
- package/dist/validate.js +129 -1
- package/dist/vault.js +534 -1
- package/dist/vector-init.js +67 -1
- package/dist/vectors.js +184 -3
- package/dist/version-check.js +136 -4
- package/dist/visibility.js +155 -19
- package/dist/wyrm-cli.js +2845 -101
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.d.ts.map +1 -1
- package/dist/wyrm-guard.js +475 -14
- package/dist/wyrm-guard.js.map +1 -1
- package/dist/wyrm-loop.js +150 -3
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +11 -1
- package/dist/wyrm-statusline.js +56 -4
- package/dist/wyrm-ui.js +77 -9
- package/package.json +1 -1
package/dist/migrations.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Wyrm Database Migrations — Versioned schema management
|
|
3
|
+
*
|
|
4
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
+
* @license Proprietary — (c) 2026 Ghost Protocol (Pvt) Ltd. All rights reserved. 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(`
|
|
2
20
|
-- Core tables
|
|
3
21
|
CREATE TABLE IF NOT EXISTS projects (
|
|
4
22
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -197,7 +215,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
197
215
|
VALUES('delete', old.id, old.key, old.value);
|
|
198
216
|
INSERT INTO data_lake_fts(rowid, key, value) VALUES (new.id, new.key, new.value);
|
|
199
217
|
END;
|
|
200
|
-
`)
|
|
218
|
+
`);
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
version: 2,
|
|
223
|
+
description: 'Add usage_events table for analytics tracking',
|
|
224
|
+
up: (db) => {
|
|
225
|
+
db.exec(`
|
|
201
226
|
CREATE TABLE IF NOT EXISTS usage_events (
|
|
202
227
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
203
228
|
tool_name TEXT NOT NULL,
|
|
@@ -210,7 +235,34 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
210
235
|
timestamp TEXT DEFAULT (datetime('now'))
|
|
211
236
|
);
|
|
212
237
|
CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage_events(timestamp);
|
|
213
|
-
`)
|
|
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(`
|
|
214
266
|
-- Entities: named things that can be linked together
|
|
215
267
|
CREATE TABLE IF NOT EXISTS entities (
|
|
216
268
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -239,7 +291,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
239
291
|
CREATE INDEX IF NOT EXISTS idx_entity_aliases_entity ON entity_aliases(entity_id);
|
|
240
292
|
CREATE INDEX IF NOT EXISTS idx_entity_aliases_alias ON entity_aliases(alias);
|
|
241
293
|
|
|
242
|
-
-- Relationships between entities
|
|
294
|
+
-- Relationships between entities — directed, typed, with provenance
|
|
243
295
|
CREATE TABLE IF NOT EXISTS relationships (
|
|
244
296
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
245
297
|
project_id INTEGER NOT NULL,
|
|
@@ -285,7 +337,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
285
337
|
INSERT INTO entities_fts(rowid, name, type, metadata)
|
|
286
338
|
VALUES (new.id, new.name, new.type, new.metadata);
|
|
287
339
|
END;
|
|
288
|
-
`)
|
|
340
|
+
`);
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
version: 5,
|
|
345
|
+
description: 'Memory artifacts — distilled knowledge for intelligence amplification',
|
|
346
|
+
up: (db) => {
|
|
347
|
+
db.exec(`
|
|
289
348
|
-- Memory artifacts: distilled problem-solution records, lessons, patterns, anti-patterns.
|
|
290
349
|
-- Used by wyrm_context_build to assemble task-specific memory briefs for AI models.
|
|
291
350
|
CREATE TABLE IF NOT EXISTS memory_artifacts (
|
|
@@ -340,7 +399,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
340
399
|
INSERT INTO memory_artifacts_fts(rowid, problem, constraints, validated_fix, why_it_worked, tags)
|
|
341
400
|
VALUES (new.id, new.problem, new.constraints, new.validated_fix, new.why_it_worked, new.tags);
|
|
342
401
|
END;
|
|
343
|
-
`)
|
|
402
|
+
`);
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
version: 6,
|
|
407
|
+
description: 'Intelligence amplification — ground truths, reasoning scaffolds, distillation review queue',
|
|
408
|
+
up: (db) => {
|
|
409
|
+
db.exec(`
|
|
344
410
|
-- Ground truths: authoritative, versioned project facts.
|
|
345
411
|
-- Use is_current=1 for the live value. Superseded rows kept for history.
|
|
346
412
|
CREATE TABLE IF NOT EXISTS ground_truths (
|
|
@@ -410,12 +476,33 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
410
476
|
-- needs_review=1 means it was auto-created by wyrm_distill and awaits approval
|
|
411
477
|
ALTER TABLE memory_artifacts ADD COLUMN needs_review INTEGER DEFAULT 0;
|
|
412
478
|
CREATE INDEX IF NOT EXISTS idx_artifacts_review ON memory_artifacts(project_id, needs_review);
|
|
413
|
-
`)
|
|
479
|
+
`);
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
version: 7,
|
|
484
|
+
description: 'Ground truth TTL — temporal decay support for staleness detection',
|
|
485
|
+
up: (db) => {
|
|
486
|
+
db.exec(`
|
|
414
487
|
ALTER TABLE ground_truths ADD COLUMN ttl_days INTEGER CHECK(ttl_days IS NULL OR ttl_days > 0);
|
|
415
|
-
`)
|
|
488
|
+
`);
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
version: 8,
|
|
493
|
+
description: 'Memory artifact decay — access tracking for auto-prune',
|
|
494
|
+
up: (db) => {
|
|
495
|
+
db.exec(`
|
|
416
496
|
ALTER TABLE memory_artifacts ADD COLUMN last_accessed_at TEXT DEFAULT (datetime('now'));
|
|
417
497
|
ALTER TABLE memory_artifacts ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0;
|
|
418
|
-
`)
|
|
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(`
|
|
419
506
|
-- ============================================================
|
|
420
507
|
-- Tier 1.1: Counter-pattern detection
|
|
421
508
|
-- Records edits/commands that failed so the predictive push can
|
|
@@ -562,9 +649,16 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
562
649
|
CREATE INDEX IF NOT EXISTS idx_toolcall_name ON tool_call_log(tool_name, called_at DESC);
|
|
563
650
|
CREATE INDEX IF NOT EXISTS idx_toolcall_project ON tool_call_log(project_id, called_at DESC);
|
|
564
651
|
CREATE INDEX IF NOT EXISTS idx_toolcall_success ON tool_call_log(success, called_at DESC);
|
|
565
|
-
`)
|
|
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(`
|
|
566
660
|
-- ============================================================
|
|
567
|
-
-- Tier 2.8: Federated team Wyrm
|
|
661
|
+
-- Tier 2.8: Federated team Wyrm — selective sharing
|
|
568
662
|
-- A single "is_shared" flag on the major user-facing tables.
|
|
569
663
|
-- A team server pulls only is_shared=1 rows during sync.
|
|
570
664
|
-- ============================================================
|
|
@@ -614,7 +708,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
614
708
|
ON sync_log(remote_origin, direction, synced_at DESC);
|
|
615
709
|
|
|
616
710
|
-- ============================================================
|
|
617
|
-
-- Tier 3.9: Compliance audit trail
|
|
711
|
+
-- Tier 3.9: Compliance audit trail — hash-chained event log
|
|
618
712
|
-- Each row references the hash of the previous row. Tampering with
|
|
619
713
|
-- any historical entry breaks the chain. Combined with optional
|
|
620
714
|
-- Ed25519 signing this provides a SOC2/HIPAA-grade audit surface.
|
|
@@ -636,7 +730,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
636
730
|
CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_log(actor, logged_at DESC);
|
|
637
731
|
|
|
638
732
|
-- ============================================================
|
|
639
|
-
-- Tier 3.10: Sub-agent embedding
|
|
733
|
+
-- Tier 3.10: Sub-agent embedding — wyrm_ask query log
|
|
640
734
|
-- Tracks every wyrm_ask invocation: query, context window, model,
|
|
641
735
|
-- response, latency, tokens. Cost tracking + answer quality
|
|
642
736
|
-- analysis. Doubles as a public-facing audit if wyrm_ask is
|
|
@@ -659,9 +753,16 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
659
753
|
);
|
|
660
754
|
CREATE INDEX IF NOT EXISTS idx_llm_project ON llm_query_log(project_id, asked_at DESC);
|
|
661
755
|
CREATE INDEX IF NOT EXISTS idx_llm_model ON llm_query_log(model, asked_at DESC);
|
|
662
|
-
`)
|
|
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(`
|
|
663
764
|
-- ============================================================
|
|
664
|
-
-- v5.0.0: Goals
|
|
765
|
+
-- v5.0.0: Goals — persistent objectives Wyrm pursues
|
|
665
766
|
-- ============================================================
|
|
666
767
|
CREATE TABLE IF NOT EXISTS goals (
|
|
667
768
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -709,7 +810,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
709
810
|
CREATE INDEX IF NOT EXISTS idx_goal_iter_goal ON goal_iterations(goal_id, iteration_num);
|
|
710
811
|
|
|
711
812
|
-- ============================================================
|
|
712
|
-
-- v5.0.0: Outbound MCP clients
|
|
813
|
+
-- v5.0.0: Outbound MCP clients — Wyrm calls other servers
|
|
713
814
|
-- Config rows for MCP servers Wyrm should connect to (stdio).
|
|
714
815
|
-- ============================================================
|
|
715
816
|
CREATE TABLE IF NOT EXISTS mcp_client_configs (
|
|
@@ -717,7 +818,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
717
818
|
server_name TEXT UNIQUE NOT NULL, -- 'github', 'slack', 'phantomdragon', etc.
|
|
718
819
|
command TEXT NOT NULL, -- 'npx' or absolute path
|
|
719
820
|
args TEXT, -- JSON array of args
|
|
720
|
-
env TEXT, -- JSON object of env vars (NEVER store secrets here in plaintext for prod
|
|
821
|
+
env TEXT, -- JSON object of env vars (NEVER store secrets here in plaintext for prod — but local dev is fine)
|
|
721
822
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
722
823
|
added_at TEXT DEFAULT (datetime('now')),
|
|
723
824
|
last_used_at TEXT,
|
|
@@ -738,7 +839,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
738
839
|
CREATE INDEX IF NOT EXISTS idx_ext_call_server ON external_call_log(server_name, called_at DESC);
|
|
739
840
|
|
|
740
841
|
-- ============================================================
|
|
741
|
-
-- v5.0.0: Agent actions
|
|
842
|
+
-- v5.0.0: Agent actions — the audit-trail of autonomous loop ticks
|
|
742
843
|
-- Higher-level than goal_iterations; one row per wyrm-loop tick.
|
|
743
844
|
-- ============================================================
|
|
744
845
|
CREATE TABLE IF NOT EXISTS agent_actions (
|
|
@@ -753,7 +854,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
753
854
|
);
|
|
754
855
|
CREATE INDEX IF NOT EXISTS idx_agent_actions_goal ON agent_actions(goal_id, ran_at DESC);
|
|
755
856
|
CREATE INDEX IF NOT EXISTS idx_agent_actions_actor ON agent_actions(actor, ran_at DESC);
|
|
756
|
-
`)
|
|
857
|
+
`);
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
version: 12,
|
|
862
|
+
description: 'session_seen_artifacts (spec 014) + token-budget telemetry columns',
|
|
863
|
+
up: (db) => {
|
|
864
|
+
db.exec(`
|
|
757
865
|
-- session_seen_artifacts: per-session dedup so wyrm_context_build
|
|
758
866
|
-- doesn't re-emit fully-rendered content the AI already saw this
|
|
759
867
|
-- session. Pruned by wyrm_maintenance after WYRM_SEEN_TTL_DAYS
|
|
@@ -770,7 +878,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
770
878
|
ON session_seen_artifacts(session_id, shown_at DESC);
|
|
771
879
|
CREATE INDEX IF NOT EXISTS idx_session_seen_prune
|
|
772
880
|
ON session_seen_artifacts(shown_at);
|
|
773
|
-
`)
|
|
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(`
|
|
774
889
|
CREATE TABLE IF NOT EXISTS design_tokens (
|
|
775
890
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
776
891
|
project_id INTEGER NOT NULL,
|
|
@@ -821,7 +936,14 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
821
936
|
INSERT INTO design_references_fts(rowid, title, notes, tags)
|
|
822
937
|
VALUES (new.id, new.title, new.notes, new.tags);
|
|
823
938
|
END;
|
|
824
|
-
`)
|
|
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(`
|
|
825
947
|
-- Token-savings telemetry: every Wyrm tool that has a counterfactual
|
|
826
948
|
-- logs an estimate so the statusline and digest can show real ROI.
|
|
827
949
|
CREATE TABLE IF NOT EXISTS token_savings_log (
|
|
@@ -844,10 +966,36 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
844
966
|
ON token_savings_log(session_id, timestamp DESC);
|
|
845
967
|
CREATE INDEX IF NOT EXISTS idx_savings_category
|
|
846
968
|
ON token_savings_log(category, timestamp DESC);
|
|
847
|
-
`);
|
|
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(`
|
|
848
986
|
ALTER TABLE ${t} ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
|
|
849
987
|
CHECK (cross_project_visibility IN ('within', 'org', 'public'));
|
|
850
|
-
`)
|
|
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(`
|
|
851
999
|
-- Node-local KV (stable device id for cross-device event identity, etc.)
|
|
852
1000
|
CREATE TABLE IF NOT EXISTS wyrm_meta (
|
|
853
1001
|
key TEXT PRIMARY KEY,
|
|
@@ -856,7 +1004,7 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
856
1004
|
|
|
857
1005
|
-- Append-only event log (Live Memory v6.4). The existing tables stay the
|
|
858
1006
|
-- system of record; this is a derived stream for live propagation. Writes
|
|
859
|
-
-- emit here as a FAILURE-ISOLATED side effect (see events.ts)
|
|
1007
|
+
-- emit here as a FAILURE-ISOLATED side effect (see events.ts) — a broken
|
|
860
1008
|
-- events table can never regress the canonical write. 'cursor' is the
|
|
861
1009
|
-- LOCAL subscription cursor (monotonic per node); cross-device identity is
|
|
862
1010
|
-- (origin_device, origin_seq), with replication via INSERT OR IGNORE.
|
|
@@ -877,7 +1025,19 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
877
1025
|
);
|
|
878
1026
|
CREATE INDEX IF NOT EXISTS idx_events_project_cursor ON events(project_id, cursor);
|
|
879
1027
|
CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind, created_at);
|
|
880
|
-
`)
|
|
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(`
|
|
881
1041
|
DROP TABLE IF EXISTS quests_fts;
|
|
882
1042
|
CREATE VIRTUAL TABLE quests_fts USING fts5(
|
|
883
1043
|
title, description, content='quests', content_rowid='id',
|
|
@@ -905,13 +1065,62 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
905
1065
|
tokenize='porter unicode61'
|
|
906
1066
|
);
|
|
907
1067
|
INSERT INTO skills_fts(skills_fts) VALUES('rebuild');
|
|
908
|
-
`)
|
|
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(`
|
|
909
1083
|
ALTER TABLE decision_edges ADD COLUMN cross_project_visibility TEXT NOT NULL DEFAULT 'within'
|
|
910
1084
|
CHECK (cross_project_visibility IN ('within', 'org', 'public'));
|
|
911
|
-
`)
|
|
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(`
|
|
912
1108
|
ALTER TABLE skills ADD COLUMN tier TEXT NOT NULL DEFAULT 'atomic'
|
|
913
1109
|
CHECK (tier IN ('atomic', 'mega', 'god'));
|
|
914
|
-
`)
|
|
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(`
|
|
915
1124
|
CREATE TABLE IF NOT EXISTS specs (
|
|
916
1125
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
917
1126
|
project_id INTEGER NOT NULL,
|
|
@@ -926,11 +1135,86 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
926
1135
|
);
|
|
927
1136
|
CREATE INDEX IF NOT EXISTS idx_specs_project ON specs(project_id);
|
|
928
1137
|
CREATE INDEX IF NOT EXISTS idx_specs_dir ON specs(spec_dir);
|
|
929
|
-
`)
|
|
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(`
|
|
930
1157
|
ALTER TABLE projects ADD COLUMN sync_policy TEXT NOT NULL DEFAULT 'private'
|
|
931
1158
|
CHECK (sync_policy IN ('private', 'cloud', 'team'));
|
|
932
|
-
`);
|
|
933
|
-
|
|
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(`
|
|
934
1218
|
CREATE TABLE IF NOT EXISTS runs (
|
|
935
1219
|
run_id TEXT PRIMARY KEY,
|
|
936
1220
|
parent_run_id TEXT,
|
|
@@ -955,14 +1239,98 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
955
1239
|
FOREIGN KEY (run_id) REFERENCES runs(run_id) ON DELETE CASCADE
|
|
956
1240
|
);
|
|
957
1241
|
CREATE INDEX IF NOT EXISTS idx_run_agents_agent ON run_agents(agent_id);
|
|
958
|
-
`);
|
|
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(`
|
|
959
1284
|
CREATE INDEX IF NOT EXISTS idx_failure_run ON failure_patterns(run_id) WHERE run_id IS NOT NULL;
|
|
960
1285
|
CREATE INDEX IF NOT EXISTS idx_events_run ON events(run_id) WHERE run_id IS NOT NULL;
|
|
961
1286
|
CREATE INDEX IF NOT EXISTS idx_artifacts_run ON memory_artifacts(run_id) WHERE run_id IS NOT NULL;
|
|
962
|
-
`)
|
|
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(`
|
|
963
1305
|
ALTER TABLE failure_patterns ADD COLUMN quarantine_scope TEXT NOT NULL DEFAULT 'project'
|
|
964
1306
|
CHECK (quarantine_scope IN ('run', 'project', 'global'));
|
|
965
|
-
`)
|
|
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(`
|
|
966
1334
|
CREATE TABLE IF NOT EXISTS failure_confirmations (
|
|
967
1335
|
failure_id INTEGER NOT NULL,
|
|
968
1336
|
agent_id TEXT NOT NULL,
|
|
@@ -971,7 +1339,42 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
971
1339
|
PRIMARY KEY (failure_id, agent_id),
|
|
972
1340
|
FOREIGN KEY (failure_id) REFERENCES failure_patterns(id) ON DELETE CASCADE
|
|
973
1341
|
);
|
|
974
|
-
`)
|
|
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(`
|
|
975
1378
|
CREATE TABLE IF NOT EXISTS failure_blocks (
|
|
976
1379
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
977
1380
|
failure_id INTEGER NOT NULL,
|
|
@@ -981,7 +1384,41 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
981
1384
|
blocked_at TEXT DEFAULT (datetime('now')),
|
|
982
1385
|
FOREIGN KEY (failure_id) REFERENCES failure_patterns(id) ON DELETE CASCADE
|
|
983
1386
|
);
|
|
984
|
-
`)
|
|
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(`
|
|
985
1422
|
CREATE TABLE IF NOT EXISTS run_briefs (
|
|
986
1423
|
run_id TEXT NOT NULL,
|
|
987
1424
|
role TEXT NOT NULL DEFAULT '',
|
|
@@ -990,17 +1427,169 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
990
1427
|
created_at TEXT DEFAULT (datetime('now')),
|
|
991
1428
|
PRIMARY KEY (run_id, role)
|
|
992
1429
|
);
|
|
993
|
-
`)
|
|
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(`
|
|
994
1474
|
CREATE INDEX IF NOT EXISTS idx_quest_claims_run ON quest_claims(run_id) WHERE run_id IS NOT NULL;
|
|
995
1475
|
CREATE INDEX IF NOT EXISTS idx_presence_run ON agent_presence(run_id) WHERE run_id IS NOT NULL;
|
|
996
|
-
`)
|
|
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(`
|
|
997
1566
|
CREATE TABLE IF NOT EXISTS reverse_bridge_tombstones (
|
|
998
1567
|
project_id INTEGER NOT NULL,
|
|
999
1568
|
sig TEXT NOT NULL,
|
|
1000
1569
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1001
1570
|
PRIMARY KEY (project_id, sig)
|
|
1002
1571
|
);
|
|
1003
|
-
`)
|
|
1572
|
+
`);
|
|
1573
|
+
},
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
version: 27,
|
|
1577
|
+
description: 'Continuity Layer — the rolling per-project living thread ("where we left off / what we were talking and thinking about"), captured continuously and restored on every new session, for any agent/system that primes Wyrm',
|
|
1578
|
+
up: (db) => {
|
|
1579
|
+
// ============================================================
|
|
1580
|
+
// CONTINUITY LAYER. Ground truths/quests/memories capture CURATED facts;
|
|
1581
|
+
// they do not capture the LIVING THREAD of a working session — what we were
|
|
1582
|
+
// just discussing, deciding, and thinking through. So a fresh chat (or a
|
|
1583
|
+
// different surface: phone, Hermes, Ember, Gemini) re-opens cold.
|
|
1584
|
+
//
|
|
1585
|
+
// One evolving narrative per project: appended to continuously and bounded
|
|
1586
|
+
// (oldest trimmed with a marker) so it never grows unbounded but never loses
|
|
1587
|
+
// the gist. wyrm_session_prime / rehydrate restore it verbatim up front; the
|
|
1588
|
+
// row syncs like any other so every device/agent inherits the same thread.
|
|
1589
|
+
//
|
|
1590
|
+
// Additive + guarded (Article VI), migration 25/26 style: brand-new table,
|
|
1591
|
+
// nothing renamed/dropped; an older DB opens with 100% of its rows.
|
|
1592
|
+
db.exec(`
|
|
1004
1593
|
CREATE TABLE IF NOT EXISTS project_continuity (
|
|
1005
1594
|
project_id INTEGER PRIMARY KEY,
|
|
1006
1595
|
thread TEXT NOT NULL DEFAULT '',
|
|
@@ -1009,7 +1598,46 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
1009
1598
|
turn_count INTEGER NOT NULL DEFAULT 0,
|
|
1010
1599
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
1011
1600
|
);
|
|
1012
|
-
`)
|
|
1601
|
+
`);
|
|
1602
|
+
},
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
version: 28,
|
|
1606
|
+
description: 'Routing memory — record which model/tool succeeded (or failed) per task_type so the dragon-cli multi-model router can do negative learning over past routing outcomes',
|
|
1607
|
+
up: (db) => {
|
|
1608
|
+
// ============================================================
|
|
1609
|
+
// ROUTING MEMORY (Ghost Protocol local multi-model ROUTER, subsystem
|
|
1610
|
+
// wyrm-routing-rerank). The dragon-cli router classifies a task into a
|
|
1611
|
+
// task_type, selects a model + tool set, delegates, and logs the outcome
|
|
1612
|
+
// to ~/.dragon/routing.jsonl. That trace is LOCAL to one machine and one
|
|
1613
|
+
// process — it cannot inform another agent, a fresh CLI session, or the
|
|
1614
|
+
// fleet. This table is the durable, queryable home for the SAME signal so
|
|
1615
|
+
// the router can ask Wyrm "which model/tool succeeded for THIS task_type"
|
|
1616
|
+
// and steer toward winners + away from past failures (negative learning).
|
|
1617
|
+
//
|
|
1618
|
+
// One row per routing DECISION+OUTCOME (an append-only event log, like
|
|
1619
|
+
// the routing.jsonl line that produced it):
|
|
1620
|
+
// project_id — the project the task ran under (-1 = global/unscoped;
|
|
1621
|
+
// NOT an FK so a router that hasn't registered a project
|
|
1622
|
+
// can still record, and a project delete never orphans
|
|
1623
|
+
// the routing history — attribution metadata must never
|
|
1624
|
+
// block or cascade, the migration 20/24 soft-ref rule).
|
|
1625
|
+
// task_type — the router's classification (e.g. 'reason', 'tools',
|
|
1626
|
+
// 'code', 'chat'); the recall key.
|
|
1627
|
+
// model_id — the model that ran it (e.g. 'vibethinker', 'mistral-
|
|
1628
|
+
// nemo', 'ember', 'ollama:qwen2.5:1.5b').
|
|
1629
|
+
// tool_names — comma-joined tools used on that run (NULL when none).
|
|
1630
|
+
// success — 1 = the routed run succeeded, 0 = it failed. The whole
|
|
1631
|
+
// point of negative learning: a 0 is as valuable as a 1.
|
|
1632
|
+
// latency_ms — optional wall-clock of the run (NULL when not measured)
|
|
1633
|
+
// — lets recall break ties toward the FASTER winner.
|
|
1634
|
+
// ts — when it ran (router clock). Aggregation/recency use it.
|
|
1635
|
+
//
|
|
1636
|
+
// Additive + guarded (Article VI), migration 25/26/27 style: a brand-new
|
|
1637
|
+
// table, nothing renamed/dropped, an older DB opens with 100% of its
|
|
1638
|
+
// rows. Indexed for the hot recall path: (project_id, task_type) is the
|
|
1639
|
+
// exact WHERE the router queries by, with ts DESC for recency windows.
|
|
1640
|
+
db.exec(`
|
|
1013
1641
|
CREATE TABLE IF NOT EXISTS routing_memory (
|
|
1014
1642
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1015
1643
|
project_id INTEGER NOT NULL DEFAULT -1,
|
|
@@ -1024,10 +1652,139 @@ const n=[{version:1,description:"Baseline schema \u2014 projects, sessions, ques
|
|
|
1024
1652
|
ON routing_memory(project_id, task_type, ts DESC);
|
|
1025
1653
|
CREATE INDEX IF NOT EXISTS idx_routing_memory_task
|
|
1026
1654
|
ON routing_memory(task_type, ts DESC);
|
|
1027
|
-
`)
|
|
1655
|
+
`);
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
version: 29,
|
|
1660
|
+
description: 'Failure Firewall v2 — content tier: a normalized signature of the CODE that broke, so the opt-in PreToolUse guard can block an edit that re-introduces a recorded failure pattern (not just an identical target path)',
|
|
1661
|
+
up: (db) => {
|
|
1662
|
+
// Additive + guarded (Article VI): one nullable column, nothing renamed or
|
|
1663
|
+
// dropped — an older DB opens with 100% of its rows; content_sig is NULL on
|
|
1664
|
+
// every existing failure (the content tier only matches rows recorded WITH
|
|
1665
|
+
// code content, and is itself opt-in via WYRM_GUARD_CONTENT in the guard).
|
|
1666
|
+
// IDEMPOTENT: the grove-sync-policy test re-runs migrations >= 19, and
|
|
1667
|
+
// SQLite has no `ADD COLUMN IF NOT EXISTS`, so only add when absent.
|
|
1668
|
+
const hasCol = db.prepare(`PRAGMA table_info(failure_patterns)`).all()
|
|
1669
|
+
.some((c) => c.name === 'content_sig');
|
|
1670
|
+
if (!hasCol)
|
|
1671
|
+
db.exec(`ALTER TABLE failure_patterns ADD COLUMN content_sig TEXT;`);
|
|
1672
|
+
},
|
|
1673
|
+
},
|
|
1674
|
+
{
|
|
1675
|
+
version: 30,
|
|
1676
|
+
description: 'Failure Firewall staleness oracle — failure_anchors: the source files a failure was recorded against (project-relative path + normalized-content sha + stat fast-path fields), so the firewall can tell a still-true block from one whose source has since changed and downgrade it to advisory',
|
|
1677
|
+
up: (db) => {
|
|
1678
|
+
// ============================================================
|
|
1679
|
+
// STALENESS ANCHORS (Failure Firewall staleness oracle). A recorded
|
|
1680
|
+
// failure can be ANCHORED to the source files it is about (the
|
|
1681
|
+
// undeclared `files` param of wyrm_failure_record — the content-param
|
|
1682
|
+
// precedent, T022 lean-union rule: the advertised ListTools surface
|
|
1683
|
+
// does not change). At check time the shared oracle
|
|
1684
|
+
// (FailurePatterns.staleAnchorIds) stats each anchor: size+mtime_ns
|
|
1685
|
+
// equal ⇒ fresh with zero hashing; on stat drift it re-hashes the
|
|
1686
|
+
// NORMALIZED content (normalizeContentSignature — whitespace/reformat
|
|
1687
|
+
// churn never false-flags) and only a POSITIVE hash mismatch marks the
|
|
1688
|
+
// match stale, downgrading a file/symbol-scoped hard block to advisory
|
|
1689
|
+
// (stale_anchor). Exact command/edit signature matches keep their
|
|
1690
|
+
// block — code moving on does not unprove a command failure
|
|
1691
|
+
// (PROTOCOL.md §10.2.5).
|
|
1692
|
+
//
|
|
1693
|
+
// Columns:
|
|
1694
|
+
// failure_id — the anchored failure (references failure_patterns;
|
|
1695
|
+
// rows are pruned in FailurePatterns.delete()).
|
|
1696
|
+
// rel_path — PROJECT-RELATIVE path (never absolute): anchors must
|
|
1697
|
+
// replicate meaningfully across fleet machines whose
|
|
1698
|
+
// checkouts live at different roots. Traversal-escaping
|
|
1699
|
+
// paths are rejected at write time.
|
|
1700
|
+
// sha — hex sha256 of normalizeContentSignature(content) at
|
|
1701
|
+
// record time (crypto.ts WyrmCrypto.hash).
|
|
1702
|
+
// size — byte size at record time (stat fast path).
|
|
1703
|
+
// mtime_ns — mtime in NANOSECONDS at record time (stat fast path;
|
|
1704
|
+
// INT64 — read back via safeIntegers, the value exceeds
|
|
1705
|
+
// JS safe-integer range).
|
|
1706
|
+
// recorded_at — when the anchor was taken.
|
|
1707
|
+
//
|
|
1708
|
+
// Cap: at most 8 DISTINCT anchored files per failure, enforced at
|
|
1709
|
+
// WRITE time (FailurePatterns.recordAnchors) — a schema CHECK cannot
|
|
1710
|
+
// count rows.
|
|
1711
|
+
//
|
|
1712
|
+
// ONE row per (failure_id, rel_path): record() coalesces repeat
|
|
1713
|
+
// failures onto one id, so recordAnchors REFRESHES an existing path's
|
|
1714
|
+
// anchor (delete-then-insert) instead of appending a second row beside
|
|
1715
|
+
// the old-sha one — otherwise a failure just re-confirmed against
|
|
1716
|
+
// CURRENT source would read permanently 'stale' (anchorFreshness is
|
|
1717
|
+
// 'stale' on ANY drifted row) and never regain its hard block. The
|
|
1718
|
+
// UNIQUE index enforces that shape at the schema level; failure_id
|
|
1719
|
+
// being its leftmost column, it also serves the exact per-failure
|
|
1720
|
+
// lookup the check path does per matched failure.
|
|
1721
|
+
//
|
|
1722
|
+
// Additive + guarded (Article VI), migration 25/26/27/28 style: a
|
|
1723
|
+
// brand-new table, nothing renamed/dropped; an older DB opens with
|
|
1724
|
+
// 100% of its rows and a pre-30 DB simply has no anchors (the oracle
|
|
1725
|
+
// fails open to "unanchored").
|
|
1726
|
+
db.exec(`
|
|
1727
|
+
CREATE TABLE IF NOT EXISTS failure_anchors (
|
|
1728
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1729
|
+
failure_id INTEGER NOT NULL REFERENCES failure_patterns(id) ON DELETE CASCADE,
|
|
1730
|
+
rel_path TEXT NOT NULL,
|
|
1731
|
+
sha TEXT NOT NULL,
|
|
1732
|
+
size INTEGER NOT NULL,
|
|
1733
|
+
mtime_ns INTEGER NOT NULL,
|
|
1734
|
+
recorded_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1735
|
+
);
|
|
1736
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_failure_anchors_failure_rel
|
|
1737
|
+
ON failure_anchors(failure_id, rel_path);
|
|
1738
|
+
`);
|
|
1739
|
+
},
|
|
1740
|
+
},
|
|
1741
|
+
];
|
|
1742
|
+
/**
|
|
1743
|
+
* Run all pending migrations on the database.
|
|
1744
|
+
* Creates the schema_versions table if it doesn't exist.
|
|
1745
|
+
* Returns the list of migrations that were applied.
|
|
1746
|
+
*/
|
|
1747
|
+
export function runMigrations(db) {
|
|
1748
|
+
// Create the version tracking table (always safe to re-run)
|
|
1749
|
+
db.exec(`
|
|
1028
1750
|
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
1029
1751
|
version INTEGER PRIMARY KEY,
|
|
1030
1752
|
description TEXT NOT NULL,
|
|
1031
1753
|
applied_at TEXT DEFAULT (datetime('now'))
|
|
1032
1754
|
);
|
|
1033
|
-
`);
|
|
1755
|
+
`);
|
|
1756
|
+
const currentVersion = db.prepare('SELECT COALESCE(MAX(version), 0) as v FROM schema_versions').get().v;
|
|
1757
|
+
// Always apply in ascending version order, regardless of array order in
|
|
1758
|
+
// `migrations` (defensive — v3.9 added a migration that was authored out
|
|
1759
|
+
// of array order). Sorted copy so we don't mutate the exported array.
|
|
1760
|
+
const pending = migrations
|
|
1761
|
+
.filter(m => m.version > currentVersion)
|
|
1762
|
+
.slice()
|
|
1763
|
+
.sort((a, b) => a.version - b.version);
|
|
1764
|
+
if (pending.length === 0)
|
|
1765
|
+
return [];
|
|
1766
|
+
const applied = [];
|
|
1767
|
+
const insertVersion = db.prepare('INSERT INTO schema_versions (version, description) VALUES (?, ?)');
|
|
1768
|
+
for (const migration of pending) {
|
|
1769
|
+
// Each migration runs in its own transaction for atomicity
|
|
1770
|
+
const runOne = db.transaction(() => {
|
|
1771
|
+
migration.up(db);
|
|
1772
|
+
insertVersion.run(migration.version, migration.description);
|
|
1773
|
+
});
|
|
1774
|
+
runOne();
|
|
1775
|
+
applied.push(migration);
|
|
1776
|
+
}
|
|
1777
|
+
return applied;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Get the current schema version.
|
|
1781
|
+
*/
|
|
1782
|
+
export function getSchemaVersion(db) {
|
|
1783
|
+
try {
|
|
1784
|
+
return db.prepare('SELECT COALESCE(MAX(version), 0) as v FROM schema_versions').get().v;
|
|
1785
|
+
}
|
|
1786
|
+
catch {
|
|
1787
|
+
return 0; // schema_versions table doesn't exist yet
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
//# sourceMappingURL=migrations.js.map
|