yantrikdb 0.6.2

Cognitive memory engine for persistent AI systems
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
pub const SCHEMA_VERSION: i32 = 18;

pub const SCHEMA_SQL: &str = "
-- Memory records: the source of truth
CREATE TABLE IF NOT EXISTS memories (
    rid TEXT PRIMARY KEY,                -- UUIDv7, stable across devices
    type TEXT NOT NULL DEFAULT 'episodic', -- episodic | semantic | procedural | emotional
    text TEXT NOT NULL,                  -- raw memory content
    embedding BLOB,                     -- vector embedding (float32 array)

    -- Temporal
    created_at REAL NOT NULL,           -- unix timestamp (float for sub-second)
    updated_at REAL NOT NULL,

    -- Decay parameters (stored, not continuously updated)
    importance REAL NOT NULL DEFAULT 0.5,  -- base importance I0 [0, 1]
    half_life REAL NOT NULL DEFAULT 604800.0, -- seconds (default: 7 days)
    last_access REAL NOT NULL,            -- unix timestamp of last recall/reinforce
    access_count INTEGER NOT NULL DEFAULT 0, -- number of times retrieved via recall
    valence REAL NOT NULL DEFAULT 0.0,    -- emotional weight [-1, 1]

    -- Consolidation tracking
    consolidated_into TEXT,              -- rid of the semantic memory this was merged into
    consolidation_status TEXT DEFAULT 'active', -- active | consolidated | tombstoned

    -- Storage tier
    storage_tier TEXT NOT NULL DEFAULT 'hot', -- hot | cold

    -- Metadata
    metadata TEXT DEFAULT '{}',          -- JSON blob for extensibility

    -- Namespace for memory isolation
    namespace TEXT NOT NULL DEFAULT 'default',

    -- Cognitive dimensions (V10)
    certainty REAL NOT NULL DEFAULT 0.8,     -- confidence in accuracy [0, 1]
    domain TEXT NOT NULL DEFAULT 'general',   -- topic domain (work, health, family, finance, etc.)
    source TEXT NOT NULL DEFAULT 'user',      -- origin (user, system, document, inference)
    emotional_state TEXT,                     -- rich emotion label (joy, sadness, anger, fear, etc.)

    -- Session & temporal (V13)
    session_id TEXT,                          -- FK to sessions.session_id (nullable)
    due_at REAL,                              -- unix timestamp for upcoming() queries
    temporal_kind TEXT                         -- deadline | reminder | event | follow_up
);

-- Session tracking (V13)
CREATE TABLE IF NOT EXISTS sessions (
    session_id TEXT PRIMARY KEY,
    namespace TEXT NOT NULL DEFAULT 'default',
    client_id TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    started_at REAL NOT NULL,
    ended_at REAL,
    summary TEXT,
    avg_valence REAL,
    memory_count INTEGER NOT NULL DEFAULT 0,
    topics TEXT NOT NULL DEFAULT '[]',
    metadata TEXT NOT NULL DEFAULT '{}',
    hlc BLOB,
    origin_actor TEXT
);

