traqr-memory-mcp 0.1.0

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/setup.sql ADDED
@@ -0,0 +1,1037 @@
1
+ -- ============================================================================
2
+ -- TraqrDB Fresh-Install Setup
3
+ -- ============================================================================
4
+ -- Compiled from migrations 001-011 into a single idempotent fresh-install.
5
+ -- No ALTER TABLE, no DROP, no backfill. Tables have ALL v2 columns from start.
6
+ --
7
+ -- Dependency order:
8
+ -- 1. Extensions
9
+ -- 2. Immutable wrapper functions (needed by GENERATED columns)
10
+ -- 3. Tables: memory_users, memory_domains, traqr_memories, traqr_memory_history,
11
+ -- memory_relationships, memory_entities, memory_entity_links, schema_version
12
+ -- 4. Indexes
13
+ -- 5. Conditional RLS (auth.role() only exists on Supabase)
14
+ -- 6. RPC functions (v2 versions only)
15
+ -- 7. Schema version tracking + migration bootstrap
16
+ --
17
+ -- Safe to re-run: all operations use IF NOT EXISTS / CREATE OR REPLACE.
18
+ -- ============================================================================
19
+
20
+
21
+ -- ============================================================================
22
+ -- 1. EXTENSIONS
23
+ -- ============================================================================
24
+
25
+ CREATE EXTENSION IF NOT EXISTS vector;
26
+ CREATE EXTENSION IF NOT EXISTS pg_trgm;
27
+
28
+
29
+ -- ============================================================================
30
+ -- 2. IMMUTABLE WRAPPER FUNCTIONS
31
+ -- ============================================================================
32
+ -- to_tsvector() is STABLE, but GENERATED ALWAYS AS requires IMMUTABLE.
33
+ -- Safe because english/simple dictionaries are built-in and never change.
34
+
35
+ CREATE OR REPLACE FUNCTION traqr_tsvector_en(content text, summary text, tags text[])
36
+ RETURNS tsvector
37
+ LANGUAGE sql IMMUTABLE AS $$
38
+ SELECT to_tsvector('english',
39
+ COALESCE(content, '') || ' ' ||
40
+ COALESCE(summary, '') || ' ' ||
41
+ COALESCE(array_to_string(tags, ' '), '')
42
+ );
43
+ $$;
44
+
45
+ CREATE OR REPLACE FUNCTION traqr_tsvector_simple(content text, summary text, tags text[])
46
+ RETURNS tsvector
47
+ LANGUAGE sql IMMUTABLE AS $$
48
+ SELECT to_tsvector('simple',
49
+ COALESCE(content, '') || ' ' ||
50
+ COALESCE(summary, '') || ' ' ||
51
+ COALESCE(array_to_string(tags, ' '), '')
52
+ );
53
+ $$;
54
+
55
+
56
+ -- ============================================================================
57
+ -- 3. TABLES
58
+ -- ============================================================================
59
+
60
+ -- 3a. memory_users — user identity for multi-tenant support
61
+ CREATE TABLE IF NOT EXISTS memory_users (
62
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
63
+ api_key VARCHAR(64) UNIQUE NOT NULL,
64
+ email VARCHAR(255) UNIQUE,
65
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
66
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
67
+ );
68
+
69
+ -- 3b. memory_domains — legacy project/domain isolation
70
+ CREATE TABLE IF NOT EXISTS memory_domains (
71
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
72
+ user_id UUID REFERENCES memory_users(id) ON DELETE CASCADE,
73
+ name VARCHAR(100) NOT NULL,
74
+ description TEXT,
75
+ is_shareable BOOLEAN DEFAULT FALSE,
76
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
77
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
78
+ UNIQUE(user_id, name)
79
+ );
80
+
81
+ -- 3c. traqr_memories — core memories table (v1 + v2 columns merged)
82
+ CREATE TABLE IF NOT EXISTS traqr_memories (
83
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
84
+
85
+ -- Identity / scoping
86
+ user_id UUID,
87
+ project_id UUID,
88
+ domain_id UUID REFERENCES memory_domains(id) ON DELETE CASCADE,
89
+
90
+ -- Content
91
+ content TEXT NOT NULL,
92
+ summary VARCHAR(500),
93
+
94
+ -- Categorization
95
+ category VARCHAR(50),
96
+ tags VARCHAR(100)[] DEFAULT '{}',
97
+ context_tags VARCHAR(100)[] DEFAULT '{}',
98
+ domain VARCHAR(100),
99
+ topic VARCHAR(100),
100
+
101
+ -- Embedding
102
+ embedding vector(1536),
103
+ embedding_model VARCHAR(100) DEFAULT 'openai/text-embedding-3-small',
104
+ embedding_model_version VARCHAR(20) DEFAULT 'v1',
105
+ needs_reembedding BOOLEAN DEFAULT FALSE,
106
+
107
+ -- Provenance
108
+ source_type VARCHAR(50),
109
+ source_ref VARCHAR(255),
110
+ source_project VARCHAR(100) DEFAULT 'default',
111
+ source_tool VARCHAR(50),
112
+
113
+ -- Confidence & Decay
114
+ original_confidence FLOAT DEFAULT 1.0,
115
+ last_validated TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
116
+
117
+ -- Version relationships
118
+ related_to UUID[] DEFAULT '{}',
119
+ is_contradiction BOOLEAN DEFAULT FALSE,
120
+
121
+ -- Archive
122
+ is_archived BOOLEAN DEFAULT FALSE,
123
+ archived_at TIMESTAMP WITH TIME ZONE,
124
+ archive_reason VARCHAR(50),
125
+
126
+ -- Durability / TTL
127
+ durability VARCHAR(20) DEFAULT 'permanent',
128
+ expires_at TIMESTAMP WITH TIME ZONE,
129
+
130
+ -- Cross-project / portability
131
+ is_universal BOOLEAN DEFAULT FALSE,
132
+ is_portable BOOLEAN DEFAULT TRUE,
133
+ agent_type VARCHAR(50),
134
+
135
+ -- Citation tracking
136
+ times_returned INTEGER DEFAULT 0,
137
+ times_cited INTEGER DEFAULT 0,
138
+ last_returned_at TIMESTAMP WITH TIME ZONE,
139
+ last_cited_at TIMESTAMP WITH TIME ZONE,
140
+
141
+ -- v2: Memory lifecycle (M5 Pipeline Design)
142
+ memory_type VARCHAR(20) DEFAULT 'pattern',
143
+ is_latest BOOLEAN DEFAULT TRUE,
144
+ is_forgotten BOOLEAN DEFAULT FALSE,
145
+ forgotten_at TIMESTAMP WITH TIME ZONE,
146
+ forget_after TIMESTAMP WITH TIME ZONE,
147
+
148
+ -- v2: Temporal model (M7)
149
+ valid_at TIMESTAMP WITH TIME ZONE,
150
+ invalid_at TIMESTAMP WITH TIME ZONE,
151
+
152
+ -- v2: Dual tsvector for BM25 (GENERATED ALWAYS, auto-computed)
153
+ search_vector_en tsvector
154
+ GENERATED ALWAYS AS (traqr_tsvector_en(content, summary::text, tags::text[])) STORED,
155
+ search_vector_simple tsvector
156
+ GENERATED ALWAYS AS (traqr_tsvector_simple(content, summary::text, tags::text[])) STORED,
157
+
158
+ -- Timestamps
159
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
160
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
161
+
162
+ -- Constraints
163
+ CONSTRAINT chk_memory_type CHECK (memory_type IN ('fact', 'preference', 'pattern'))
164
+ );
165
+
166
+ -- 3d. traqr_memory_history — evolution tracking
167
+ CREATE TABLE IF NOT EXISTS traqr_memory_history (
168
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
169
+ memory_id UUID REFERENCES traqr_memories(id) ON DELETE CASCADE,
170
+ previous_content TEXT,
171
+ previous_embedding vector(1536),
172
+ previous_confidence FLOAT,
173
+ change_reason TEXT,
174
+ changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
175
+ );
176
+
177
+ -- 3e. memory_relationships — memory-to-memory edges for version chains
178
+ CREATE TABLE IF NOT EXISTS memory_relationships (
179
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
180
+ source_memory_id UUID NOT NULL REFERENCES traqr_memories(id) ON DELETE CASCADE,
181
+ target_memory_id UUID NOT NULL REFERENCES traqr_memories(id) ON DELETE CASCADE,
182
+ edge_type VARCHAR(20) NOT NULL,
183
+ confidence FLOAT DEFAULT 1.0,
184
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
185
+ metadata JSONB DEFAULT '{}',
186
+ CONSTRAINT chk_edge_type CHECK (edge_type IN ('updates', 'extends', 'derives', 'related')),
187
+ CONSTRAINT uq_memory_rel UNIQUE (source_memory_id, target_memory_id, edge_type)
188
+ );
189
+
190
+ -- 3f. memory_entities — entities extracted from memories (user-scoped)
191
+ CREATE TABLE IF NOT EXISTS memory_entities (
192
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
193
+ user_id UUID NOT NULL,
194
+ name VARCHAR(255) NOT NULL,
195
+ entity_type VARCHAR(50) NOT NULL,
196
+ embedding vector(1536),
197
+ mentions_count INTEGER DEFAULT 0,
198
+ first_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
199
+ last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
200
+ metadata JSONB DEFAULT '{}',
201
+ is_archived BOOLEAN DEFAULT FALSE,
202
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
203
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
204
+ UNIQUE(user_id, name, entity_type)
205
+ );
206
+
207
+ -- 3g. memory_entity_links — junction table (many-to-many memories <-> entities)
208
+ CREATE TABLE IF NOT EXISTS memory_entity_links (
209
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
210
+ memory_id UUID NOT NULL REFERENCES traqr_memories(id) ON DELETE CASCADE,
211
+ entity_id UUID NOT NULL REFERENCES memory_entities(id) ON DELETE CASCADE,
212
+ role VARCHAR(20) DEFAULT 'mention',
213
+ extracted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
214
+ UNIQUE(memory_id, entity_id, role)
215
+ );
216
+
217
+ -- 3h. schema_version — tracks installed schema version
218
+ CREATE TABLE IF NOT EXISTS schema_version (
219
+ version INTEGER PRIMARY KEY,
220
+ description TEXT,
221
+ applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
222
+ );
223
+
224
+
225
+ -- ============================================================================
226
+ -- 4. INDEXES
227
+ -- ============================================================================
228
+
229
+ -- memory_users
230
+ CREATE INDEX IF NOT EXISTS memory_users_api_key_idx
231
+ ON memory_users(api_key);
232
+
233
+ -- memory_domains
234
+ CREATE INDEX IF NOT EXISTS memory_domains_user_idx
235
+ ON memory_domains(user_id);
236
+
237
+ -- traqr_memories: partial HNSW (only active, non-forgotten memories)
238
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_active_embedding
239
+ ON traqr_memories USING hnsw (embedding vector_cosine_ops)
240
+ WHERE is_archived = FALSE AND is_forgotten = FALSE;
241
+
242
+ -- traqr_memories: legacy indexes (renamed from memories_* prefix)
243
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_domain_id
244
+ ON traqr_memories(domain_id);
245
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_tags
246
+ ON traqr_memories USING gin(tags);
247
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_category
248
+ ON traqr_memories(category);
249
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_source_project
250
+ ON traqr_memories(source_project);
251
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_archived
252
+ ON traqr_memories(is_archived);
253
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_created
254
+ ON traqr_memories(created_at DESC);
255
+
256
+ -- traqr_memories: cross-project / portability
257
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_universal
258
+ ON traqr_memories(is_universal)
259
+ WHERE is_universal = TRUE;
260
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_universal_category
261
+ ON traqr_memories(is_universal, category)
262
+ WHERE is_universal = TRUE;
263
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_agent_type
264
+ ON traqr_memories(agent_type)
265
+ WHERE agent_type IS NOT NULL;
266
+
267
+ -- traqr_memories: durability / TTL
268
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_expires_at
269
+ ON traqr_memories(expires_at)
270
+ WHERE expires_at IS NOT NULL AND durability != 'permanent';
271
+
272
+ -- traqr_memories: citation tracking
273
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_citation
274
+ ON traqr_memories(times_returned, times_cited)
275
+ WHERE is_archived = FALSE;
276
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_cited
277
+ ON traqr_memories(times_cited DESC)
278
+ WHERE times_cited > 0 AND is_archived = FALSE;
279
+
280
+ -- traqr_memories: v2 lifecycle indexes
281
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_memory_type
282
+ ON traqr_memories(memory_type) WHERE is_archived = FALSE;
283
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_is_latest
284
+ ON traqr_memories(is_latest) WHERE is_latest = TRUE AND is_archived = FALSE;
285
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_forgotten
286
+ ON traqr_memories(is_forgotten) WHERE is_forgotten = TRUE;
287
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_forget_after
288
+ ON traqr_memories(forget_after)
289
+ WHERE forget_after IS NOT NULL AND is_forgotten = FALSE;
290
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_source_tool
291
+ ON traqr_memories(source_tool) WHERE source_tool IS NOT NULL;
292
+
293
+ -- traqr_memories: v2 temporal indexes
294
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_valid_at
295
+ ON traqr_memories(valid_at) WHERE valid_at IS NOT NULL;
296
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_temporal
297
+ ON traqr_memories(valid_at, invalid_at)
298
+ WHERE is_archived = FALSE AND is_forgotten = FALSE;
299
+
300
+ -- traqr_memories: v2 BM25 tsvector indexes (GIN for full-text search)
301
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_search_en
302
+ ON traqr_memories USING gin(search_vector_en);
303
+ CREATE INDEX IF NOT EXISTS idx_traqr_memories_search_simple
304
+ ON traqr_memories USING gin(search_vector_simple);
305
+
306
+ -- traqr_memory_history
307
+ CREATE INDEX IF NOT EXISTS idx_traqr_memory_history_memory
308
+ ON traqr_memory_history(memory_id);
309
+ CREATE INDEX IF NOT EXISTS idx_traqr_memory_history_changed
310
+ ON traqr_memory_history(changed_at DESC);
311
+
312
+ -- memory_relationships
313
+ CREATE INDEX IF NOT EXISTS idx_rel_source
314
+ ON memory_relationships(source_memory_id);
315
+ CREATE INDEX IF NOT EXISTS idx_rel_target
316
+ ON memory_relationships(target_memory_id);
317
+ CREATE INDEX IF NOT EXISTS idx_rel_edge
318
+ ON memory_relationships(edge_type);
319
+
320
+ -- memory_entities
321
+ CREATE INDEX IF NOT EXISTS idx_entities_user
322
+ ON memory_entities(user_id);
323
+ CREATE INDEX IF NOT EXISTS idx_entities_type
324
+ ON memory_entities(entity_type);
325
+ CREATE INDEX IF NOT EXISTS idx_entities_name
326
+ ON memory_entities(name);
327
+ CREATE INDEX IF NOT EXISTS idx_entities_mentions
328
+ ON memory_entities(mentions_count DESC);
329
+ CREATE INDEX IF NOT EXISTS idx_entities_embedding
330
+ ON memory_entities USING hnsw (embedding vector_cosine_ops);
331
+
332
+ -- memory_entity_links
333
+ CREATE INDEX IF NOT EXISTS idx_entity_links_memory
334
+ ON memory_entity_links(memory_id);
335
+ CREATE INDEX IF NOT EXISTS idx_entity_links_entity
336
+ ON memory_entity_links(entity_id);
337
+
338
+
339
+ -- ============================================================================
340
+ -- 5. CONDITIONAL ROW-LEVEL SECURITY
341
+ -- ============================================================================
342
+ -- auth.role() only exists on Supabase. Wrap ALL RLS in a DO block that
343
+ -- checks for the function's existence first.
344
+
345
+ DO $$
346
+ DECLARE
347
+ has_auth_role BOOLEAN;
348
+ BEGIN
349
+ -- Check if auth.role() exists (Supabase-specific)
350
+ SELECT EXISTS(
351
+ SELECT 1 FROM pg_proc p
352
+ JOIN pg_namespace n ON p.pronamespace = n.oid
353
+ WHERE n.nspname = 'auth' AND p.proname = 'role'
354
+ ) INTO has_auth_role;
355
+
356
+ -- Enable RLS on all tables (safe everywhere)
357
+ ALTER TABLE memory_users ENABLE ROW LEVEL SECURITY;
358
+ ALTER TABLE memory_domains ENABLE ROW LEVEL SECURITY;
359
+ ALTER TABLE traqr_memories ENABLE ROW LEVEL SECURITY;
360
+ ALTER TABLE traqr_memory_history ENABLE ROW LEVEL SECURITY;
361
+ ALTER TABLE memory_relationships ENABLE ROW LEVEL SECURITY;
362
+ ALTER TABLE memory_entities ENABLE ROW LEVEL SECURITY;
363
+ ALTER TABLE memory_entity_links ENABLE ROW LEVEL SECURITY;
364
+ ALTER TABLE schema_version ENABLE ROW LEVEL SECURITY;
365
+
366
+ IF has_auth_role THEN
367
+ -- Supabase: restrict to service_role
368
+ BEGIN
369
+ CREATE POLICY "Service role full access on memory_users"
370
+ ON memory_users FOR ALL
371
+ USING (auth.role() = 'service_role')
372
+ WITH CHECK (auth.role() = 'service_role');
373
+ EXCEPTION WHEN duplicate_object THEN NULL;
374
+ END;
375
+
376
+ BEGIN
377
+ CREATE POLICY "Service role full access on memory_domains"
378
+ ON memory_domains FOR ALL
379
+ USING (auth.role() = 'service_role')
380
+ WITH CHECK (auth.role() = 'service_role');
381
+ EXCEPTION WHEN duplicate_object THEN NULL;
382
+ END;
383
+
384
+ BEGIN
385
+ CREATE POLICY "Service role full access on traqr_memories"
386
+ ON traqr_memories FOR ALL
387
+ USING (auth.role() = 'service_role')
388
+ WITH CHECK (auth.role() = 'service_role');
389
+ EXCEPTION WHEN duplicate_object THEN NULL;
390
+ END;
391
+
392
+ BEGIN
393
+ CREATE POLICY "Service role full access on traqr_memory_history"
394
+ ON traqr_memory_history FOR ALL
395
+ USING (auth.role() = 'service_role')
396
+ WITH CHECK (auth.role() = 'service_role');
397
+ EXCEPTION WHEN duplicate_object THEN NULL;
398
+ END;
399
+
400
+ BEGIN
401
+ CREATE POLICY "Service role full access on relationships"
402
+ ON memory_relationships FOR ALL
403
+ USING (auth.role() = 'service_role')
404
+ WITH CHECK (auth.role() = 'service_role');
405
+ EXCEPTION WHEN duplicate_object THEN NULL;
406
+ END;
407
+
408
+ BEGIN
409
+ CREATE POLICY "Service role full access on entities"
410
+ ON memory_entities FOR ALL
411
+ USING (auth.role() = 'service_role')
412
+ WITH CHECK (auth.role() = 'service_role');
413
+ EXCEPTION WHEN duplicate_object THEN NULL;
414
+ END;
415
+
416
+ BEGIN
417
+ CREATE POLICY "Service role full access on entity_links"
418
+ ON memory_entity_links FOR ALL
419
+ USING (auth.role() = 'service_role')
420
+ WITH CHECK (auth.role() = 'service_role');
421
+ EXCEPTION WHEN duplicate_object THEN NULL;
422
+ END;
423
+
424
+ BEGIN
425
+ CREATE POLICY "Service role full access on schema_version"
426
+ ON schema_version FOR ALL
427
+ USING (auth.role() = 'service_role')
428
+ WITH CHECK (auth.role() = 'service_role');
429
+ EXCEPTION WHEN duplicate_object THEN NULL;
430
+ END;
431
+ ELSE
432
+ -- Non-Supabase (RDS, local): open access policies
433
+ BEGIN
434
+ CREATE POLICY "Full access on memory_users"
435
+ ON memory_users FOR ALL USING (true) WITH CHECK (true);
436
+ EXCEPTION WHEN duplicate_object THEN NULL;
437
+ END;
438
+
439
+ BEGIN
440
+ CREATE POLICY "Full access on memory_domains"
441
+ ON memory_domains FOR ALL USING (true) WITH CHECK (true);
442
+ EXCEPTION WHEN duplicate_object THEN NULL;
443
+ END;
444
+
445
+ BEGIN
446
+ CREATE POLICY "Full access on traqr_memories"
447
+ ON traqr_memories FOR ALL USING (true) WITH CHECK (true);
448
+ EXCEPTION WHEN duplicate_object THEN NULL;
449
+ END;
450
+
451
+ BEGIN
452
+ CREATE POLICY "Full access on traqr_memory_history"
453
+ ON traqr_memory_history FOR ALL USING (true) WITH CHECK (true);
454
+ EXCEPTION WHEN duplicate_object THEN NULL;
455
+ END;
456
+
457
+ BEGIN
458
+ CREATE POLICY "Full access on relationships"
459
+ ON memory_relationships FOR ALL USING (true) WITH CHECK (true);
460
+ EXCEPTION WHEN duplicate_object THEN NULL;
461
+ END;
462
+
463
+ BEGIN
464
+ CREATE POLICY "Full access on entities"
465
+ ON memory_entities FOR ALL USING (true) WITH CHECK (true);
466
+ EXCEPTION WHEN duplicate_object THEN NULL;
467
+ END;
468
+
469
+ BEGIN
470
+ CREATE POLICY "Full access on entity_links"
471
+ ON memory_entity_links FOR ALL USING (true) WITH CHECK (true);
472
+ EXCEPTION WHEN duplicate_object THEN NULL;
473
+ END;
474
+
475
+ BEGIN
476
+ CREATE POLICY "Full access on schema_version"
477
+ ON schema_version FOR ALL USING (true) WITH CHECK (true);
478
+ EXCEPTION WHEN duplicate_object THEN NULL;
479
+ END;
480
+ END IF;
481
+ END $$;
482
+
483
+
484
+ -- ============================================================================
485
+ -- 6. RPC FUNCTIONS (v2 versions only)
486
+ -- ============================================================================
487
+
488
+ -- 6a. calculate_current_confidence — v2: 5-arg, STABLE (uses NOW())
489
+ CREATE OR REPLACE FUNCTION calculate_current_confidence(
490
+ p_original_confidence FLOAT,
491
+ p_created_at TIMESTAMP WITH TIME ZONE,
492
+ p_times_cited INTEGER DEFAULT 0,
493
+ p_times_returned INTEGER DEFAULT 0,
494
+ p_memory_type VARCHAR DEFAULT 'pattern'
495
+ )
496
+ RETURNS FLOAT
497
+ LANGUAGE plpgsql STABLE AS $$
498
+ DECLARE
499
+ years_elapsed FLOAT;
500
+ decay_rate FLOAT;
501
+ BEGIN
502
+ years_elapsed := EXTRACT(EPOCH FROM (NOW() - p_created_at)) / 31536000.0;
503
+
504
+ -- Type-aware decay rates (M5 Pipeline Design ADR)
505
+ IF p_memory_type = 'fact' THEN
506
+ decay_rate := 0.98; -- 2%/year (facts barely decay)
507
+ ELSIF p_memory_type = 'preference' THEN
508
+ IF p_times_cited > 0 THEN
509
+ decay_rate := 0.90; -- 10%/year if cited
510
+ ELSE
511
+ decay_rate := 0.85; -- 15%/year if uncited
512
+ END IF;
513
+ ELSE -- 'pattern' (default)
514
+ IF p_times_cited > 3 THEN
515
+ decay_rate := 0.95; -- 5%/year (proven valuable)
516
+ ELSIF p_times_cited >= 1 THEN
517
+ decay_rate := 0.90; -- 10%/year (moderate usage)
518
+ ELSIF p_times_returned > 5 AND p_times_cited = 0 THEN
519
+ decay_rate := 0.60; -- 40%/year (noise: returned but never cited)
520
+ ELSE
521
+ decay_rate := 0.70; -- 30%/year (default uncited)
522
+ END IF;
523
+ END IF;
524
+
525
+ RETURN GREATEST(0.1, p_original_confidence * POWER(decay_rate, years_elapsed));
526
+ END;
527
+ $$;
528
+
529
+ -- 6b. search_memories — v2: with p_latest_only + v2 columns
530
+ CREATE OR REPLACE FUNCTION search_memories(
531
+ p_query_embedding vector(1536),
532
+ p_project_id UUID DEFAULT NULL,
533
+ p_category VARCHAR DEFAULT NULL,
534
+ p_tags VARCHAR[] DEFAULT NULL,
535
+ p_include_archived BOOLEAN DEFAULT FALSE,
536
+ p_limit INTEGER DEFAULT 10,
537
+ p_similarity_threshold FLOAT DEFAULT 0.35,
538
+ p_latest_only BOOLEAN DEFAULT TRUE
539
+ )
540
+ RETURNS TABLE (
541
+ id UUID,
542
+ content TEXT,
543
+ summary VARCHAR,
544
+ category VARCHAR,
545
+ tags VARCHAR[],
546
+ context_tags VARCHAR[],
547
+ source_type VARCHAR,
548
+ source_ref VARCHAR,
549
+ source_project VARCHAR,
550
+ original_confidence FLOAT,
551
+ current_confidence FLOAT,
552
+ similarity FLOAT,
553
+ relevance_score FLOAT,
554
+ created_at TIMESTAMP WITH TIME ZONE,
555
+ updated_at TIMESTAMP WITH TIME ZONE,
556
+ durability VARCHAR,
557
+ is_universal BOOLEAN,
558
+ times_returned INTEGER,
559
+ times_cited INTEGER,
560
+ last_returned_at TIMESTAMP WITH TIME ZONE,
561
+ last_cited_at TIMESTAMP WITH TIME ZONE,
562
+ memory_type VARCHAR,
563
+ valid_at TIMESTAMP WITH TIME ZONE,
564
+ invalid_at TIMESTAMP WITH TIME ZONE,
565
+ is_latest BOOLEAN,
566
+ source_tool VARCHAR
567
+ )
568
+ LANGUAGE plpgsql AS $$
569
+ BEGIN
570
+ RETURN QUERY
571
+ SELECT
572
+ m.id, m.content, m.summary, m.category, m.tags, m.context_tags,
573
+ m.source_type, m.source_ref, m.source_project,
574
+ m.original_confidence,
575
+ calculate_current_confidence(
576
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
577
+ ) AS current_confidence,
578
+ 1 - (m.embedding <=> p_query_embedding) AS similarity,
579
+ (1 - (m.embedding <=> p_query_embedding))
580
+ * calculate_current_confidence(
581
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
582
+ )
583
+ * (1 + ln(1 + m.times_cited) * 0.1) AS relevance_score,
584
+ m.created_at, m.updated_at, m.durability, m.is_universal,
585
+ m.times_returned, m.times_cited, m.last_returned_at, m.last_cited_at,
586
+ m.memory_type, m.valid_at, m.invalid_at, m.is_latest, m.source_tool
587
+ FROM traqr_memories m
588
+ WHERE (p_include_archived OR m.is_archived = FALSE)
589
+ AND m.is_forgotten = FALSE
590
+ AND (NOT p_latest_only OR m.is_latest = TRUE)
591
+ AND (m.invalid_at IS NULL OR m.invalid_at > NOW())
592
+ AND (p_project_id IS NULL OR m.project_id = p_project_id)
593
+ AND (p_category IS NULL OR m.category = p_category)
594
+ AND (p_tags IS NULL OR m.tags && p_tags)
595
+ AND 1 - (m.embedding <=> p_query_embedding) >= p_similarity_threshold
596
+ ORDER BY relevance_score DESC
597
+ LIMIT p_limit;
598
+ END;
599
+ $$;
600
+
601
+ -- 6c. search_memories_cross_project — v2: with p_latest_only + v2 columns
602
+ CREATE OR REPLACE FUNCTION search_memories_cross_project(
603
+ p_query_embedding vector(1536),
604
+ p_project_id UUID DEFAULT NULL,
605
+ p_source_project VARCHAR DEFAULT NULL,
606
+ p_category VARCHAR DEFAULT NULL,
607
+ p_tags VARCHAR[] DEFAULT NULL,
608
+ p_include_archived BOOLEAN DEFAULT FALSE,
609
+ p_include_portable BOOLEAN DEFAULT TRUE,
610
+ p_agent_type VARCHAR DEFAULT NULL,
611
+ p_limit INTEGER DEFAULT 10,
612
+ p_similarity_threshold FLOAT DEFAULT 0.35,
613
+ p_latest_only BOOLEAN DEFAULT TRUE
614
+ )
615
+ RETURNS TABLE (
616
+ id UUID,
617
+ content TEXT,
618
+ summary VARCHAR,
619
+ category VARCHAR,
620
+ tags VARCHAR[],
621
+ context_tags VARCHAR[],
622
+ source_type VARCHAR,
623
+ source_ref VARCHAR,
624
+ source_project VARCHAR,
625
+ original_confidence FLOAT,
626
+ current_confidence FLOAT,
627
+ similarity FLOAT,
628
+ relevance_score FLOAT,
629
+ created_at TIMESTAMP WITH TIME ZONE,
630
+ updated_at TIMESTAMP WITH TIME ZONE,
631
+ durability VARCHAR,
632
+ is_universal BOOLEAN,
633
+ times_returned INTEGER,
634
+ times_cited INTEGER,
635
+ last_returned_at TIMESTAMP WITH TIME ZONE,
636
+ last_cited_at TIMESTAMP WITH TIME ZONE,
637
+ memory_type VARCHAR,
638
+ valid_at TIMESTAMP WITH TIME ZONE,
639
+ invalid_at TIMESTAMP WITH TIME ZONE,
640
+ is_latest BOOLEAN,
641
+ source_tool VARCHAR
642
+ )
643
+ LANGUAGE plpgsql AS $$
644
+ BEGIN
645
+ RETURN QUERY
646
+ SELECT
647
+ m.id, m.content, m.summary, m.category, m.tags, m.context_tags,
648
+ m.source_type, m.source_ref, m.source_project,
649
+ m.original_confidence,
650
+ calculate_current_confidence(
651
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
652
+ ) AS current_confidence,
653
+ 1 - (m.embedding <=> p_query_embedding) AS similarity,
654
+ (1 - (m.embedding <=> p_query_embedding))
655
+ * calculate_current_confidence(
656
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
657
+ )
658
+ * (1 + ln(1 + m.times_cited) * 0.1) AS relevance_score,
659
+ m.created_at, m.updated_at, m.durability, m.is_universal,
660
+ m.times_returned, m.times_cited, m.last_returned_at, m.last_cited_at,
661
+ m.memory_type, m.valid_at, m.invalid_at, m.is_latest, m.source_tool
662
+ FROM traqr_memories m
663
+ WHERE (p_include_archived OR m.is_archived = FALSE)
664
+ AND m.is_forgotten = FALSE
665
+ AND (NOT p_latest_only OR m.is_latest = TRUE)
666
+ AND (m.invalid_at IS NULL OR m.invalid_at > NOW())
667
+ AND (
668
+ (p_project_id IS NOT NULL AND m.project_id = p_project_id)
669
+ OR (p_source_project IS NOT NULL AND m.source_project = p_source_project)
670
+ OR (p_include_portable AND m.is_universal = TRUE)
671
+ )
672
+ AND (p_category IS NULL OR m.category = p_category)
673
+ AND (p_tags IS NULL OR m.tags && p_tags)
674
+ AND (p_agent_type IS NULL OR m.agent_type = p_agent_type)
675
+ AND 1 - (m.embedding <=> p_query_embedding) >= p_similarity_threshold
676
+ ORDER BY relevance_score DESC
677
+ LIMIT p_limit;
678
+ END;
679
+ $$;
680
+
681
+ -- 6d. archive_decayed_memories — v2: uses 5-arg confidence
682
+ CREATE OR REPLACE FUNCTION archive_decayed_memories()
683
+ RETURNS TABLE (
684
+ archived_id UUID,
685
+ content_preview TEXT,
686
+ final_confidence FLOAT,
687
+ times_cited INTEGER,
688
+ times_returned INTEGER,
689
+ reason TEXT
690
+ )
691
+ LANGUAGE plpgsql AS $$
692
+ BEGIN
693
+ RETURN QUERY
694
+ WITH decayed AS (
695
+ SELECT
696
+ m.id,
697
+ LEFT(m.content, 100) AS content_preview,
698
+ calculate_current_confidence(
699
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
700
+ ) AS conf,
701
+ m.times_cited,
702
+ m.times_returned,
703
+ CASE
704
+ WHEN m.times_returned > 5 AND m.times_cited = 0 THEN 'noise'
705
+ WHEN m.times_cited = 0 THEN 'uncited'
706
+ ELSE 'low-confidence'
707
+ END AS reason
708
+ FROM traqr_memories m
709
+ WHERE m.is_archived = FALSE
710
+ AND m.is_forgotten = FALSE
711
+ AND calculate_current_confidence(
712
+ m.original_confidence, m.created_at, m.times_cited, m.times_returned, m.memory_type
713
+ ) < 0.3
714
+ ),
715
+ archived AS (
716
+ UPDATE traqr_memories
717
+ SET is_archived = TRUE,
718
+ archived_at = NOW(),
719
+ archive_reason = d.reason,
720
+ updated_at = NOW()
721
+ FROM decayed d
722
+ WHERE traqr_memories.id = d.id
723
+ RETURNING traqr_memories.id
724
+ )
725
+ SELECT d.id, d.content_preview, d.conf, d.times_cited, d.times_returned, d.reason
726
+ FROM decayed d
727
+ JOIN archived a ON a.id = d.id;
728
+ END;
729
+ $$;
730
+
731
+ -- 6e. bm25_search — keyword search over dual tsvectors
732
+ -- NOTE: ts_rank_cd returns REAL; cast to ::FLOAT. VARCHAR columns need ::TEXT.
733
+ CREATE OR REPLACE FUNCTION bm25_search(
734
+ p_query_text TEXT,
735
+ p_project_id UUID DEFAULT NULL,
736
+ p_domain TEXT DEFAULT NULL,
737
+ p_category TEXT DEFAULT NULL,
738
+ p_limit INTEGER DEFAULT 20,
739
+ p_min_score FLOAT DEFAULT 0.01
740
+ )
741
+ RETURNS TABLE (
742
+ id UUID,
743
+ content TEXT,
744
+ summary TEXT,
745
+ bm25_score FLOAT,
746
+ domain TEXT,
747
+ category TEXT,
748
+ memory_type TEXT
749
+ )
750
+ LANGUAGE plpgsql AS $$
751
+ DECLARE
752
+ tsquery_en tsquery;
753
+ tsquery_simple tsquery;
754
+ BEGIN
755
+ tsquery_en := plainto_tsquery('english', p_query_text);
756
+ tsquery_simple := plainto_tsquery('simple', p_query_text);
757
+
758
+ RETURN QUERY
759
+ SELECT
760
+ m.id,
761
+ m.content,
762
+ m.summary::TEXT,
763
+ GREATEST(
764
+ ts_rank_cd(m.search_vector_en, tsquery_en),
765
+ ts_rank_cd(m.search_vector_simple, tsquery_simple)
766
+ )::FLOAT AS bm25_score,
767
+ m.domain::TEXT,
768
+ m.category::TEXT,
769
+ m.memory_type::TEXT
770
+ FROM traqr_memories m
771
+ WHERE (m.search_vector_en @@ tsquery_en
772
+ OR m.search_vector_simple @@ tsquery_simple)
773
+ AND m.is_archived = FALSE
774
+ AND m.is_forgotten = FALSE
775
+ AND (m.invalid_at IS NULL OR m.invalid_at > NOW())
776
+ AND (p_project_id IS NULL OR m.project_id = p_project_id)
777
+ AND (p_domain IS NULL OR m.domain = p_domain)
778
+ AND (p_category IS NULL OR m.category = p_category)
779
+ ORDER BY bm25_score DESC
780
+ LIMIT p_limit;
781
+ END;
782
+ $$;
783
+
784
+ -- 6f. temporal_search — valid_at range + embedding similarity
785
+ CREATE OR REPLACE FUNCTION temporal_search(
786
+ p_query_embedding vector(1536),
787
+ p_date_start TIMESTAMPTZ,
788
+ p_date_end TIMESTAMPTZ,
789
+ p_project_id UUID DEFAULT NULL,
790
+ p_similarity_threshold FLOAT DEFAULT 0.3,
791
+ p_limit INTEGER DEFAULT 20
792
+ )
793
+ RETURNS TABLE (
794
+ id UUID,
795
+ content TEXT,
796
+ summary TEXT,
797
+ similarity FLOAT,
798
+ temporal_proximity FLOAT,
799
+ valid_at TIMESTAMPTZ
800
+ )
801
+ LANGUAGE plpgsql AS $$
802
+ DECLARE
803
+ date_mid TIMESTAMPTZ;
804
+ total_days FLOAT;
805
+ BEGIN
806
+ date_mid := p_date_start + (p_date_end - p_date_start) / 2;
807
+ total_days := GREATEST(EXTRACT(EPOCH FROM (p_date_end - p_date_start)) / 86400.0, 1.0);
808
+
809
+ RETURN QUERY
810
+ SELECT
811
+ m.id,
812
+ m.content,
813
+ m.summary,
814
+ 1 - (m.embedding <=> p_query_embedding) AS similarity,
815
+ GREATEST(0.0, 1.0 - (
816
+ ABS(EXTRACT(EPOCH FROM (m.valid_at - date_mid)) / 86400.0) / (total_days / 2.0)
817
+ )) AS temporal_proximity,
818
+ m.valid_at
819
+ FROM traqr_memories m
820
+ WHERE m.valid_at BETWEEN p_date_start AND p_date_end
821
+ AND m.is_archived = FALSE
822
+ AND m.is_forgotten = FALSE
823
+ AND (p_project_id IS NULL OR m.project_id = p_project_id)
824
+ AND 1 - (m.embedding <=> p_query_embedding) >= p_similarity_threshold
825
+ ORDER BY similarity DESC
826
+ LIMIT p_limit;
827
+ END;
828
+ $$;
829
+
830
+ -- 6g. graph_search — link expansion CTE traversing memory_relationships
831
+ CREATE OR REPLACE FUNCTION graph_search(
832
+ p_seed_ids UUID[],
833
+ p_edge_types TEXT[] DEFAULT ARRAY['updates', 'extends', 'derives', 'related'],
834
+ p_max_depth INTEGER DEFAULT 2,
835
+ p_limit INTEGER DEFAULT 20
836
+ )
837
+ RETURNS TABLE (
838
+ id UUID,
839
+ content TEXT,
840
+ summary TEXT,
841
+ graph_score FLOAT,
842
+ edge_type TEXT,
843
+ depth INTEGER
844
+ )
845
+ LANGUAGE plpgsql AS $$
846
+ BEGIN
847
+ RETURN QUERY
848
+ WITH RECURSIVE graph_walk AS (
849
+ -- Seed: direct neighbors of seed memories
850
+ SELECT
851
+ mr.target_memory_id AS memory_id,
852
+ mr.edge_type,
853
+ mr.confidence AS score,
854
+ 1 AS depth
855
+ FROM memory_relationships mr
856
+ WHERE mr.source_memory_id = ANY(p_seed_ids)
857
+ AND mr.edge_type = ANY(p_edge_types)
858
+
859
+ UNION ALL
860
+
861
+ -- Expand: neighbors of neighbors (0.7 decay per hop)
862
+ SELECT
863
+ mr.target_memory_id,
864
+ mr.edge_type,
865
+ gw.score * mr.confidence * 0.7 AS score,
866
+ gw.depth + 1
867
+ FROM graph_walk gw
868
+ JOIN memory_relationships mr ON mr.source_memory_id = gw.memory_id
869
+ WHERE gw.depth < p_max_depth
870
+ AND mr.edge_type = ANY(p_edge_types)
871
+ )
872
+ SELECT
873
+ m.id,
874
+ m.content,
875
+ m.summary,
876
+ MAX(gw.score) AS graph_score,
877
+ (ARRAY_AGG(gw.edge_type ORDER BY gw.score DESC))[1] AS edge_type,
878
+ MIN(gw.depth) AS depth
879
+ FROM graph_walk gw
880
+ JOIN traqr_memories m ON m.id = gw.memory_id
881
+ WHERE m.is_archived = FALSE AND m.is_forgotten = FALSE
882
+ GROUP BY m.id, m.content, m.summary
883
+ ORDER BY graph_score DESC
884
+ LIMIT p_limit;
885
+ END;
886
+ $$;
887
+
888
+ -- 6h. search_entities — embedding-based entity lookup
889
+ CREATE OR REPLACE FUNCTION search_entities(
890
+ p_user_id UUID,
891
+ p_embedding vector(1536),
892
+ p_entity_type VARCHAR DEFAULT NULL,
893
+ p_threshold FLOAT DEFAULT 0.85,
894
+ p_limit INTEGER DEFAULT 5
895
+ )
896
+ RETURNS TABLE (
897
+ id UUID,
898
+ name VARCHAR,
899
+ entity_type VARCHAR,
900
+ similarity FLOAT,
901
+ mentions_count INTEGER
902
+ )
903
+ LANGUAGE plpgsql AS $$
904
+ BEGIN
905
+ RETURN QUERY
906
+ SELECT
907
+ e.id, e.name, e.entity_type,
908
+ 1 - (e.embedding <=> p_embedding) AS similarity,
909
+ e.mentions_count
910
+ FROM memory_entities e
911
+ WHERE e.user_id = p_user_id
912
+ AND e.is_archived = FALSE
913
+ AND (p_entity_type IS NULL OR e.entity_type = p_entity_type)
914
+ AND 1 - (e.embedding <=> p_embedding) >= p_threshold
915
+ ORDER BY similarity DESC
916
+ LIMIT p_limit;
917
+ END;
918
+ $$;
919
+
920
+ -- 6i. count_entity_mentions — 3+ threshold check
921
+ CREATE OR REPLACE FUNCTION count_entity_mentions(
922
+ p_user_id UUID,
923
+ p_name VARCHAR
924
+ )
925
+ RETURNS INTEGER
926
+ LANGUAGE plpgsql AS $$
927
+ DECLARE
928
+ mention_count INTEGER;
929
+ BEGIN
930
+ SELECT COUNT(*) INTO mention_count
931
+ FROM traqr_memories m
932
+ WHERE m.user_id = p_user_id
933
+ AND m.is_archived = FALSE
934
+ AND m.is_forgotten = FALSE
935
+ AND m.content ILIKE '%' || p_name || '%';
936
+ RETURN mention_count;
937
+ END;
938
+ $$;
939
+
940
+ -- 6j. forget_expired_memories — daily cron function
941
+ CREATE OR REPLACE FUNCTION forget_expired_memories()
942
+ RETURNS INTEGER
943
+ LANGUAGE plpgsql AS $$
944
+ DECLARE
945
+ forgotten_count INTEGER;
946
+ BEGIN
947
+ WITH to_forget AS (
948
+ UPDATE traqr_memories
949
+ SET is_forgotten = TRUE,
950
+ forgotten_at = NOW(),
951
+ updated_at = NOW()
952
+ WHERE is_forgotten = FALSE
953
+ AND forget_after IS NOT NULL
954
+ AND forget_after < NOW()
955
+ RETURNING id
956
+ )
957
+ SELECT COUNT(*) INTO forgotten_count FROM to_forget;
958
+ RETURN forgotten_count;
959
+ END;
960
+ $$;
961
+
962
+ -- 6k. increment_memory_returns — batch fire-and-forget from context assembly
963
+ CREATE OR REPLACE FUNCTION increment_memory_returns(p_memory_ids UUID[])
964
+ RETURNS void AS $$
965
+ BEGIN
966
+ UPDATE traqr_memories
967
+ SET
968
+ times_returned = COALESCE(times_returned, 0) + 1,
969
+ last_returned_at = NOW()
970
+ WHERE id = ANY(p_memory_ids)
971
+ AND is_archived = FALSE;
972
+ END;
973
+ $$ LANGUAGE plpgsql;
974
+
975
+ -- 6l. cite_memory — record a citation (resets decay timer)
976
+ CREATE OR REPLACE FUNCTION cite_memory(p_memory_id UUID)
977
+ RETURNS void AS $$
978
+ BEGIN
979
+ UPDATE traqr_memories
980
+ SET
981
+ times_cited = COALESCE(times_cited, 0) + 1,
982
+ last_cited_at = NOW(),
983
+ last_validated = NOW()
984
+ WHERE id = p_memory_id
985
+ AND is_archived = FALSE;
986
+ END;
987
+ $$ LANGUAGE plpgsql;
988
+
989
+
990
+ -- ============================================================================
991
+ -- 7. SCHEMA VERSION TRACKING + MIGRATION BOOTSTRAP
992
+ -- ============================================================================
993
+
994
+ INSERT INTO schema_version (version, description)
995
+ VALUES (1, 'Memory Engine v1 -- base schema (migrations 001-010)')
996
+ ON CONFLICT (version) DO NOTHING;
997
+
998
+ INSERT INTO schema_version (version, description)
999
+ VALUES (2, 'Memory Engine v2 -- pipeline, retrieval, temporal, entities (M5-M9)')
1000
+ ON CONFLICT (version) DO NOTHING;
1001
+
1002
+ -- Migration tracking table (so migrate.ts runner works for future migrations)
1003
+ CREATE TABLE IF NOT EXISTS _traqr_migrations (
1004
+ name TEXT PRIMARY KEY,
1005
+ applied_at TIMESTAMPTZ DEFAULT NOW()
1006
+ );
1007
+
1008
+ INSERT INTO _traqr_migrations (name) VALUES
1009
+ ('001_memory_schema.sql'),
1010
+ ('002_slack_task_queue.sql'),
1011
+ ('003_cross_project_memory.sql'),
1012
+ ('004_memory_durability.sql'),
1013
+ ('005_memory_citation_tracking.sql'),
1014
+ ('006_memory_curation_functions.sql'),
1015
+ ('007_accelerated_decay.sql'),
1016
+ ('008_audit_cleanup.sql'),
1017
+ ('009_quality_audit_cleanup.sql'),
1018
+ ('010_citation_boosted_ranking.sql'),
1019
+ ('011_memory_engine_v2_schema.sql')
1020
+ ON CONFLICT DO NOTHING;
1021
+
1022
+
1023
+ -- ============================================================================
1024
+ -- DONE. Verify with:
1025
+ -- ============================================================================
1026
+ --
1027
+ -- SELECT tablename FROM pg_tables WHERE schemaname = 'public'
1028
+ -- AND tablename IN ('memory_users','memory_domains','traqr_memories',
1029
+ -- 'traqr_memory_history','memory_relationships','memory_entities',
1030
+ -- 'memory_entity_links','schema_version','_traqr_migrations');
1031
+ --
1032
+ -- SELECT column_name, data_type FROM information_schema.columns
1033
+ -- WHERE table_name = 'traqr_memories' ORDER BY ordinal_position;
1034
+ --
1035
+ -- SELECT indexname FROM pg_indexes WHERE tablename = 'traqr_memories';
1036
+ --
1037
+ -- SELECT * FROM bm25_search('test query', NULL, NULL, NULL, 5, 0.01);