@engram-mem/supabase 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.
@@ -0,0 +1,650 @@
1
+ // =============================================================================
2
+ // Engram PostgreSQL Migrations
3
+ // Apply these SQL strings via Supabase dashboard, psql, or a migration tool.
4
+ // =============================================================================
5
+ /**
6
+ * Migration 004: Add Engram columns to existing tables and rename memory_knowledge.
7
+ * Applies on top of the legacy 001–003 migrations (initial schema, search functions,
8
+ * enable RLS).
9
+ */
10
+ export const MIGRATION_004 = /* sql */ `
11
+ -- Migration 004: Engram v1 — extend existing tables
12
+ -- Run this after 001_initial_schema, 002_search_functions, 003_enable_rls
13
+
14
+ -- Add missing columns to memory_episodes
15
+ ALTER TABLE memory_episodes
16
+ ADD COLUMN IF NOT EXISTS salience real NOT NULL DEFAULT 0.3
17
+ CHECK (salience >= 0.0 AND salience <= 1.0),
18
+ ADD COLUMN IF NOT EXISTS access_count integer NOT NULL DEFAULT 0,
19
+ ADD COLUMN IF NOT EXISTS last_accessed timestamptz,
20
+ ADD COLUMN IF NOT EXISTS consolidated_at timestamptz,
21
+ ADD COLUMN IF NOT EXISTS entities text[] NOT NULL DEFAULT '{}';
22
+
23
+ -- Add missing columns to memory_digests
24
+ ALTER TABLE memory_digests
25
+ ADD COLUMN IF NOT EXISTS source_digest_ids uuid[] NOT NULL DEFAULT '{}',
26
+ ADD COLUMN IF NOT EXISTS level integer NOT NULL DEFAULT 0
27
+ CHECK (level >= 0 AND level <= 10);
28
+
29
+ -- Rename memory_knowledge -> memory_semantic (catalog-only, instantaneous)
30
+ -- Skip if already renamed
31
+ DO $$
32
+ BEGIN
33
+ IF EXISTS (
34
+ SELECT 1 FROM information_schema.tables
35
+ WHERE table_schema = 'public' AND table_name = 'memory_knowledge'
36
+ ) AND NOT EXISTS (
37
+ SELECT 1 FROM information_schema.tables
38
+ WHERE table_schema = 'public' AND table_name = 'memory_semantic'
39
+ ) THEN
40
+ ALTER TABLE memory_knowledge RENAME TO memory_semantic;
41
+ END IF;
42
+ END $$;
43
+
44
+ -- Add missing columns to memory_semantic
45
+ ALTER TABLE memory_semantic
46
+ ADD COLUMN IF NOT EXISTS source_episode_ids uuid[] NOT NULL DEFAULT '{}',
47
+ ADD COLUMN IF NOT EXISTS decay_rate real NOT NULL DEFAULT 0.02
48
+ CHECK (decay_rate > 0.0 AND decay_rate <= 1.0),
49
+ ADD COLUMN IF NOT EXISTS supersedes uuid REFERENCES memories(id),
50
+ ADD COLUMN IF NOT EXISTS superseded_by uuid REFERENCES memories(id),
51
+ ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
52
+
53
+ -- Record migration
54
+ INSERT INTO schema_migrations (version, checksum)
55
+ VALUES ('004_engram_v1', md5('004'))
56
+ ON CONFLICT DO NOTHING;
57
+ `;
58
+ /**
59
+ * Migration 005: Create new tables — memories pool, memory_procedural,
60
+ * memory_associations, consolidation_runs, sensory_snapshots.
61
+ */
62
+ export const MIGRATION_005 = /* sql */ `
63
+ -- Migration 005: New Engram tables
64
+
65
+ -- Enable required extensions
66
+ CREATE EXTENSION IF NOT EXISTS vector;
67
+ CREATE EXTENSION IF NOT EXISTS pg_trgm;
68
+
69
+ -- Memory ID Pool (base table for FK integrity across all memory types)
70
+ CREATE TABLE IF NOT EXISTS memories (
71
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
72
+ type text NOT NULL
73
+ CHECK (type IN ('episode', 'digest', 'semantic', 'procedural')),
74
+ created_at timestamptz NOT NULL DEFAULT now()
75
+ );
76
+
77
+ -- Episodic Memory (full schema — idempotent)
78
+ CREATE TABLE IF NOT EXISTS memory_episodes (
79
+ id uuid PRIMARY KEY REFERENCES memories(id),
80
+ session_id text NOT NULL,
81
+ role text NOT NULL
82
+ CHECK (role IN ('user', 'assistant', 'system')),
83
+ content text NOT NULL,
84
+ salience real NOT NULL DEFAULT 0.3
85
+ CHECK (salience >= 0.0 AND salience <= 1.0),
86
+ access_count integer NOT NULL DEFAULT 0,
87
+ last_accessed timestamptz,
88
+ consolidated_at timestamptz,
89
+ embedding vector(1536),
90
+ entities text[] NOT NULL DEFAULT '{}',
91
+ metadata jsonb NOT NULL DEFAULT '{}',
92
+ created_at timestamptz NOT NULL DEFAULT now()
93
+ );
94
+
95
+ CREATE INDEX IF NOT EXISTS idx_episodes_session
96
+ ON memory_episodes(session_id);
97
+ CREATE INDEX IF NOT EXISTS idx_episodes_unconsolidated
98
+ ON memory_episodes(session_id, salience DESC)
99
+ WHERE consolidated_at IS NULL;
100
+ CREATE INDEX IF NOT EXISTS idx_episodes_created
101
+ ON memory_episodes(created_at DESC);
102
+ CREATE INDEX IF NOT EXISTS idx_episodes_last_accessed
103
+ ON memory_episodes(last_accessed)
104
+ WHERE last_accessed IS NOT NULL;
105
+ CREATE INDEX IF NOT EXISTS idx_episodes_entities
106
+ ON memory_episodes USING GIN(entities);
107
+ CREATE INDEX IF NOT EXISTS idx_episodes_content_trgm
108
+ ON memory_episodes USING GIN(content gin_trgm_ops);
109
+
110
+ -- Digest Layer
111
+ CREATE TABLE IF NOT EXISTS memory_digests (
112
+ id uuid PRIMARY KEY REFERENCES memories(id),
113
+ session_id text NOT NULL,
114
+ summary text NOT NULL,
115
+ key_topics text[] NOT NULL DEFAULT '{}',
116
+ source_episode_ids uuid[] NOT NULL DEFAULT '{}',
117
+ source_digest_ids uuid[] NOT NULL DEFAULT '{}',
118
+ level integer NOT NULL DEFAULT 0
119
+ CHECK (level >= 0 AND level <= 10),
120
+ embedding vector(1536),
121
+ metadata jsonb NOT NULL DEFAULT '{}',
122
+ created_at timestamptz NOT NULL DEFAULT now()
123
+ );
124
+
125
+ CREATE INDEX IF NOT EXISTS idx_digests_session
126
+ ON memory_digests(session_id);
127
+ CREATE INDEX IF NOT EXISTS idx_digests_created
128
+ ON memory_digests(created_at DESC);
129
+ CREATE INDEX IF NOT EXISTS idx_digests_topics
130
+ ON memory_digests USING GIN(key_topics);
131
+ CREATE INDEX IF NOT EXISTS idx_digests_source_episodes
132
+ ON memory_digests USING GIN(source_episode_ids);
133
+
134
+ -- Semantic Memory
135
+ CREATE TABLE IF NOT EXISTS memory_semantic (
136
+ id uuid PRIMARY KEY REFERENCES memories(id),
137
+ topic text NOT NULL,
138
+ content text NOT NULL,
139
+ confidence real NOT NULL DEFAULT 0.5
140
+ CHECK (confidence >= 0.0 AND confidence <= 1.0),
141
+ source_digest_ids uuid[] NOT NULL DEFAULT '{}',
142
+ source_episode_ids uuid[] NOT NULL DEFAULT '{}',
143
+ access_count integer NOT NULL DEFAULT 0,
144
+ last_accessed timestamptz,
145
+ decay_rate real NOT NULL DEFAULT 0.02
146
+ CHECK (decay_rate > 0.0 AND decay_rate <= 1.0),
147
+ supersedes uuid REFERENCES memories(id),
148
+ superseded_by uuid REFERENCES memories(id),
149
+ embedding vector(1536),
150
+ metadata jsonb NOT NULL DEFAULT '{}',
151
+ created_at timestamptz NOT NULL DEFAULT now(),
152
+ updated_at timestamptz NOT NULL DEFAULT now()
153
+ );
154
+
155
+ CREATE INDEX IF NOT EXISTS idx_semantic_topic
156
+ ON memory_semantic(topic);
157
+ CREATE INDEX IF NOT EXISTS idx_semantic_confidence
158
+ ON memory_semantic(confidence DESC)
159
+ WHERE superseded_by IS NULL;
160
+ CREATE INDEX IF NOT EXISTS idx_semantic_last_accessed
161
+ ON memory_semantic(last_accessed);
162
+ CREATE INDEX IF NOT EXISTS idx_semantic_created
163
+ ON memory_semantic(created_at DESC);
164
+
165
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
166
+ RETURNS TRIGGER LANGUAGE plpgsql AS $$
167
+ BEGIN
168
+ NEW.updated_at = now();
169
+ RETURN NEW;
170
+ END;
171
+ $$;
172
+
173
+ DO $$
174
+ BEGIN
175
+ IF NOT EXISTS (
176
+ SELECT 1 FROM pg_trigger WHERE tgname = 'semantic_updated_at'
177
+ ) THEN
178
+ CREATE TRIGGER semantic_updated_at
179
+ BEFORE UPDATE ON memory_semantic
180
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
181
+ END IF;
182
+ END $$;
183
+
184
+ -- Procedural Memory
185
+ CREATE TABLE IF NOT EXISTS memory_procedural (
186
+ id uuid PRIMARY KEY REFERENCES memories(id),
187
+ category text NOT NULL
188
+ CHECK (category IN ('workflow', 'preference', 'habit', 'pattern', 'convention')),
189
+ trigger_text text NOT NULL,
190
+ procedure text NOT NULL,
191
+ confidence real NOT NULL DEFAULT 0.5
192
+ CHECK (confidence >= 0.0 AND confidence <= 1.0),
193
+ observation_count integer NOT NULL DEFAULT 1,
194
+ last_observed timestamptz NOT NULL DEFAULT now(),
195
+ first_observed timestamptz NOT NULL DEFAULT now(),
196
+ access_count integer NOT NULL DEFAULT 0,
197
+ last_accessed timestamptz,
198
+ decay_rate real NOT NULL DEFAULT 0.01
199
+ CHECK (decay_rate > 0.0 AND decay_rate <= 1.0),
200
+ source_episode_ids uuid[] NOT NULL DEFAULT '{}',
201
+ embedding vector(1536),
202
+ metadata jsonb NOT NULL DEFAULT '{}',
203
+ created_at timestamptz NOT NULL DEFAULT now(),
204
+ updated_at timestamptz NOT NULL DEFAULT now()
205
+ );
206
+
207
+ CREATE INDEX IF NOT EXISTS idx_procedural_category
208
+ ON memory_procedural(category);
209
+ CREATE INDEX IF NOT EXISTS idx_procedural_confidence
210
+ ON memory_procedural(confidence DESC);
211
+ CREATE INDEX IF NOT EXISTS idx_procedural_last_accessed
212
+ ON memory_procedural(last_accessed);
213
+ CREATE INDEX IF NOT EXISTS idx_procedural_created
214
+ ON memory_procedural(created_at DESC);
215
+
216
+ DO $$
217
+ BEGIN
218
+ IF NOT EXISTS (
219
+ SELECT 1 FROM pg_trigger WHERE tgname = 'procedural_updated_at'
220
+ ) THEN
221
+ CREATE TRIGGER procedural_updated_at
222
+ BEFORE UPDATE ON memory_procedural
223
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
224
+ END IF;
225
+ END $$;
226
+
227
+ -- Associative Network
228
+ CREATE TABLE IF NOT EXISTS memory_associations (
229
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
230
+ source_id uuid NOT NULL REFERENCES memories(id),
231
+ source_type text NOT NULL
232
+ CHECK (source_type IN ('episode', 'digest', 'semantic', 'procedural')),
233
+ target_id uuid NOT NULL REFERENCES memories(id),
234
+ target_type text NOT NULL
235
+ CHECK (target_type IN ('episode', 'digest', 'semantic', 'procedural')),
236
+ edge_type text NOT NULL
237
+ CHECK (edge_type IN ('temporal', 'causal', 'topical', 'supports',
238
+ 'contradicts', 'elaborates', 'derives_from', 'co_recalled')),
239
+ strength real NOT NULL DEFAULT 0.3
240
+ CHECK (strength >= 0.0 AND strength <= 1.0),
241
+ last_activated timestamptz,
242
+ metadata jsonb NOT NULL DEFAULT '{}',
243
+ created_at timestamptz NOT NULL DEFAULT now(),
244
+
245
+ CONSTRAINT uq_association_pair UNIQUE (source_id, target_id, edge_type)
246
+ );
247
+
248
+ CREATE INDEX IF NOT EXISTS idx_assoc_source_strength
249
+ ON memory_associations(source_id, strength DESC);
250
+ CREATE INDEX IF NOT EXISTS idx_assoc_target_strength
251
+ ON memory_associations(target_id, strength DESC);
252
+ CREATE INDEX IF NOT EXISTS idx_assoc_source_type_strength
253
+ ON memory_associations(source_id, edge_type, strength DESC);
254
+ CREATE INDEX IF NOT EXISTS idx_assoc_prune
255
+ ON memory_associations(strength, last_activated)
256
+ WHERE strength < 0.1;
257
+
258
+ -- Consolidation Run Tracking
259
+ CREATE TABLE IF NOT EXISTS consolidation_runs (
260
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
261
+ cycle text NOT NULL
262
+ CHECK (cycle IN ('light', 'deep', 'dream', 'decay')),
263
+ started_at timestamptz NOT NULL DEFAULT now(),
264
+ completed_at timestamptz,
265
+ status text NOT NULL DEFAULT 'running'
266
+ CHECK (status IN ('running', 'completed', 'failed')),
267
+ metadata jsonb NOT NULL DEFAULT '{}'
268
+ );
269
+
270
+ CREATE INDEX IF NOT EXISTS idx_consolidation_runs_status
271
+ ON consolidation_runs(status, started_at DESC);
272
+
273
+ -- Sensory Buffer Persistence
274
+ CREATE TABLE IF NOT EXISTS sensory_snapshots (
275
+ session_id text PRIMARY KEY,
276
+ snapshot jsonb NOT NULL DEFAULT '{}',
277
+ saved_at timestamptz NOT NULL DEFAULT now()
278
+ );
279
+
280
+ -- Schema migrations tracking
281
+ CREATE TABLE IF NOT EXISTS schema_migrations (
282
+ version text PRIMARY KEY,
283
+ applied_at timestamptz NOT NULL DEFAULT now(),
284
+ checksum text NOT NULL
285
+ );
286
+
287
+ INSERT INTO schema_migrations (version, checksum) VALUES
288
+ ('005_engram_tables', md5('005'))
289
+ ON CONFLICT DO NOTHING;
290
+ `;
291
+ /**
292
+ * Migration 006: RPC functions — engram_recall, engram_association_walk,
293
+ * engram_record_access, engram_upsert_co_recalled, engram_decay_pass,
294
+ * engram_dream_cycle.
295
+ */
296
+ export const MIGRATION_006 = /* sql */ `
297
+ -- Migration 006: RPC Functions
298
+
299
+ -- Unified recall across all memory types
300
+ CREATE OR REPLACE FUNCTION engram_recall(
301
+ p_query_embedding vector,
302
+ p_session_id text DEFAULT NULL,
303
+ p_match_count int DEFAULT 10,
304
+ p_min_similarity float DEFAULT 0.3,
305
+ p_include_episodes bool DEFAULT true,
306
+ p_include_digests bool DEFAULT true,
307
+ p_include_semantic bool DEFAULT true,
308
+ p_include_procedural bool DEFAULT true
309
+ )
310
+ RETURNS TABLE (
311
+ id uuid,
312
+ memory_type text,
313
+ content text,
314
+ salience float,
315
+ access_count int,
316
+ created_at timestamptz,
317
+ similarity float,
318
+ entities text[]
319
+ )
320
+ LANGUAGE sql STABLE PARALLEL SAFE
321
+ SECURITY DEFINER
322
+ SET search_path = public
323
+ AS $$
324
+ SELECT id, 'episode'::text, content, salience::float, access_count,
325
+ created_at, (1-(embedding<=>p_query_embedding))::float, entities
326
+ FROM memory_episodes
327
+ WHERE p_include_episodes AND embedding IS NOT NULL
328
+ AND (p_session_id IS NULL OR session_id = p_session_id)
329
+ AND (1-(embedding<=>p_query_embedding)) >= p_min_similarity
330
+ ORDER BY embedding<=>p_query_embedding
331
+ LIMIT p_match_count
332
+ UNION ALL
333
+ SELECT id, 'digest'::text, summary, 0.5::float, 0,
334
+ created_at, (1-(embedding<=>p_query_embedding))::float, key_topics
335
+ FROM memory_digests
336
+ WHERE p_include_digests AND embedding IS NOT NULL
337
+ AND (1-(embedding<=>p_query_embedding)) >= p_min_similarity
338
+ ORDER BY embedding<=>p_query_embedding
339
+ LIMIT p_match_count
340
+ UNION ALL
341
+ SELECT id, 'semantic'::text, content, confidence::float, access_count,
342
+ created_at, (1-(embedding<=>p_query_embedding))::float, ARRAY[]::text[]
343
+ FROM memory_semantic
344
+ WHERE p_include_semantic AND embedding IS NOT NULL
345
+ AND superseded_by IS NULL
346
+ AND (1-(embedding<=>p_query_embedding)) >= p_min_similarity
347
+ ORDER BY embedding<=>p_query_embedding
348
+ LIMIT p_match_count
349
+ UNION ALL
350
+ SELECT id, 'procedural'::text, procedure, confidence::float, access_count,
351
+ created_at, (1-(embedding<=>p_query_embedding))::float, ARRAY[]::text[]
352
+ FROM memory_procedural
353
+ WHERE p_include_procedural AND embedding IS NOT NULL
354
+ AND (1-(embedding<=>p_query_embedding)) >= p_min_similarity
355
+ ORDER BY embedding<=>p_query_embedding
356
+ LIMIT p_match_count
357
+ $$;
358
+
359
+ -- Association walk (single SQL call, replaces N+1 queries)
360
+ CREATE OR REPLACE FUNCTION engram_association_walk(
361
+ p_seed_ids uuid[],
362
+ p_max_hops int DEFAULT 2,
363
+ p_min_strength float DEFAULT 0.2,
364
+ p_limit int DEFAULT 20
365
+ )
366
+ RETURNS TABLE (
367
+ memory_id uuid,
368
+ memory_type text,
369
+ depth int,
370
+ path_strength float
371
+ )
372
+ LANGUAGE sql STABLE
373
+ SECURITY DEFINER
374
+ SET search_path = public
375
+ AS $$
376
+ WITH RECURSIVE walk AS (
377
+ SELECT
378
+ s.id AS memory_id,
379
+ NULL::text AS memory_type,
380
+ 0 AS depth,
381
+ ARRAY[s.id] AS visited_ids,
382
+ 1.0::float AS path_strength
383
+ FROM unnest(p_seed_ids) AS s(id)
384
+
385
+ UNION ALL
386
+
387
+ SELECT
388
+ CASE WHEN a.source_id = w.memory_id THEN a.target_id ELSE a.source_id END,
389
+ CASE WHEN a.source_id = w.memory_id THEN a.target_type ELSE a.source_type END,
390
+ w.depth + 1,
391
+ w.visited_ids ||
392
+ (CASE WHEN a.source_id = w.memory_id THEN a.target_id ELSE a.source_id END),
393
+ (w.path_strength * a.strength)::float
394
+ FROM walk w
395
+ JOIN memory_associations a ON
396
+ (a.source_id = w.memory_id OR a.target_id = w.memory_id)
397
+ WHERE
398
+ w.depth < p_max_hops
399
+ AND a.strength >= p_min_strength
400
+ AND NOT (
401
+ CASE WHEN a.source_id = w.memory_id THEN a.target_id
402
+ ELSE a.source_id END
403
+ ) = ANY(w.visited_ids)
404
+ )
405
+ SELECT DISTINCT ON (memory_id)
406
+ memory_id,
407
+ memory_type,
408
+ depth,
409
+ path_strength
410
+ FROM walk
411
+ WHERE depth > 0
412
+ ORDER BY memory_id, path_strength DESC, depth ASC
413
+ LIMIT p_limit;
414
+ $$;
415
+
416
+ -- Atomic reconsolidation update
417
+ CREATE OR REPLACE FUNCTION engram_record_access(
418
+ p_id uuid,
419
+ p_memory_type text,
420
+ p_conf_boost float DEFAULT 0.0
421
+ )
422
+ RETURNS void
423
+ LANGUAGE plpgsql
424
+ SECURITY DEFINER
425
+ SET search_path = public
426
+ AS $$
427
+ BEGIN
428
+ IF p_memory_type = 'episode' THEN
429
+ UPDATE memory_episodes
430
+ SET access_count = access_count + 1,
431
+ last_accessed = now()
432
+ WHERE id = p_id;
433
+
434
+ ELSIF p_memory_type = 'semantic' THEN
435
+ UPDATE memory_semantic
436
+ SET access_count = access_count + 1,
437
+ last_accessed = now(),
438
+ confidence = LEAST(1.0, confidence + p_conf_boost),
439
+ updated_at = now()
440
+ WHERE id = p_id;
441
+
442
+ ELSIF p_memory_type = 'procedural' THEN
443
+ UPDATE memory_procedural
444
+ SET access_count = access_count + 1,
445
+ last_accessed = now(),
446
+ confidence = LEAST(1.0, confidence + p_conf_boost),
447
+ updated_at = now()
448
+ WHERE id = p_id;
449
+ END IF;
450
+ END;
451
+ $$;
452
+
453
+ -- Upsert co_recalled association
454
+ CREATE OR REPLACE FUNCTION engram_upsert_co_recalled(
455
+ p_source_id uuid,
456
+ p_source_type text,
457
+ p_target_id uuid,
458
+ p_target_type text
459
+ )
460
+ RETURNS void
461
+ LANGUAGE sql
462
+ SECURITY DEFINER
463
+ SET search_path = public
464
+ AS $$
465
+ INSERT INTO memory_associations
466
+ (source_id, source_type, target_id, target_type, edge_type, strength, last_activated)
467
+ VALUES
468
+ (p_source_id, p_source_type, p_target_id, p_target_type, 'co_recalled', 0.2, now())
469
+ ON CONFLICT (source_id, target_id, edge_type) DO UPDATE SET
470
+ strength = LEAST(1.0, memory_associations.strength + 0.1),
471
+ last_activated = now();
472
+ $$;
473
+
474
+ -- Batch decay pass
475
+ CREATE OR REPLACE FUNCTION engram_decay_pass(
476
+ p_semantic_decay_rate float DEFAULT 0.02,
477
+ p_procedural_decay_rate float DEFAULT 0.01,
478
+ p_semantic_days int DEFAULT 30,
479
+ p_procedural_days int DEFAULT 60,
480
+ p_edge_prune_strength float DEFAULT 0.05,
481
+ p_edge_prune_days int DEFAULT 90
482
+ )
483
+ RETURNS TABLE (
484
+ semantic_decayed int,
485
+ procedural_decayed int,
486
+ edges_pruned int
487
+ )
488
+ LANGUAGE plpgsql
489
+ SECURITY DEFINER
490
+ SET search_path = public
491
+ AS $$
492
+ DECLARE
493
+ v_semantic_decayed int;
494
+ v_procedural_decayed int;
495
+ v_edges_pruned int;
496
+ BEGIN
497
+ UPDATE memory_semantic
498
+ SET confidence = GREATEST(0.05, confidence - p_semantic_decay_rate),
499
+ updated_at = now()
500
+ WHERE confidence > 0.05
501
+ AND (last_accessed IS NULL OR last_accessed < now() - (p_semantic_days || ' days')::interval);
502
+ GET DIAGNOSTICS v_semantic_decayed = ROW_COUNT;
503
+
504
+ UPDATE memory_procedural
505
+ SET confidence = GREATEST(0.05, confidence - p_procedural_decay_rate),
506
+ updated_at = now()
507
+ WHERE confidence > 0.05
508
+ AND (last_accessed IS NULL OR last_accessed < now() - (p_procedural_days || ' days')::interval);
509
+ GET DIAGNOSTICS v_procedural_decayed = ROW_COUNT;
510
+
511
+ DELETE FROM memory_associations
512
+ WHERE strength < p_edge_prune_strength
513
+ AND (last_activated IS NULL OR
514
+ last_activated < now() - (p_edge_prune_days || ' days')::interval);
515
+ GET DIAGNOSTICS v_edges_pruned = ROW_COUNT;
516
+
517
+ RETURN QUERY SELECT v_semantic_decayed, v_procedural_decayed, v_edges_pruned;
518
+ END;
519
+ $$;
520
+
521
+ -- Dream cycle: SQL-side entity co-occurrence
522
+ CREATE OR REPLACE FUNCTION engram_dream_cycle(
523
+ p_days_lookback int DEFAULT 30,
524
+ p_max_new_associations int DEFAULT 50
525
+ )
526
+ RETURNS TABLE (
527
+ source_id uuid,
528
+ source_type text,
529
+ target_id uuid,
530
+ target_type text,
531
+ shared_entity text,
532
+ entity_count int
533
+ )
534
+ LANGUAGE sql
535
+ SECURITY DEFINER
536
+ SET search_path = public
537
+ AS $$
538
+ WITH entity_memories AS (
539
+ SELECT e.id AS memory_id, 'episode'::text AS memory_type,
540
+ LOWER(unnest(e.entities)) AS entity
541
+ FROM memory_episodes e
542
+ WHERE e.created_at >= now() - (p_days_lookback || ' days')::interval
543
+ AND array_length(e.entities, 1) > 0
544
+
545
+ UNION ALL
546
+
547
+ SELECT s.id, 'semantic'::text, LOWER(s.topic)
548
+ FROM memory_semantic s
549
+ WHERE s.created_at >= now() - (p_days_lookback || ' days')::interval
550
+ ),
551
+ pairs AS (
552
+ SELECT
553
+ em1.memory_id AS source_id,
554
+ em1.memory_type AS source_type,
555
+ em2.memory_id AS target_id,
556
+ em2.memory_type AS target_type,
557
+ em1.entity AS shared_entity,
558
+ COUNT(*) AS entity_count
559
+ FROM entity_memories em1
560
+ JOIN entity_memories em2
561
+ ON em1.entity = em2.entity
562
+ AND em1.memory_id < em2.memory_id
563
+ WHERE NOT EXISTS (
564
+ SELECT 1 FROM memory_associations a
565
+ WHERE (a.source_id = em1.memory_id AND a.target_id = em2.memory_id)
566
+ OR (a.source_id = em2.memory_id AND a.target_id = em1.memory_id)
567
+ )
568
+ GROUP BY em1.memory_id, em1.memory_type, em2.memory_id, em2.memory_type, em1.entity
569
+ )
570
+ SELECT source_id, source_type, target_id, target_type, shared_entity, entity_count::int
571
+ FROM pairs
572
+ ORDER BY entity_count DESC, source_id, target_id
573
+ LIMIT p_max_new_associations;
574
+ $$;
575
+
576
+ INSERT INTO schema_migrations (version, checksum)
577
+ VALUES ('006_rpc_functions', md5('006'))
578
+ ON CONFLICT DO NOTHING;
579
+ `;
580
+ /**
581
+ * Migration 007: Row Level Security policies and GRANT statements.
582
+ */
583
+ export const MIGRATION_007 = /* sql */ `
584
+ -- Migration 007: RLS policies
585
+
586
+ ALTER TABLE memories ENABLE ROW LEVEL SECURITY;
587
+ ALTER TABLE memory_episodes ENABLE ROW LEVEL SECURITY;
588
+ ALTER TABLE memory_digests ENABLE ROW LEVEL SECURITY;
589
+ ALTER TABLE memory_semantic ENABLE ROW LEVEL SECURITY;
590
+ ALTER TABLE memory_procedural ENABLE ROW LEVEL SECURITY;
591
+ ALTER TABLE memory_associations ENABLE ROW LEVEL SECURITY;
592
+ ALTER TABLE consolidation_runs ENABLE ROW LEVEL SECURITY;
593
+ ALTER TABLE sensory_snapshots ENABLE ROW LEVEL SECURITY;
594
+
595
+ -- Option A: Supabase Auth (user-specific memory isolation)
596
+ CREATE POLICY episodes_auth_policy ON memory_episodes
597
+ FOR ALL USING (
598
+ session_id LIKE ('agent:' || auth.uid()::text || ':%')
599
+ OR auth.role() = 'service_role'
600
+ );
601
+
602
+ CREATE POLICY digests_auth_policy ON memory_digests
603
+ FOR ALL USING (
604
+ session_id LIKE ('agent:' || auth.uid()::text || ':%')
605
+ OR auth.role() = 'service_role'
606
+ );
607
+
608
+ -- Semantic, procedural, associations: cross-session, service role only
609
+ CREATE POLICY semantic_auth_policy ON memory_semantic
610
+ FOR ALL USING (auth.role() = 'service_role');
611
+
612
+ CREATE POLICY procedural_auth_policy ON memory_procedural
613
+ FOR ALL USING (auth.role() = 'service_role');
614
+
615
+ CREATE POLICY associations_auth_policy ON memory_associations
616
+ FOR ALL USING (auth.role() = 'service_role');
617
+
618
+ CREATE POLICY consolidation_runs_policy ON consolidation_runs
619
+ FOR ALL USING (auth.role() = 'service_role');
620
+
621
+ CREATE POLICY sensory_snapshots_policy ON sensory_snapshots
622
+ FOR ALL USING (
623
+ session_id LIKE ('agent:' || auth.uid()::text || ':%')
624
+ OR auth.role() = 'service_role'
625
+ );
626
+
627
+ CREATE POLICY memories_policy ON memories
628
+ FOR ALL USING (auth.role() = 'service_role');
629
+
630
+ -- Grant execute on RPC functions
631
+ GRANT EXECUTE ON FUNCTION engram_recall TO authenticated, service_role;
632
+ GRANT EXECUTE ON FUNCTION engram_association_walk TO authenticated, service_role;
633
+ GRANT EXECUTE ON FUNCTION engram_record_access TO authenticated, service_role;
634
+ GRANT EXECUTE ON FUNCTION engram_upsert_co_recalled TO authenticated, service_role;
635
+ GRANT EXECUTE ON FUNCTION engram_decay_pass TO service_role;
636
+ GRANT EXECUTE ON FUNCTION engram_dream_cycle TO service_role;
637
+ GRANT EXECUTE ON FUNCTION update_updated_at_column TO service_role;
638
+
639
+ INSERT INTO schema_migrations (version, checksum)
640
+ VALUES ('007_rls_policies', md5('007'))
641
+ ON CONFLICT DO NOTHING;
642
+ `;
643
+ /**
644
+ * Returns all migration SQL concatenated in order.
645
+ * Apply this to a fresh Supabase project or run each migration individually.
646
+ */
647
+ export function getMigrationSQL() {
648
+ return [MIGRATION_004, MIGRATION_005, MIGRATION_006, MIGRATION_007].join('\n\n');
649
+ }
650
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,+BAA+B;AAC/B,6EAA6E;AAC7E,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CtC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoOtC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2RtC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DtC,CAAA;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AAClF,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { ProceduralMemory, SearchOptions, SearchResult } from '@engram-mem/core';
3
+ import type { ProceduralStorage } from '@engram-mem/core';
4
+ export declare class SupabaseProceduralStorage implements ProceduralStorage {
5
+ private readonly client;
6
+ constructor(client: SupabaseClient);
7
+ insert(memory: Omit<ProceduralMemory, 'id' | 'createdAt' | 'updatedAt' | 'accessCount' | 'lastAccessed'>): Promise<ProceduralMemory>;
8
+ search(query: string, opts?: SearchOptions): Promise<SearchResult<ProceduralMemory>[]>;
9
+ searchByTrigger(activity: string, opts?: SearchOptions): Promise<SearchResult<ProceduralMemory>[]>;
10
+ recordAccess(id: string): Promise<void>;
11
+ incrementObservation(id: string): Promise<void>;
12
+ batchDecay(opts: {
13
+ daysThreshold: number;
14
+ decayRate: number;
15
+ }): Promise<number>;
16
+ }
17
+ //# sourceMappingURL=procedural.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"procedural.d.ts","sourceRoot":"","sources":["../src/procedural.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAErF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGzD,qBAAa,yBAA0B,YAAW,iBAAiB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAE7C,MAAM,CACV,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC,GAChG,OAAO,CAAC,gBAAgB,CAAC;IAgCtB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAAC;IA4DtF,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,gBAAgB,CAAC,EAAE,CAAC;IAsBlG,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvC,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/C,UAAU,CAAC,IAAI,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAatF"}