-- Claims: first-class semantic relationship ledger (RFC 006 Phase 5)
-- Each claim records a structured (subject, relation, object) triple.
-- The legacy 'edges' name is preserved as a read-only VIEW for backward compat.
CREATE TABLE IF NOT EXISTS claims (
    claim_id TEXT PRIMARY KEY,           -- UUIDv7
    src TEXT NOT NULL,                   -- entity name or memory rid
    dst TEXT NOT NULL,                   -- entity name or memory rid
    rel_type TEXT NOT NULL,              -- relationship type (e.g., \"ceo_of\", \"works_at\")
    weight REAL NOT NULL DEFAULT 1.0,    -- relationship strength [0, 1]
    created_at REAL NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    -- RFC 006 claim qualifiers
    polarity INTEGER NOT NULL DEFAULT 1,           -- 1=positive, -1=negative, 0=unknown
    modality TEXT NOT NULL DEFAULT 'asserted',      -- asserted|reported|hypothetical|denied|quoted
    valid_from REAL,                                -- world-validity start (nullable)
    valid_to REAL,                                  -- world-validity end (null=present)
    extractor TEXT NOT NULL DEFAULT 'manual',       -- manual|structured_ingest|heuristic_v1|agent_llm
    extractor_version TEXT,
    confidence_band TEXT NOT NULL DEFAULT 'medium', -- low|medium|high
    source_memory_rid TEXT,                         -- provenance: which memory spawned this claim
    span_start INTEGER,                             -- byte offset in source memory text
    span_end INTEGER,
    namespace TEXT NOT NULL DEFAULT 'default',

    -- RFC 006: multiple sources can make conflicting claims about the same (src, rel, dst).
    -- Uniqueness is scoped to (src, dst, rel, extractor, polarity, namespace) so
    -- e.g. witness A can claim \"X did Y\" while witness B claims \"X did NOT do Y\" and
    -- both rows coexist. This enables polarity contradiction detection.
    UNIQUE(src, dst, rel_type, extractor, polarity, namespace)
);

-- Backward-compatible VIEW: all code reading FROM edges continues to work.
CREATE VIEW IF NOT EXISTS edges AS
    SELECT claim_id AS edge_id, src, dst, rel_type, weight, created_at, tombstoned,
           polarity, modality, valid_from, valid_to, extractor, extractor_version,
           confidence_band, source_memory_rid, span_start, span_end, namespace
    FROM claims;

-- Entity aliases for alias-aware conflict detection (RFC 006 Layer B)
CREATE TABLE IF NOT EXISTS entity_aliases (
    alias TEXT NOT NULL,
    canonical_name TEXT NOT NULL,
    namespace TEXT NOT NULL DEFAULT 'default',
    source TEXT NOT NULL DEFAULT 'explicit',  -- explicit|auto_suggested|approved
    created_at REAL NOT NULL,
    PRIMARY KEY (alias, namespace)
);
CREATE INDEX IF NOT EXISTS idx_alias_canonical ON entity_aliases(canonical_name, namespace);

-- Relation conflict policies (RFC 006 Phase 3)
-- Per-relation rules that govern how the conflict scanner treats claims.
CREATE TABLE IF NOT EXISTS relation_policies (
    relation_type TEXT NOT NULL,
    namespace TEXT NOT NULL DEFAULT '*',           -- '*' = global default
    uniqueness_scope TEXT NOT NULL DEFAULT '[\"dst\"]', -- JSON: which fields define uniqueness
    overlap_allowed INTEGER NOT NULL DEFAULT 0,    -- 1 if multiple dst values are normal
    temporal_required INTEGER NOT NULL DEFAULT 0,  -- 1 if conflict needs temporal overlap
    missing_time_severity TEXT NOT NULL DEFAULT 'medium', -- low|medium|high
    qualifier_exceptions TEXT,                     -- JSON: e.g. [\"qualifier=co\", \"qualifier=interim\"]
    PRIMARY KEY (relation_type, namespace)
);

-- Entities extracted from memories
CREATE TABLE IF NOT EXISTS entities (
    name TEXT PRIMARY KEY,               -- normalized entity name
    entity_type TEXT DEFAULT 'unknown',  -- person | place | thing | concept | etc.
    first_seen REAL NOT NULL,
    last_seen REAL NOT NULL,
    mention_count INTEGER NOT NULL DEFAULT 1,
    metadata TEXT DEFAULT '{}'
);

-- Append-only operation log (CRDT replication)
CREATE TABLE IF NOT EXISTS oplog (
    op_id TEXT PRIMARY KEY,              -- UUIDv7
    op_type TEXT NOT NULL,               -- record | relate | consolidate | decay | forget | update
    timestamp REAL NOT NULL,             -- when the operation occurred
    target_rid TEXT,                     -- primary memory affected
    payload TEXT NOT NULL DEFAULT '{}',  -- JSON: full operation details
    actor_id TEXT DEFAULT 'local',       -- device/agent identifier
    hlc BLOB,                           -- hybrid logical clock timestamp (16 bytes)
    embedding_hash BLOB,                -- BLAKE3 hash of embedding (if applicable)
    origin_actor TEXT NOT NULL DEFAULT 'local', -- which device originally created this op
    applied INTEGER NOT NULL DEFAULT 1  -- 1 = materialized locally, 0 = pending
);

-- Schema version tracking
CREATE TABLE IF NOT EXISTS meta (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

-- Peer tracking for delta sync
CREATE TABLE IF NOT EXISTS sync_peers (
    peer_actor TEXT PRIMARY KEY,
    last_synced_hlc BLOB NOT NULL,
    last_synced_op_id TEXT NOT NULL,
    last_sync_time REAL NOT NULL
);

-- Consolidation membership (set-union CRDT)
CREATE TABLE IF NOT EXISTS consolidation_members (
    consolidation_rid TEXT NOT NULL,     -- the consolidated memory
    source_rid TEXT NOT NULL,            -- original memory
    hlc BLOB NOT NULL,                  -- when this consolidation happened
    actor_id TEXT NOT NULL,             -- which device did it
    PRIMARY KEY (consolidation_rid, source_rid)
);

-- Conflict tracking (first-class data)
CREATE TABLE IF NOT EXISTS conflicts (
    conflict_id TEXT PRIMARY KEY,           -- UUIDv7
    conflict_type TEXT NOT NULL,            -- identity_fact | preference | temporal | consolidation | minor
    priority TEXT NOT NULL DEFAULT 'medium',-- low | medium | high | critical
    status TEXT NOT NULL DEFAULT 'open',    -- open | resolved | dismissed
    memory_a TEXT NOT NULL,                 -- rid of first conflicting memory
    memory_b TEXT NOT NULL,                 -- rid of second conflicting memory
    entity TEXT,                            -- entity name (nullable)
    rel_type TEXT,                          -- relationship type in conflict (nullable)
    detected_at REAL NOT NULL,
    detected_by TEXT NOT NULL,              -- actor_id that detected it
    detection_reason TEXT NOT NULL,
    resolved_at REAL,
    resolved_by TEXT,
    strategy TEXT,                          -- keep_a | keep_b | keep_both | merge | correct
    winner_rid TEXT,
    resolution_note TEXT,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

-- Persisted triggers with lifecycle tracking
CREATE TABLE IF NOT EXISTS trigger_log (
    trigger_id TEXT PRIMARY KEY,
    trigger_type TEXT NOT NULL,
    urgency REAL NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    reason TEXT NOT NULL,
    suggested_action TEXT NOT NULL,
    source_rids TEXT NOT NULL DEFAULT '[]',
    context TEXT NOT NULL DEFAULT '{}',
    created_at REAL NOT NULL,
    delivered_at REAL,
    acknowledged_at REAL,
    acted_at REAL,
    expires_at REAL,
    cooldown_key TEXT,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

-- Detected patterns across memories
CREATE TABLE IF NOT EXISTS patterns (
    pattern_id TEXT PRIMARY KEY,
    pattern_type TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    confidence REAL NOT NULL,
    description TEXT NOT NULL,
    evidence_rids TEXT NOT NULL DEFAULT '[]',
    entity_names TEXT NOT NULL DEFAULT '[]',
    context TEXT NOT NULL DEFAULT '{}',
    first_seen REAL NOT NULL,
    last_confirmed REAL NOT NULL,
    occurrence_count INTEGER NOT NULL DEFAULT 1,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

-- Indexes for common query patterns
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance DESC);
CREATE INDEX IF NOT EXISTS idx_memories_consolidation ON memories(consolidation_status);
CREATE INDEX IF NOT EXISTS idx_memories_storage_tier ON memories(storage_tier);
CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);
CREATE INDEX IF NOT EXISTS idx_memories_access_count ON memories(access_count);
CREATE INDEX IF NOT EXISTS idx_memories_domain ON memories(domain);
CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source);
CREATE INDEX IF NOT EXISTS idx_memories_emotional_state ON memories(emotional_state);
CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(namespace, session_id);
CREATE INDEX IF NOT EXISTS idx_memories_due_at ON memories(namespace, due_at) WHERE due_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_memories_last_access ON memories(last_access);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_one_active ON sessions(namespace, client_id) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_sessions_client_started ON sessions(namespace, client_id, started_at DESC);
CREATE INDEX IF NOT EXISTS idx_claims_src ON claims(src);
CREATE INDEX IF NOT EXISTS idx_claims_dst ON claims(dst);
CREATE INDEX IF NOT EXISTS idx_claims_rel ON claims(rel_type);
CREATE INDEX IF NOT EXISTS idx_oplog_timestamp ON oplog(timestamp);
CREATE INDEX IF NOT EXISTS idx_oplog_target ON oplog(target_rid);
CREATE INDEX IF NOT EXISTS idx_oplog_hlc ON oplog(hlc);
CREATE INDEX IF NOT EXISTS idx_oplog_actor ON oplog(origin_actor);
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(entity_type);
CREATE INDEX IF NOT EXISTS idx_consolidation_source ON consolidation_members(source_rid);
CREATE INDEX IF NOT EXISTS idx_conflicts_status ON conflicts(status);
CREATE INDEX IF NOT EXISTS idx_conflicts_type ON conflicts(conflict_type);
CREATE INDEX IF NOT EXISTS idx_conflicts_priority ON conflicts(priority);
CREATE INDEX IF NOT EXISTS idx_conflicts_entity ON conflicts(entity);
CREATE INDEX IF NOT EXISTS idx_conflicts_memory_a ON conflicts(memory_a);
CREATE INDEX IF NOT EXISTS idx_conflicts_memory_b ON conflicts(memory_b);
CREATE INDEX IF NOT EXISTS idx_trigger_log_status ON trigger_log(status);
CREATE INDEX IF NOT EXISTS idx_trigger_log_type ON trigger_log(trigger_type);
CREATE INDEX IF NOT EXISTS idx_trigger_log_created ON trigger_log(created_at);
CREATE INDEX IF NOT EXISTS idx_trigger_log_cooldown ON trigger_log(cooldown_key);
CREATE INDEX IF NOT EXISTS idx_trigger_log_urgency ON trigger_log(urgency DESC);
CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);
CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);
CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC);

