@traqr/memory 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 +135 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth.d.ts +18 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +31 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/auto-derive.d.ts +35 -0
- package/dist/lib/auto-derive.js +261 -0
- package/dist/lib/auto-derive.js.map +1 -0
- package/dist/lib/borderline.d.ts +26 -0
- package/dist/lib/borderline.js +121 -0
- package/dist/lib/borderline.js.map +1 -0
- package/dist/lib/client.d.ts +28 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +60 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/context.d.ts +38 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +334 -0
- package/dist/lib/context.js.map +1 -0
- package/dist/lib/embeddings.d.ts +60 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/lib/embeddings.js +229 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/entity-pipeline.d.ts +23 -0
- package/dist/lib/entity-pipeline.js +151 -0
- package/dist/lib/entity-pipeline.js.map +1 -0
- package/dist/lib/formatting.d.ts +13 -0
- package/dist/lib/formatting.d.ts.map +1 -0
- package/dist/lib/formatting.js +60 -0
- package/dist/lib/formatting.js.map +1 -0
- package/dist/lib/learning-extractor.d.ts +144 -0
- package/dist/lib/learning-extractor.d.ts.map +1 -0
- package/dist/lib/learning-extractor.js +921 -0
- package/dist/lib/learning-extractor.js.map +1 -0
- package/dist/lib/lifecycle.d.ts +45 -0
- package/dist/lib/lifecycle.js +84 -0
- package/dist/lib/lifecycle.js.map +1 -0
- package/dist/lib/memory.d.ts +128 -0
- package/dist/lib/memory.d.ts.map +1 -0
- package/dist/lib/memory.js +590 -0
- package/dist/lib/memory.js.map +1 -0
- package/dist/lib/quality-gate.d.ts +32 -0
- package/dist/lib/quality-gate.js +158 -0
- package/dist/lib/quality-gate.js.map +1 -0
- package/dist/lib/quality-gate.test.d.ts +7 -0
- package/dist/lib/quality-gate.test.js +75 -0
- package/dist/lib/quality-gate.test.js.map +1 -0
- package/dist/lib/rerank.d.ts +22 -0
- package/dist/lib/rerank.js +61 -0
- package/dist/lib/rerank.js.map +1 -0
- package/dist/lib/retrieval.d.ts +75 -0
- package/dist/lib/retrieval.js +380 -0
- package/dist/lib/retrieval.js.map +1 -0
- package/dist/migrate.d.ts +17 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +81 -0
- package/dist/migrate.js.map +1 -0
- package/dist/routes/analyze-codebase.d.ts +9 -0
- package/dist/routes/analyze-codebase.d.ts.map +1 -0
- package/dist/routes/analyze-codebase.js +70 -0
- package/dist/routes/analyze-codebase.js.map +1 -0
- package/dist/routes/analyze-voice.d.ts +9 -0
- package/dist/routes/analyze-voice.d.ts.map +1 -0
- package/dist/routes/analyze-voice.js +63 -0
- package/dist/routes/analyze-voice.js.map +1 -0
- package/dist/routes/assemble-context.d.ts +9 -0
- package/dist/routes/assemble-context.d.ts.map +1 -0
- package/dist/routes/assemble-context.js +68 -0
- package/dist/routes/assemble-context.js.map +1 -0
- package/dist/routes/bootstrap.d.ts +12 -0
- package/dist/routes/bootstrap.d.ts.map +1 -0
- package/dist/routes/bootstrap.js +102 -0
- package/dist/routes/bootstrap.js.map +1 -0
- package/dist/routes/browse.d.ts +11 -0
- package/dist/routes/browse.js +85 -0
- package/dist/routes/browse.js.map +1 -0
- package/dist/routes/capture-thought.d.ts +13 -0
- package/dist/routes/capture-thought.d.ts.map +1 -0
- package/dist/routes/capture-thought.js +178 -0
- package/dist/routes/capture-thought.js.map +1 -0
- package/dist/routes/capture.d.ts +13 -0
- package/dist/routes/capture.d.ts.map +1 -0
- package/dist/routes/capture.js +86 -0
- package/dist/routes/capture.js.map +1 -0
- package/dist/routes/cite.d.ts +9 -0
- package/dist/routes/cite.d.ts.map +1 -0
- package/dist/routes/cite.js +49 -0
- package/dist/routes/cite.js.map +1 -0
- package/dist/routes/crud.d.ts +11 -0
- package/dist/routes/crud.d.ts.map +1 -0
- package/dist/routes/crud.js +176 -0
- package/dist/routes/crud.js.map +1 -0
- package/dist/routes/dashboard.d.ts +9 -0
- package/dist/routes/dashboard.d.ts.map +1 -0
- package/dist/routes/dashboard.js +85 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/entity-cron.d.ts +8 -0
- package/dist/routes/entity-cron.js +31 -0
- package/dist/routes/entity-cron.js.map +1 -0
- package/dist/routes/export.d.ts +8 -0
- package/dist/routes/export.d.ts.map +1 -0
- package/dist/routes/export.js +69 -0
- package/dist/routes/export.js.map +1 -0
- package/dist/routes/extract-pr-learnings.d.ts +12 -0
- package/dist/routes/extract-pr-learnings.d.ts.map +1 -0
- package/dist/routes/extract-pr-learnings.js +127 -0
- package/dist/routes/extract-pr-learnings.js.map +1 -0
- package/dist/routes/forget-cron.d.ts +9 -0
- package/dist/routes/forget-cron.js +30 -0
- package/dist/routes/forget-cron.js.map +1 -0
- package/dist/routes/learnings.d.ts +9 -0
- package/dist/routes/learnings.d.ts.map +1 -0
- package/dist/routes/learnings.js +237 -0
- package/dist/routes/learnings.js.map +1 -0
- package/dist/routes/pulse.d.ts +9 -0
- package/dist/routes/pulse.d.ts.map +1 -0
- package/dist/routes/pulse.js +133 -0
- package/dist/routes/pulse.js.map +1 -0
- package/dist/routes/search.d.ts +8 -0
- package/dist/routes/search.d.ts.map +1 -0
- package/dist/routes/search.js +107 -0
- package/dist/routes/search.js.map +1 -0
- package/dist/routes/store.d.ts +8 -0
- package/dist/routes/store.d.ts.map +1 -0
- package/dist/routes/store.js +89 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/routes/sync.d.ts +12 -0
- package/dist/routes/sync.d.ts.map +1 -0
- package/dist/routes/sync.js +83 -0
- package/dist/routes/sync.js.map +1 -0
- package/dist/routes/voice-profile.d.ts +9 -0
- package/dist/routes/voice-profile.d.ts.map +1 -0
- package/dist/routes/voice-profile.js +124 -0
- package/dist/routes/voice-profile.js.map +1 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +99 -0
- package/dist/server.js.map +1 -0
- package/dist/vectordb/index.d.ts +17 -0
- package/dist/vectordb/index.d.ts.map +1 -0
- package/dist/vectordb/index.js +39 -0
- package/dist/vectordb/index.js.map +1 -0
- package/dist/vectordb/supabase.d.ts +62 -0
- package/dist/vectordb/supabase.d.ts.map +1 -0
- package/dist/vectordb/supabase.js +711 -0
- package/dist/vectordb/supabase.js.map +1 -0
- package/dist/vectordb/types.d.ts +217 -0
- package/dist/vectordb/types.d.ts.map +1 -0
- package/dist/vectordb/types.js +28 -0
- package/dist/vectordb/types.js.map +1 -0
- package/package.json +49 -0
- package/setup.sql +1037 -0
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);
|