@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.
- package/README.md +301 -0
- package/dist/adapter.d.ts +51 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +411 -0
- package/dist/adapter.js.map +1 -0
- package/dist/associations.d.ts +23 -0
- package/dist/associations.d.ts.map +1 -0
- package/dist/associations.js +109 -0
- package/dist/associations.js.map +1 -0
- package/dist/digests.d.ts +13 -0
- package/dist/digests.d.ts.map +1 -0
- package/dist/digests.js +154 -0
- package/dist/digests.js.map +1 -0
- package/dist/episodes.d.ts +25 -0
- package/dist/episodes.d.ts.map +1 -0
- package/dist/episodes.js +260 -0
- package/dist/episodes.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +27 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +650 -0
- package/dist/migrations.js.map +1 -0
- package/dist/procedural.d.ts +17 -0
- package/dist/procedural.d.ts.map +1 -0
- package/dist/procedural.js +195 -0
- package/dist/procedural.js.map +1 -0
- package/dist/search.d.ts +10 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +16 -0
- package/dist/search.js.map +1 -0
- package/dist/semantic.d.ts +17 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +184 -0
- package/dist/semantic.js.map +1 -0
- package/package.json +24 -0
|
@@ -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"}
|