-- Memory-entity join table for graph-augmented recall
CREATE TABLE IF NOT EXISTS memory_entities (
    memory_rid TEXT NOT NULL,
    entity_name TEXT NOT NULL,
    PRIMARY KEY (memory_rid, entity_name)
);
CREATE INDEX IF NOT EXISTS idx_memory_entities_entity ON memory_entities(entity_name);
CREATE INDEX IF NOT EXISTS idx_memory_entities_rid ON memory_entities(memory_rid);

-- FTS5 for full-text search on memories
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(text, content=memories, content_rowid=rowid);

-- Auto-sync triggers for FTS5
CREATE TRIGGER IF NOT EXISTS memories_fts_insert AFTER INSERT ON memories BEGIN
    INSERT INTO memories_fts(rowid, text) VALUES (new.rowid, new.text);
END;
CREATE TRIGGER IF NOT EXISTS memories_fts_delete BEFORE DELETE ON memories BEGIN
    INSERT INTO memories_fts(memories_fts, rowid, text) VALUES ('delete', old.rowid, old.text);
END;
CREATE TRIGGER IF NOT EXISTS memories_fts_update AFTER UPDATE OF text ON memories BEGIN
    INSERT INTO memories_fts(memories_fts, rowid, text) VALUES ('delete', old.rowid, old.text);
    INSERT INTO memories_fts(rowid, text) VALUES (new.rowid, new.text);
END;

-- Normalized join tables for trigger/pattern JSON arrays
CREATE TABLE IF NOT EXISTS trigger_source_rids (
    trigger_id TEXT NOT NULL,
    rid TEXT NOT NULL,
    PRIMARY KEY (trigger_id, rid)
);
CREATE INDEX IF NOT EXISTS idx_trigger_source_rids_rid ON trigger_source_rids(rid);

CREATE TABLE IF NOT EXISTS pattern_evidence (
    pattern_id TEXT NOT NULL,
    rid TEXT NOT NULL,
    PRIMARY KEY (pattern_id, rid)
);
CREATE INDEX IF NOT EXISTS idx_pattern_evidence_rid ON pattern_evidence(rid);

CREATE TABLE IF NOT EXISTS pattern_entities (
    pattern_id TEXT NOT NULL,
    entity_name TEXT NOT NULL,
    PRIMARY KEY (pattern_id, entity_name)
);
CREATE INDEX IF NOT EXISTS idx_pattern_entities_entity ON pattern_entities(entity_name);

-- Substitution categories for conflict detection (V14)
CREATE TABLE IF NOT EXISTS substitution_categories (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL UNIQUE,
    conflict_mode TEXT NOT NULL DEFAULT 'exclusive',
    status TEXT NOT NULL DEFAULT 'active',
    created_at REAL NOT NULL,
    updated_at REAL NOT NULL,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS substitution_members (
    id TEXT PRIMARY KEY,
    category_id TEXT NOT NULL REFERENCES substitution_categories(id),
    token_normalized TEXT NOT NULL,
    token_display TEXT NOT NULL,
    confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),
    source TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    context_hint TEXT,
    created_at REAL NOT NULL,
    updated_at REAL NOT NULL,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL,
    UNIQUE(category_id, token_normalized)
);
CREATE INDEX IF NOT EXISTS idx_sub_members_token ON substitution_members(token_normalized);
CREATE INDEX IF NOT EXISTS idx_sub_members_category ON substitution_members(category_id);
CREATE INDEX IF NOT EXISTS idx_sub_members_source_status ON substitution_members(source, status);
CREATE INDEX IF NOT EXISTS idx_sub_categories_name ON substitution_categories(name);

-- Recall feedback for adaptive learning (V10)
CREATE TABLE IF NOT EXISTS recall_feedback (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    query_text TEXT,
    query_embedding BLOB,
    rid TEXT NOT NULL,
    feedback TEXT NOT NULL,              -- 'relevant' | 'irrelevant'
    score_at_retrieval REAL,
    rank_at_retrieval INTEGER,
    created_at REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_feedback_created ON recall_feedback(created_at);

-- Learned scoring weights (singleton row, V10)
CREATE TABLE IF NOT EXISTS learned_weights (
    id INTEGER PRIMARY KEY CHECK (id = 1),
    w_sim REAL NOT NULL DEFAULT 0.50,
    w_decay REAL NOT NULL DEFAULT 0.20,
    w_recency REAL NOT NULL DEFAULT 0.30,
    gate_tau REAL NOT NULL DEFAULT 0.25,
    alpha_imp REAL NOT NULL DEFAULT 0.80,
    keyword_boost REAL NOT NULL DEFAULT 0.31,
    updated_at REAL,
    feedback_count INTEGER DEFAULT 0,
    generation INTEGER DEFAULT 0
);
INSERT OR IGNORE INTO learned_weights (id) VALUES (1);

-- Personality traits (V11)
CREATE TABLE IF NOT EXISTS personality_traits (
    trait_name TEXT PRIMARY KEY,
    score REAL NOT NULL DEFAULT 0.5,
    confidence REAL NOT NULL DEFAULT 0.0,
    sample_count INTEGER NOT NULL DEFAULT 0,
    updated_at REAL NOT NULL DEFAULT 0.0
);
INSERT OR IGNORE INTO personality_traits (trait_name, score, confidence, sample_count, updated_at)
    VALUES ('warmth', 0.5, 0.0, 0, 0.0),
           ('depth', 0.5, 0.0, 0, 0.0),
           ('energy', 0.5, 0.0, 0, 0.0),
           ('attentiveness', 0.5, 0.0, 0, 0.0);

-- Cognitive State Graph: Nodes (V12)
CREATE TABLE IF NOT EXISTS cognitive_nodes (
    node_id INTEGER PRIMARY KEY,            -- compact NodeId (4-bit kind + 28-bit seq)
    kind TEXT NOT NULL,                      -- node kind string (entity, belief, goal, etc.)
    label TEXT NOT NULL,                     -- human-readable label
    -- Universal cognitive attributes
    confidence REAL NOT NULL DEFAULT 0.5,
    activation REAL NOT NULL DEFAULT 0.0,
    salience REAL NOT NULL DEFAULT 0.5,
    persistence REAL NOT NULL DEFAULT 0.5,
    valence REAL NOT NULL DEFAULT 0.0,
    urgency REAL NOT NULL DEFAULT 0.0,
    novelty REAL NOT NULL DEFAULT 1.0,
    volatility REAL NOT NULL DEFAULT 0.1,
    provenance TEXT NOT NULL DEFAULT 'observed',
    evidence_count INTEGER NOT NULL DEFAULT 1,
    last_updated_ms INTEGER NOT NULL,
    -- Kind-specific payload (JSON)
    payload TEXT NOT NULL DEFAULT '{}',
    -- Metadata (JSON)
    metadata TEXT NOT NULL DEFAULT '{}',
    -- Lifecycle
    created_at REAL NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    -- Replication
    hlc BLOB,
    origin_actor TEXT
);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_kind ON cognitive_nodes(kind);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_activation ON cognitive_nodes(activation);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_urgency ON cognitive_nodes(urgency);

-- Cognitive State Graph: Edges (V12)
CREATE TABLE IF NOT EXISTS cognitive_edges (
    src_id INTEGER NOT NULL,                 -- source NodeId
    dst_id INTEGER NOT NULL,                 -- destination NodeId
    kind TEXT NOT NULL,                      -- edge kind string (supports, contradicts, etc.)
    weight REAL NOT NULL DEFAULT 0.5,        -- edge weight [-1.0, 1.0]
    confidence REAL NOT NULL DEFAULT 0.5,
    observation_count INTEGER NOT NULL DEFAULT 1,
    created_at_ms INTEGER NOT NULL,
    last_confirmed_ms INTEGER NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    hlc BLOB,
    origin_actor TEXT,
    PRIMARY KEY (src_id, dst_id, kind)
);
CREATE INDEX IF NOT EXISTS idx_cognitive_edges_dst ON cognitive_edges(dst_id);
CREATE INDEX IF NOT EXISTS idx_cognitive_edges_kind ON cognitive_edges(kind);

-- High-water marks for NodeId allocator (V12)
CREATE TABLE IF NOT EXISTS cognitive_node_hwm (
    kind TEXT PRIMARY KEY,                   -- node kind string
    high_water_mark INTEGER NOT NULL DEFAULT 0
);
";

/// SQL to migrate from schema V1 to V2.
pub const MIGRATE_V1_TO_V2: &str = "
ALTER TABLE oplog ADD COLUMN hlc BLOB;
ALTER TABLE oplog ADD COLUMN embedding_hash BLOB;
ALTER TABLE oplog ADD COLUMN origin_actor TEXT NOT NULL DEFAULT 'local';
ALTER TABLE oplog ADD COLUMN applied INTEGER NOT NULL DEFAULT 1;

CREATE INDEX IF NOT EXISTS idx_oplog_hlc ON oplog(hlc);
CREATE INDEX IF NOT EXISTS idx_oplog_actor ON oplog(origin_actor);

CREATE TABLE IF NOT EXISTS sync_peers (
    peer_actor TEXT PRIMARY KEY,
    last_synced_hlc BLOB NOT NULL,
    last_synced_op_id TEXT NOT NULL,
    last_sync_time REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS consolidation_members (
    consolidation_rid TEXT NOT NULL,
    source_rid TEXT NOT NULL,
    hlc BLOB NOT NULL,
    actor_id TEXT NOT NULL,
    PRIMARY KEY (consolidation_rid, source_rid)
);
CREATE INDEX IF NOT EXISTS idx_consolidation_source ON consolidation_members(source_rid);
";

/// SQL to migrate from schema V2 to V3.
pub const MIGRATE_V2_TO_V3: &str = "
CREATE TABLE IF NOT EXISTS conflicts (
    conflict_id TEXT PRIMARY KEY,
    conflict_type TEXT NOT NULL,
    priority TEXT NOT NULL DEFAULT 'medium',
    status TEXT NOT NULL DEFAULT 'open',
    memory_a TEXT NOT NULL,
    memory_b TEXT NOT NULL,
    entity TEXT,
    rel_type TEXT,
    detected_at REAL NOT NULL,
    detected_by TEXT NOT NULL,
    detection_reason TEXT NOT NULL,
    resolved_at REAL,
    resolved_by TEXT,
    strategy TEXT,
    winner_rid TEXT,
    resolution_note TEXT,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_conflicts_status ON conflicts(status);
CREATE INDEX IF NOT EXISTS idx_conflicts_type ON conflicts(conflict_type);
CREATE INDEX IF NOT EXISTS idx_conflicts_priority ON conflicts(priority);
CREATE INDEX IF NOT EXISTS idx_conflicts_entity ON conflicts(entity);
CREATE INDEX IF NOT EXISTS idx_conflicts_memory_a ON conflicts(memory_a);
CREATE INDEX IF NOT EXISTS idx_conflicts_memory_b ON conflicts(memory_b);
";

/// SQL to migrate from schema V3 to V4.
pub const MIGRATE_V3_TO_V4: &str = "
CREATE TABLE IF NOT EXISTS trigger_log (
    trigger_id TEXT PRIMARY KEY,
    trigger_type TEXT NOT NULL,
    urgency REAL NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    reason TEXT NOT NULL,
    suggested_action TEXT NOT NULL,
    source_rids TEXT NOT NULL DEFAULT '[]',
    context TEXT NOT NULL DEFAULT '{}',
    created_at REAL NOT NULL,
    delivered_at REAL,
    acknowledged_at REAL,
    acted_at REAL,
    expires_at REAL,
    cooldown_key TEXT,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS patterns (
    pattern_id TEXT PRIMARY KEY,
    pattern_type TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    confidence REAL NOT NULL,
    description TEXT NOT NULL,
    evidence_rids TEXT NOT NULL DEFAULT '[]',
    entity_names TEXT NOT NULL DEFAULT '[]',
    context TEXT NOT NULL DEFAULT '{}',
    first_seen REAL NOT NULL,
    last_confirmed REAL NOT NULL,
    occurrence_count INTEGER NOT NULL DEFAULT 1,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_trigger_log_status ON trigger_log(status);
CREATE INDEX IF NOT EXISTS idx_trigger_log_type ON trigger_log(trigger_type);
CREATE INDEX IF NOT EXISTS idx_trigger_log_created ON trigger_log(created_at);
CREATE INDEX IF NOT EXISTS idx_trigger_log_cooldown ON trigger_log(cooldown_key);
CREATE INDEX IF NOT EXISTS idx_trigger_log_urgency ON trigger_log(urgency DESC);
CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);
CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);
CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC);
";

/// SQL to migrate from schema V4 to V5.
pub const MIGRATE_V4_TO_V5: &str = "
CREATE TABLE IF NOT EXISTS memory_entities (
    memory_rid TEXT NOT NULL,
    entity_name TEXT NOT NULL,
    PRIMARY KEY (memory_rid, entity_name)
);
CREATE INDEX IF NOT EXISTS idx_memory_entities_entity ON memory_entities(entity_name);
CREATE INDEX IF NOT EXISTS idx_memory_entities_rid ON memory_entities(memory_rid);
";

/// SQL to migrate from schema V5 to V6.
pub const MIGRATE_V5_TO_V6: &str = "
ALTER TABLE memories ADD COLUMN storage_tier TEXT NOT NULL DEFAULT 'hot';
CREATE INDEX IF NOT EXISTS idx_memories_storage_tier ON memories(storage_tier);
";

/// SQL to migrate from schema V6 to V7.
pub const MIGRATE_V6_TO_V7: &str = "
-- FTS5 for full-text search on memories
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(text, content=memories, content_rowid=rowid);

-- Populate FTS5 from existing data
INSERT INTO memories_fts(memories_fts) VALUES('rebuild');

-- Auto-sync triggers for FTS5
CREATE TRIGGER IF NOT EXISTS memories_fts_insert AFTER INSERT ON memories BEGIN
    INSERT INTO memories_fts(rowid, text) VALUES (new.rowid, new.text);
END;
CREATE TRIGGER IF NOT EXISTS memories_fts_delete BEFORE DELETE ON memories BEGIN
    INSERT INTO memories_fts(memories_fts, rowid, text) VALUES ('delete', old.rowid, old.text);
END;
CREATE TRIGGER IF NOT EXISTS memories_fts_update AFTER UPDATE OF text ON memories BEGIN
    INSERT INTO memories_fts(memories_fts, rowid, text) VALUES ('delete', old.rowid, old.text);
    INSERT INTO memories_fts(rowid, text) VALUES (new.rowid, new.text);
END;

-- Normalized join tables
CREATE TABLE IF NOT EXISTS trigger_source_rids (
    trigger_id TEXT NOT NULL,
    rid TEXT NOT NULL,
    PRIMARY KEY (trigger_id, rid)
);
CREATE INDEX IF NOT EXISTS idx_trigger_source_rids_rid ON trigger_source_rids(rid);

CREATE TABLE IF NOT EXISTS pattern_evidence (
    pattern_id TEXT NOT NULL,
    rid TEXT NOT NULL,
    PRIMARY KEY (pattern_id, rid)
);
CREATE INDEX IF NOT EXISTS idx_pattern_evidence_rid ON pattern_evidence(rid);

CREATE TABLE IF NOT EXISTS pattern_entities (
    pattern_id TEXT NOT NULL,
    entity_name TEXT NOT NULL,
    PRIMARY KEY (pattern_id, entity_name)
);
CREATE INDEX IF NOT EXISTS idx_pattern_entities_entity ON pattern_entities(entity_name);

-- Backfill join tables from JSON columns
INSERT OR IGNORE INTO trigger_source_rids (trigger_id, rid)
    SELECT trigger_id, json_each.value FROM trigger_log, json_each(source_rids)
    WHERE source_rids IS NOT NULL AND source_rids != '[]';

INSERT OR IGNORE INTO pattern_evidence (pattern_id, rid)
    SELECT pattern_id, json_each.value FROM patterns, json_each(evidence_rids)
    WHERE evidence_rids IS NOT NULL AND evidence_rids != '[]';

INSERT OR IGNORE INTO pattern_entities (pattern_id, entity_name)
    SELECT pattern_id, json_each.value FROM patterns, json_each(entity_names)
    WHERE entity_names IS NOT NULL AND entity_names != '[]';
";

/// SQL to migrate from schema V7 to V8.
pub const MIGRATE_V7_TO_V8: &str = "
ALTER TABLE memories ADD COLUMN namespace TEXT NOT NULL DEFAULT 'default';
CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories(namespace);
";

/// SQL to migrate from schema V8 to V9.
pub const MIGRATE_V8_TO_V9: &str = "
ALTER TABLE memories ADD COLUMN access_count INTEGER NOT NULL DEFAULT 0;
CREATE INDEX IF NOT EXISTS idx_memories_access_count ON memories(access_count);
";

/// SQL to migrate from schema V9 to V10.
pub const MIGRATE_V9_TO_V10: &str = "
-- New cognitive dimension columns
ALTER TABLE memories ADD COLUMN certainty REAL NOT NULL DEFAULT 0.8;
ALTER TABLE memories ADD COLUMN domain TEXT NOT NULL DEFAULT 'general';
ALTER TABLE memories ADD COLUMN source TEXT NOT NULL DEFAULT 'user';
ALTER TABLE memories ADD COLUMN emotional_state TEXT;
CREATE INDEX IF NOT EXISTS idx_memories_domain ON memories(domain);
CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source);
CREATE INDEX IF NOT EXISTS idx_memories_emotional_state ON memories(emotional_state);

-- Recall feedback for adaptive learning
CREATE TABLE IF NOT EXISTS recall_feedback (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    query_text TEXT,
    query_embedding BLOB,
    rid TEXT NOT NULL,
    feedback TEXT NOT NULL,
    score_at_retrieval REAL,
    rank_at_retrieval INTEGER,
    created_at REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_feedback_created ON recall_feedback(created_at);

-- Learned scoring weights (singleton)
CREATE TABLE IF NOT EXISTS learned_weights (
    id INTEGER PRIMARY KEY CHECK (id = 1),
    w_sim REAL NOT NULL DEFAULT 0.50,
    w_decay REAL NOT NULL DEFAULT 0.20,
    w_recency REAL NOT NULL DEFAULT 0.30,
    gate_tau REAL NOT NULL DEFAULT 0.25,
    alpha_imp REAL NOT NULL DEFAULT 0.80,
    keyword_boost REAL NOT NULL DEFAULT 0.31,
    updated_at REAL,
    feedback_count INTEGER DEFAULT 0,
    generation INTEGER DEFAULT 0
);
INSERT OR IGNORE INTO learned_weights (id) VALUES (1);
";

/// SQL to migrate from schema V10 to V11.
pub const MIGRATE_V10_TO_V11: &str = "
-- Personality traits derived from memory signals
CREATE TABLE IF NOT EXISTS personality_traits (
    trait_name TEXT PRIMARY KEY,
    score REAL NOT NULL DEFAULT 0.5,
    confidence REAL NOT NULL DEFAULT 0.0,
    sample_count INTEGER NOT NULL DEFAULT 0,
    updated_at REAL NOT NULL DEFAULT 0.0
);
INSERT OR IGNORE INTO personality_traits (trait_name, score, confidence, sample_count, updated_at)
    VALUES ('warmth', 0.5, 0.0, 0, 0.0),
           ('depth', 0.5, 0.0, 0, 0.0),
           ('energy', 0.5, 0.0, 0, 0.0),
           ('attentiveness', 0.5, 0.0, 0, 0.0);
";

/// SQL to migrate from schema V11 to V12.
pub const MIGRATE_V11_TO_V12: &str = "
-- Cognitive State Graph: Nodes
CREATE TABLE IF NOT EXISTS cognitive_nodes (
    node_id INTEGER PRIMARY KEY,
    kind TEXT NOT NULL,
    label TEXT NOT NULL,
    confidence REAL NOT NULL DEFAULT 0.5,
    activation REAL NOT NULL DEFAULT 0.0,
    salience REAL NOT NULL DEFAULT 0.5,
    persistence REAL NOT NULL DEFAULT 0.5,
    valence REAL NOT NULL DEFAULT 0.0,
    urgency REAL NOT NULL DEFAULT 0.0,
    novelty REAL NOT NULL DEFAULT 1.0,
    volatility REAL NOT NULL DEFAULT 0.1,
    provenance TEXT NOT NULL DEFAULT 'observed',
    evidence_count INTEGER NOT NULL DEFAULT 1,
    last_updated_ms INTEGER NOT NULL,
    payload TEXT NOT NULL DEFAULT '{}',
    metadata TEXT NOT NULL DEFAULT '{}',
    created_at REAL NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    hlc BLOB,
    origin_actor TEXT
);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_kind ON cognitive_nodes(kind);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_activation ON cognitive_nodes(activation);
CREATE INDEX IF NOT EXISTS idx_cognitive_nodes_urgency ON cognitive_nodes(urgency);

-- Cognitive State Graph: Edges
CREATE TABLE IF NOT EXISTS cognitive_edges (
    src_id INTEGER NOT NULL,
    dst_id INTEGER NOT NULL,
    kind TEXT NOT NULL,
    weight REAL NOT NULL DEFAULT 0.5,
    confidence REAL NOT NULL DEFAULT 0.5,
    observation_count INTEGER NOT NULL DEFAULT 1,
    created_at_ms INTEGER NOT NULL,
    last_confirmed_ms INTEGER NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    hlc BLOB,
    origin_actor TEXT,
    PRIMARY KEY (src_id, dst_id, kind)
);
CREATE INDEX IF NOT EXISTS idx_cognitive_edges_dst ON cognitive_edges(dst_id);
CREATE INDEX IF NOT EXISTS idx_cognitive_edges_kind ON cognitive_edges(kind);

-- High-water marks for NodeId allocator
CREATE TABLE IF NOT EXISTS cognitive_node_hwm (
    kind TEXT PRIMARY KEY,
    high_water_mark INTEGER NOT NULL DEFAULT 0
);
";

/// SQL to migrate from schema V12 to V13.
pub const MIGRATE_V12_TO_V13: &str = "
-- Session tracking
CREATE TABLE IF NOT EXISTS sessions (
    session_id TEXT PRIMARY KEY,
    namespace TEXT NOT NULL DEFAULT 'default',
    client_id TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    started_at REAL NOT NULL,
    ended_at REAL,
    summary TEXT,
    avg_valence REAL,
    memory_count INTEGER NOT NULL DEFAULT 0,
    topics TEXT NOT NULL DEFAULT '[]',
    metadata TEXT NOT NULL DEFAULT '{}',
    hlc BLOB,
    origin_actor TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_one_active
    ON sessions(namespace, client_id) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_sessions_client_started
    ON sessions(namespace, client_id, started_at DESC);

-- Memories: session & temporal columns
ALTER TABLE memories ADD COLUMN session_id TEXT;
ALTER TABLE memories ADD COLUMN due_at REAL;
ALTER TABLE memories ADD COLUMN temporal_kind TEXT;
CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(namespace, session_id);
CREATE INDEX IF NOT EXISTS idx_memories_due_at ON memories(namespace, due_at)
    WHERE due_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_memories_last_access ON memories(last_access);
";

/// SQL to migrate from schema V13 to V14.
pub const MIGRATE_V13_TO_V14: &str = "
-- Substitution categories for feedback-driven conflict learning
CREATE TABLE IF NOT EXISTS substitution_categories (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL UNIQUE,
    conflict_mode TEXT NOT NULL DEFAULT 'exclusive',
    status TEXT NOT NULL DEFAULT 'active',
    created_at REAL NOT NULL,
    updated_at REAL NOT NULL,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS substitution_members (
    id TEXT PRIMARY KEY,
    category_id TEXT NOT NULL REFERENCES substitution_categories(id),
    token_normalized TEXT NOT NULL,
    token_display TEXT NOT NULL,
    confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),
    source TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    context_hint TEXT,
    created_at REAL NOT NULL,
    updated_at REAL NOT NULL,
    hlc BLOB NOT NULL,
    origin_actor TEXT NOT NULL,
    UNIQUE(category_id, token_normalized)
);
CREATE INDEX IF NOT EXISTS idx_sub_members_token ON substitution_members(token_normalized);
CREATE INDEX IF NOT EXISTS idx_sub_members_category ON substitution_members(category_id);
CREATE INDEX IF NOT EXISTS idx_sub_members_source_status ON substitution_members(source, status);
CREATE INDEX IF NOT EXISTS idx_sub_categories_name ON substitution_categories(name);
";

/// SQL to migrate from schema V14 to V15 (RFC 006 Phase 1).
///
/// Extends edges with claim-like qualifier columns for scoped conflict
/// detection: polarity, modality, valid_from/to, extractor, confidence_band,
/// source provenance, and namespace. Also adds entity_aliases for
/// alias-aware entity linking.
pub const MIGRATE_V14_TO_V15: &str = "
-- RFC 006 Phase 1: extend edges into claim-like records
ALTER TABLE edges ADD COLUMN polarity INTEGER NOT NULL DEFAULT 1;
ALTER TABLE edges ADD COLUMN modality TEXT NOT NULL DEFAULT 'asserted';
ALTER TABLE edges ADD COLUMN valid_from REAL;
ALTER TABLE edges ADD COLUMN valid_to REAL;
ALTER TABLE edges ADD COLUMN extractor TEXT NOT NULL DEFAULT 'manual';
ALTER TABLE edges ADD COLUMN extractor_version TEXT;
ALTER TABLE edges ADD COLUMN confidence_band TEXT NOT NULL DEFAULT 'medium';
ALTER TABLE edges ADD COLUMN source_memory_rid TEXT;
ALTER TABLE edges ADD COLUMN span_start INTEGER;
ALTER TABLE edges ADD COLUMN span_end INTEGER;
ALTER TABLE edges ADD COLUMN namespace TEXT NOT NULL DEFAULT 'default';

-- Entity aliases for alias-aware conflict detection (RFC 006 Layer B)
CREATE TABLE IF NOT EXISTS entity_aliases (
    alias TEXT NOT NULL,
    canonical_name TEXT NOT NULL,
    namespace TEXT NOT NULL DEFAULT 'default',
    source TEXT NOT NULL DEFAULT 'explicit',
    created_at REAL NOT NULL DEFAULT 0.0,
    PRIMARY KEY (alias, namespace)
);
CREATE INDEX IF NOT EXISTS idx_alias_canonical ON entity_aliases(canonical_name, namespace);
";

/// SQL to migrate from schema V15 to V16 (RFC 006 Phase 3).
pub const MIGRATE_V15_TO_V16: &str = "
-- Relation conflict policies
CREATE TABLE IF NOT EXISTS relation_policies (
    relation_type TEXT NOT NULL,
    namespace TEXT NOT NULL DEFAULT '*',
    uniqueness_scope TEXT NOT NULL DEFAULT '[\"dst\"]',
    overlap_allowed INTEGER NOT NULL DEFAULT 0,
    temporal_required INTEGER NOT NULL DEFAULT 0,
    missing_time_severity TEXT NOT NULL DEFAULT 'medium',
    qualifier_exceptions TEXT,
    PRIMARY KEY (relation_type, namespace)
);

-- Seed starter policies for RFC 006 whitelist relations
INSERT OR IGNORE INTO relation_policies (relation_type, namespace, overlap_allowed, temporal_required, missing_time_severity)
VALUES
    ('ceo_of',            '*', 0, 1, 'medium'),
    ('cto_of',            '*', 0, 1, 'medium'),
    ('cfo_of',            '*', 0, 1, 'medium'),
    ('founded',           '*', 1, 0, 'low'),
    ('leads',             '*', 0, 1, 'medium'),
    ('works_at',          '*', 1, 0, 'low'),
    ('born_in',           '*', 0, 0, 'high'),
    ('headquartered_in',  '*', 0, 0, 'high'),
    ('married_to',        '*', 0, 1, 'medium'),
    ('acquired',          '*', 0, 0, 'high'),
    ('subsidiary_of',     '*', 0, 0, 'high'),
    ('speaks',            '*', 1, 0, 'low');
";

/// SQL to migrate from schema V16 to V17 (RFC 006 Phase 5).
/// Renames `edges` table to `claims` and creates `edges` as a read-only VIEW.
pub const MIGRATE_V16_TO_V17: &str = "
-- Rename edges → claims (atomic, preserves all data + indexes)
ALTER TABLE edges RENAME TO claims;
-- Rename primary key column
ALTER TABLE claims RENAME COLUMN edge_id TO claim_id;
-- Create backward-compat VIEW so all SELECT FROM edges queries still work
CREATE VIEW IF NOT EXISTS edges AS
    SELECT claim_id AS edge_id, src, dst, rel_type, weight, created_at, tombstoned,
           polarity, modality, valid_from, valid_to, extractor, extractor_version,
           confidence_band, source_memory_rid, span_start, span_end, namespace
    FROM claims;
";

/// SQL to migrate from schema V17 to V18 (RFC 006 Phase 6).
///
/// The V17 UNIQUE constraint on (src, dst, rel_type) caused ingest_claim() to
/// overwrite a previous source's claim whenever another source asserted the
/// same (src, dst, rel_type) — destroying the polarity contradiction cases
/// RFC 006 is designed to detect.
///
/// V18 widens the constraint to (src, dst, rel_type, extractor, polarity,
/// namespace). Now two sources can make contradictory claims about the same
/// fact and both rows survive, enabling proper multi-witness investigation.
///
/// Migration strategy (SQLite can't ALTER UNIQUE):
///   1. Drop the edges VIEW (depends on claims table)
///   2. Create claims_new with new constraint
///   3. Copy data — deduplicate where old UNIQUE would have rejected
///   4. Drop old claims, rename claims_new → claims
///   5. Recreate indexes + edges VIEW
pub const MIGRATE_V17_TO_V18: &str = "
DROP VIEW IF EXISTS edges;

CREATE TABLE claims_new (
    claim_id TEXT PRIMARY KEY,
    src TEXT NOT NULL,
    dst TEXT NOT NULL,
    rel_type TEXT NOT NULL,
    weight REAL NOT NULL DEFAULT 1.0,
    created_at REAL NOT NULL,
    tombstoned INTEGER NOT NULL DEFAULT 0,
    polarity INTEGER NOT NULL DEFAULT 1,
    modality TEXT NOT NULL DEFAULT 'asserted',
    valid_from REAL,
    valid_to REAL,
    extractor TEXT NOT NULL DEFAULT 'manual',
    extractor_version TEXT,
    confidence_band TEXT NOT NULL DEFAULT 'medium',
    source_memory_rid TEXT,
    span_start INTEGER,
    span_end INTEGER,
    namespace TEXT NOT NULL DEFAULT 'default',
    UNIQUE(src, dst, rel_type, extractor, polarity, namespace)
);

INSERT INTO claims_new
    SELECT claim_id, src, dst, rel_type, weight, created_at, tombstoned,
           polarity, modality, valid_from, valid_to, extractor, extractor_version,
           confidence_band, source_memory_rid, span_start, span_end, namespace
    FROM claims;

DROP TABLE claims;
ALTER TABLE claims_new RENAME TO claims;

CREATE INDEX IF NOT EXISTS idx_claims_src ON claims(src);
CREATE INDEX IF NOT EXISTS idx_claims_dst ON claims(dst);
CREATE INDEX IF NOT EXISTS idx_claims_rel ON claims(rel_type);

CREATE VIEW IF NOT EXISTS edges AS
    SELECT claim_id AS edge_id, src, dst, rel_type, weight, created_at, tombstoned,
           polarity, modality, valid_from, valid_to, extractor, extractor_version,
           confidence_band, source_memory_rid, span_start, span_end, namespace
    FROM claims;
";