@shadowforge0/aquifer-memory 1.5.12 → 1.7.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/.env.example +23 -0
- package/README.md +84 -73
- package/README_CN.md +676 -0
- package/README_TW.md +684 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +421 -53
- package/consumers/codex-handoff.js +258 -0
- package/consumers/codex.js +1676 -0
- package/consumers/default/daily-entries.js +23 -4
- package/consumers/default/index.js +2 -2
- package/consumers/default/prompts/summary.js +6 -6
- package/consumers/mcp.js +96 -5
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +1 -1
- package/consumers/shared/config.js +8 -0
- package/consumers/shared/factory.js +1 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +27 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +380 -18
- package/core/finalization-review.js +319 -0
- package/core/mcp-manifest.js +52 -2
- package/core/memory-bootstrap.js +200 -0
- package/core/memory-consolidation.js +1590 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +797 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +365 -0
- package/core/storage.js +385 -2
- package/docs/getting-started.md +105 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +92 -2
- package/package.json +25 -11
- package/pipeline/normalize/adapters/codex.js +106 -0
- package/pipeline/normalize/detect.js +3 -2
- package/schema/001-base.sql +3 -0
- package/schema/007-v1-foundation.sql +273 -0
- package/schema/008-session-finalizations.sql +50 -0
- package/schema/009-v1-assertion-plane.sql +193 -0
- package/schema/010-v1-finalization-review.sql +160 -0
- package/schema/011-v1-compaction-claim.sql +46 -0
- package/schema/012-v1-compaction-lease.sql +39 -0
- package/schema/013-v1-compaction-lineage.sql +193 -0
- package/scripts/codex-recovery.js +672 -0
- package/consumers/miranda/context-inject.js +0 -120
- package/consumers/miranda/daily-entries.js +0 -224
- package/consumers/miranda/index.js +0 -364
- package/consumers/miranda/instance.js +0 -55
- package/consumers/miranda/llm.js +0 -99
- package/consumers/miranda/profile.json +0 -145
- package/consumers/miranda/prompts/summary.js +0 -303
- package/consumers/miranda/recall-format.js +0 -76
- package/consumers/miranda/render-daily-md.js +0 -186
- package/consumers/miranda/workspace-files.js +0 -91
- package/scripts/drop-entity-state-history.sql +0 -17
- package/scripts/drop-insights.sql +0 -12
- package/scripts/install-openclaw.sh +0 -59
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { resolveApplicableRecords } = require('./memory-bootstrap');
|
|
5
|
+
|
|
6
|
+
function requireField(obj, field) {
|
|
7
|
+
if (!obj || obj[field] === undefined || obj[field] === null || obj[field] === '') {
|
|
8
|
+
throw new Error(`${field} is required`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toJson(value, fallback) {
|
|
13
|
+
return JSON.stringify(value === undefined ? fallback : value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toJsonOrNull(value) {
|
|
17
|
+
return value === undefined || value === null ? null : JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function advisoryLockKeys(namespace, value) {
|
|
21
|
+
const digest = crypto.createHash('sha256').update(`${namespace}:${value}`).digest();
|
|
22
|
+
return [digest.readInt32BE(0), digest.readInt32BE(4)];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BOOTSTRAP_ORDER_SQL = `
|
|
26
|
+
CASE m.memory_type
|
|
27
|
+
WHEN 'constraint' THEN 0
|
|
28
|
+
WHEN 'state' THEN 1
|
|
29
|
+
WHEN 'open_loop' THEN 2
|
|
30
|
+
WHEN 'decision' THEN 3
|
|
31
|
+
WHEN 'preference' THEN 4
|
|
32
|
+
WHEN 'fact' THEN 5
|
|
33
|
+
WHEN 'conclusion' THEN 6
|
|
34
|
+
WHEN 'entity_note' THEN 7
|
|
35
|
+
ELSE 99
|
|
36
|
+
END ASC,
|
|
37
|
+
CASE m.authority
|
|
38
|
+
WHEN 'user_explicit' THEN 0
|
|
39
|
+
WHEN 'executable_evidence' THEN 1
|
|
40
|
+
WHEN 'manual' THEN 2
|
|
41
|
+
WHEN 'system' THEN 3
|
|
42
|
+
WHEN 'verified_summary' THEN 4
|
|
43
|
+
WHEN 'llm_inference' THEN 5
|
|
44
|
+
WHEN 'raw_transcript' THEN 6
|
|
45
|
+
ELSE 99
|
|
46
|
+
END ASC,
|
|
47
|
+
m.accepted_at DESC NULLS LAST,
|
|
48
|
+
m.id ASC`;
|
|
49
|
+
|
|
50
|
+
const CURRENT_TYPE_PRIORITY = {
|
|
51
|
+
constraint: 0,
|
|
52
|
+
state: 1,
|
|
53
|
+
open_loop: 2,
|
|
54
|
+
decision: 3,
|
|
55
|
+
preference: 4,
|
|
56
|
+
fact: 5,
|
|
57
|
+
conclusion: 6,
|
|
58
|
+
entity_note: 7,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const CURRENT_AUTHORITY_PRIORITY = {
|
|
62
|
+
user_explicit: 0,
|
|
63
|
+
executable_evidence: 1,
|
|
64
|
+
manual: 2,
|
|
65
|
+
system: 3,
|
|
66
|
+
verified_summary: 4,
|
|
67
|
+
llm_inference: 5,
|
|
68
|
+
raw_transcript: 6,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function parseTime(value) {
|
|
72
|
+
const parsed = Date.parse(value || '');
|
|
73
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeScopePath(activeScopePath, activeScopeKey) {
|
|
77
|
+
const source = Array.isArray(activeScopePath)
|
|
78
|
+
? activeScopePath
|
|
79
|
+
: (typeof activeScopePath === 'string' ? activeScopePath.split(',') : null);
|
|
80
|
+
if (source && source.length > 0) {
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
const path = [];
|
|
83
|
+
for (const value of source) {
|
|
84
|
+
const key = String(value || '').trim();
|
|
85
|
+
if (!key || seen.has(key)) continue;
|
|
86
|
+
seen.add(key);
|
|
87
|
+
path.push(key);
|
|
88
|
+
}
|
|
89
|
+
if (path.length > 0) return path;
|
|
90
|
+
}
|
|
91
|
+
if (activeScopeKey) return [String(activeScopeKey).trim()].filter(Boolean);
|
|
92
|
+
return ['global'];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareRecordIdAsc(a, b) {
|
|
96
|
+
const left = a.memoryId ?? a.memory_id ?? a.id ?? null;
|
|
97
|
+
const right = b.memoryId ?? b.memory_id ?? b.id ?? null;
|
|
98
|
+
const leftNum = Number(left);
|
|
99
|
+
const rightNum = Number(right);
|
|
100
|
+
if (Number.isFinite(leftNum) && Number.isFinite(rightNum) && leftNum !== rightNum) {
|
|
101
|
+
return leftNum - rightNum;
|
|
102
|
+
}
|
|
103
|
+
return String(left ?? '').localeCompare(String(right ?? ''));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeCurrentMemoryRow(row = {}) {
|
|
107
|
+
const memoryId = row.memoryId ?? row.memory_id ?? row.id ?? null;
|
|
108
|
+
const evidenceRefsValue = row.evidenceRefs ?? row.evidence_refs ?? [];
|
|
109
|
+
const evidenceRefs = Array.isArray(evidenceRefsValue) ? evidenceRefsValue : [];
|
|
110
|
+
return {
|
|
111
|
+
...row,
|
|
112
|
+
memoryId: memoryId === null ? null : String(memoryId),
|
|
113
|
+
canonicalKey: row.canonicalKey ?? row.canonical_key ?? null,
|
|
114
|
+
memoryType: row.memoryType ?? row.memory_type ?? null,
|
|
115
|
+
scopeKey: row.scopeKey ?? row.scope_key ?? null,
|
|
116
|
+
scopeKind: row.scopeKind ?? row.scope_kind ?? null,
|
|
117
|
+
inheritanceMode: row.inheritanceMode ?? row.inheritance_mode ?? row.scope_inheritance_mode ?? null,
|
|
118
|
+
visibleInBootstrap: row.visibleInBootstrap ?? row.visible_in_bootstrap ?? false,
|
|
119
|
+
visibleInRecall: row.visibleInRecall ?? row.visible_in_recall ?? false,
|
|
120
|
+
acceptedAt: row.acceptedAt ?? row.accepted_at ?? null,
|
|
121
|
+
validFrom: row.validFrom ?? row.valid_from ?? null,
|
|
122
|
+
validTo: row.validTo ?? row.valid_to ?? null,
|
|
123
|
+
staleAfter: row.staleAfter ?? row.stale_after ?? null,
|
|
124
|
+
evidenceRefs,
|
|
125
|
+
evidence_refs: evidenceRefs,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function currentScopePriority(record, positions) {
|
|
130
|
+
return positions.get(record.scopeKey ?? record.scope_key) ?? -1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function sortCurrentMemoryRecords(a, b, positions) {
|
|
134
|
+
const leftScope = currentScopePriority(a, positions);
|
|
135
|
+
const rightScope = currentScopePriority(b, positions);
|
|
136
|
+
if (rightScope !== leftScope) return rightScope - leftScope;
|
|
137
|
+
|
|
138
|
+
const leftType = CURRENT_TYPE_PRIORITY[a.memoryType ?? a.memory_type] ?? 99;
|
|
139
|
+
const rightType = CURRENT_TYPE_PRIORITY[b.memoryType ?? b.memory_type] ?? 99;
|
|
140
|
+
if (leftType !== rightType) return leftType - rightType;
|
|
141
|
+
|
|
142
|
+
const leftAuthority = CURRENT_AUTHORITY_PRIORITY[a.authority] ?? 99;
|
|
143
|
+
const rightAuthority = CURRENT_AUTHORITY_PRIORITY[b.authority] ?? 99;
|
|
144
|
+
if (leftAuthority !== rightAuthority) return leftAuthority - rightAuthority;
|
|
145
|
+
|
|
146
|
+
const leftAccepted = parseTime(a.acceptedAt ?? a.accepted_at);
|
|
147
|
+
const rightAccepted = parseTime(b.acceptedAt ?? b.accepted_at);
|
|
148
|
+
if (leftAccepted !== rightAccepted) return (rightAccepted ?? 0) - (leftAccepted ?? 0);
|
|
149
|
+
|
|
150
|
+
return compareRecordIdAsc(a, b);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function isCurrentProjectionRow(row, asOf) {
|
|
154
|
+
if ((row.status || 'candidate') !== 'active') return false;
|
|
155
|
+
if (row.visibleInBootstrap !== true && row.visibleInRecall !== true) return false;
|
|
156
|
+
const at = parseTime(asOf);
|
|
157
|
+
if (at === null) return true;
|
|
158
|
+
const validFrom = parseTime(row.validFrom ?? row.valid_from);
|
|
159
|
+
const validTo = parseTime(row.validTo ?? row.valid_to);
|
|
160
|
+
const staleAfter = parseTime(row.staleAfter ?? row.stale_after);
|
|
161
|
+
if (validFrom !== null && validFrom > at) return false;
|
|
162
|
+
if (validTo !== null && validTo <= at) return false;
|
|
163
|
+
if (staleAfter !== null && staleAfter <= at) return false;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = false }) {
|
|
168
|
+
const scopes = `${schema}.scopes`;
|
|
169
|
+
const versions = `${schema}.versions`;
|
|
170
|
+
const memories = `${schema}.memory_records`;
|
|
171
|
+
const factAssertions = `${schema}.fact_assertions_v1`;
|
|
172
|
+
const evidenceRefs = `${schema}.evidence_refs`;
|
|
173
|
+
const feedback = `${schema}.feedback`;
|
|
174
|
+
const canTransact = typeof pool.connect === 'function';
|
|
175
|
+
|
|
176
|
+
async function upsertScope(input = {}) {
|
|
177
|
+
requireField(input, 'scopeKind');
|
|
178
|
+
requireField(input, 'scopeKey');
|
|
179
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
180
|
+
const result = await pool.query(
|
|
181
|
+
`INSERT INTO ${scopes} (
|
|
182
|
+
tenant_id, scope_kind, scope_key, parent_scope_id, inheritance_mode,
|
|
183
|
+
context_key, topic_key, metadata, active_from, active_to
|
|
184
|
+
)
|
|
185
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,'defaultable'),$6,$7,COALESCE($8::jsonb,'{}'::jsonb),$9,$10)
|
|
186
|
+
ON CONFLICT (tenant_id, scope_kind, scope_key) DO UPDATE SET
|
|
187
|
+
parent_scope_id = COALESCE(EXCLUDED.parent_scope_id, ${scopes}.parent_scope_id),
|
|
188
|
+
inheritance_mode = EXCLUDED.inheritance_mode,
|
|
189
|
+
context_key = COALESCE(EXCLUDED.context_key, ${scopes}.context_key),
|
|
190
|
+
topic_key = COALESCE(EXCLUDED.topic_key, ${scopes}.topic_key),
|
|
191
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${scopes}.metadata),
|
|
192
|
+
active_from = COALESCE(EXCLUDED.active_from, ${scopes}.active_from),
|
|
193
|
+
active_to = COALESCE(EXCLUDED.active_to, ${scopes}.active_to)
|
|
194
|
+
RETURNING *`,
|
|
195
|
+
[
|
|
196
|
+
tenantId,
|
|
197
|
+
input.scopeKind,
|
|
198
|
+
input.scopeKey,
|
|
199
|
+
input.parentScopeId || null,
|
|
200
|
+
input.inheritanceMode || 'defaultable',
|
|
201
|
+
input.contextKey || null,
|
|
202
|
+
input.topicKey || null,
|
|
203
|
+
toJson(input.metadata, {}),
|
|
204
|
+
input.activeFrom || null,
|
|
205
|
+
input.activeTo || null,
|
|
206
|
+
]
|
|
207
|
+
);
|
|
208
|
+
return result.rows[0] || null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function createVersion(input = {}) {
|
|
212
|
+
requireField(input, 'versionKind');
|
|
213
|
+
requireField(input, 'version');
|
|
214
|
+
requireField(input, 'versionHash');
|
|
215
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
216
|
+
const result = await pool.query(
|
|
217
|
+
`INSERT INTO ${versions} (
|
|
218
|
+
tenant_id, version_kind, version, version_hash, active, metadata,
|
|
219
|
+
released_at, retired_at
|
|
220
|
+
)
|
|
221
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,false),COALESCE($6::jsonb,'{}'::jsonb),COALESCE($7,now()),$8)
|
|
222
|
+
ON CONFLICT (tenant_id, version_kind, version_hash) DO UPDATE SET
|
|
223
|
+
version = EXCLUDED.version,
|
|
224
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${versions}.metadata),
|
|
225
|
+
retired_at = COALESCE(EXCLUDED.retired_at, ${versions}.retired_at)
|
|
226
|
+
RETURNING *`,
|
|
227
|
+
[
|
|
228
|
+
tenantId,
|
|
229
|
+
input.versionKind,
|
|
230
|
+
input.version,
|
|
231
|
+
input.versionHash,
|
|
232
|
+
input.active === true,
|
|
233
|
+
toJson(input.metadata, {}),
|
|
234
|
+
input.releasedAt || null,
|
|
235
|
+
input.retiredAt || null,
|
|
236
|
+
]
|
|
237
|
+
);
|
|
238
|
+
return result.rows[0] || null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function upsertMemory(input = {}) {
|
|
242
|
+
requireField(input, 'memoryType');
|
|
243
|
+
requireField(input, 'canonicalKey');
|
|
244
|
+
requireField(input, 'scopeId');
|
|
245
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
246
|
+
const status = input.status || 'candidate';
|
|
247
|
+
const result = await pool.query(
|
|
248
|
+
`INSERT INTO ${memories} (
|
|
249
|
+
tenant_id, memory_type, canonical_key, scope_id, context_key, topic_key,
|
|
250
|
+
title, summary, payload, status, authority, accepted_at, valid_from,
|
|
251
|
+
valid_to, stale_after, superseded_by, backing_fact_id, observed_at,
|
|
252
|
+
revoked_at, superseded_at, version_id, visible_in_bootstrap,
|
|
253
|
+
visible_in_recall, rank_features, created_by_finalization_id,
|
|
254
|
+
created_by_compaction_run_id
|
|
255
|
+
)
|
|
256
|
+
VALUES (
|
|
257
|
+
$1,$2,$3,$4,$5,$6,$7,COALESCE($8,''),COALESCE($9::jsonb,'{}'::jsonb),
|
|
258
|
+
COALESCE($10,'candidate'),COALESCE($11,'llm_inference'),$12,$13,$14,$15,
|
|
259
|
+
$16,$17,$18,$19,$20,$21,COALESCE($22,false),COALESCE($23,false),COALESCE($24::jsonb,'{}'::jsonb),$25,$26
|
|
260
|
+
)
|
|
261
|
+
ON CONFLICT (tenant_id, canonical_key) WHERE status = 'active' DO UPDATE SET
|
|
262
|
+
scope_id = EXCLUDED.scope_id,
|
|
263
|
+
context_key = COALESCE(EXCLUDED.context_key, ${memories}.context_key),
|
|
264
|
+
topic_key = COALESCE(EXCLUDED.topic_key, ${memories}.topic_key),
|
|
265
|
+
title = COALESCE(EXCLUDED.title, ${memories}.title),
|
|
266
|
+
summary = COALESCE(NULLIF(EXCLUDED.summary, ''), ${memories}.summary),
|
|
267
|
+
payload = COALESCE(NULLIF(EXCLUDED.payload, '{}'::jsonb), ${memories}.payload),
|
|
268
|
+
authority = EXCLUDED.authority,
|
|
269
|
+
accepted_at = COALESCE(EXCLUDED.accepted_at, ${memories}.accepted_at),
|
|
270
|
+
valid_from = COALESCE(EXCLUDED.valid_from, ${memories}.valid_from),
|
|
271
|
+
valid_to = COALESCE(EXCLUDED.valid_to, ${memories}.valid_to),
|
|
272
|
+
stale_after = COALESCE(EXCLUDED.stale_after, ${memories}.stale_after),
|
|
273
|
+
version_id = COALESCE(EXCLUDED.version_id, ${memories}.version_id),
|
|
274
|
+
backing_fact_id = COALESCE(EXCLUDED.backing_fact_id, ${memories}.backing_fact_id),
|
|
275
|
+
observed_at = COALESCE(EXCLUDED.observed_at, ${memories}.observed_at),
|
|
276
|
+
revoked_at = COALESCE(EXCLUDED.revoked_at, ${memories}.revoked_at),
|
|
277
|
+
superseded_at = COALESCE(EXCLUDED.superseded_at, ${memories}.superseded_at),
|
|
278
|
+
visible_in_bootstrap = EXCLUDED.visible_in_bootstrap,
|
|
279
|
+
visible_in_recall = EXCLUDED.visible_in_recall,
|
|
280
|
+
rank_features = COALESCE(NULLIF(EXCLUDED.rank_features, '{}'::jsonb), ${memories}.rank_features),
|
|
281
|
+
created_by_finalization_id = COALESCE(${memories}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
282
|
+
created_by_compaction_run_id = COALESCE(${memories}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
|
|
283
|
+
RETURNING *`,
|
|
284
|
+
[
|
|
285
|
+
tenantId,
|
|
286
|
+
input.memoryType,
|
|
287
|
+
input.canonicalKey,
|
|
288
|
+
input.scopeId,
|
|
289
|
+
input.contextKey || null,
|
|
290
|
+
input.topicKey || null,
|
|
291
|
+
input.title || null,
|
|
292
|
+
input.summary || '',
|
|
293
|
+
toJson(input.payload, {}),
|
|
294
|
+
status,
|
|
295
|
+
input.authority || 'llm_inference',
|
|
296
|
+
input.acceptedAt || (status === 'active' ? new Date().toISOString() : null),
|
|
297
|
+
input.validFrom || null,
|
|
298
|
+
input.validTo || null,
|
|
299
|
+
input.staleAfter || null,
|
|
300
|
+
input.supersededBy || null,
|
|
301
|
+
input.backingFactId || input.backing_fact_id || null,
|
|
302
|
+
input.observedAt || input.observed_at || null,
|
|
303
|
+
input.revokedAt || input.revoked_at || null,
|
|
304
|
+
input.supersededAt || input.superseded_at || null,
|
|
305
|
+
input.versionId || null,
|
|
306
|
+
input.visibleInBootstrap === true,
|
|
307
|
+
input.visibleInRecall === true,
|
|
308
|
+
toJson(input.rankFeatures, {}),
|
|
309
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
310
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
311
|
+
]
|
|
312
|
+
);
|
|
313
|
+
return result.rows[0] || null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function upsertFactAssertion(input = {}) {
|
|
317
|
+
requireField(input, 'canonicalKey');
|
|
318
|
+
requireField(input, 'scopeId');
|
|
319
|
+
requireField(input, 'predicate');
|
|
320
|
+
requireField(input, 'objectKind');
|
|
321
|
+
requireField(input, 'assertionHash');
|
|
322
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
323
|
+
const status = input.status || 'active';
|
|
324
|
+
const result = await pool.query(
|
|
325
|
+
`INSERT INTO ${factAssertions} (
|
|
326
|
+
tenant_id, canonical_key, scope_id, subject_entity_id, predicate,
|
|
327
|
+
object_kind, object_entity_id, object_value_json, qualifiers_json,
|
|
328
|
+
valid_from, valid_to, observed_at, stale_after, accepted_at,
|
|
329
|
+
revoked_at, superseded_at, status, authority, assertion_hash,
|
|
330
|
+
superseded_by, version_id, metadata, created_by_finalization_id,
|
|
331
|
+
created_by_compaction_run_id
|
|
332
|
+
)
|
|
333
|
+
VALUES (
|
|
334
|
+
$1,$2,$3,$4,$5,$6,$7,$8::jsonb,COALESCE($9::jsonb,'{}'::jsonb),
|
|
335
|
+
$10,$11,$12,$13,$14,$15,$16,COALESCE($17,'active'),COALESCE($18,'verified_summary'),
|
|
336
|
+
$19,$20,$21,COALESCE($22::jsonb,'{}'::jsonb),$23,$24
|
|
337
|
+
)
|
|
338
|
+
ON CONFLICT (tenant_id, canonical_key) WHERE status = 'active' DO UPDATE SET
|
|
339
|
+
scope_id = EXCLUDED.scope_id,
|
|
340
|
+
subject_entity_id = COALESCE(EXCLUDED.subject_entity_id, ${factAssertions}.subject_entity_id),
|
|
341
|
+
predicate = EXCLUDED.predicate,
|
|
342
|
+
object_kind = EXCLUDED.object_kind,
|
|
343
|
+
object_entity_id = COALESCE(EXCLUDED.object_entity_id, ${factAssertions}.object_entity_id),
|
|
344
|
+
object_value_json = EXCLUDED.object_value_json,
|
|
345
|
+
qualifiers_json = COALESCE(NULLIF(EXCLUDED.qualifiers_json, '{}'::jsonb), ${factAssertions}.qualifiers_json),
|
|
346
|
+
valid_from = COALESCE(EXCLUDED.valid_from, ${factAssertions}.valid_from),
|
|
347
|
+
valid_to = COALESCE(EXCLUDED.valid_to, ${factAssertions}.valid_to),
|
|
348
|
+
observed_at = COALESCE(EXCLUDED.observed_at, ${factAssertions}.observed_at),
|
|
349
|
+
stale_after = COALESCE(EXCLUDED.stale_after, ${factAssertions}.stale_after),
|
|
350
|
+
accepted_at = COALESCE(EXCLUDED.accepted_at, ${factAssertions}.accepted_at),
|
|
351
|
+
revoked_at = COALESCE(EXCLUDED.revoked_at, ${factAssertions}.revoked_at),
|
|
352
|
+
superseded_at = COALESCE(EXCLUDED.superseded_at, ${factAssertions}.superseded_at),
|
|
353
|
+
authority = EXCLUDED.authority,
|
|
354
|
+
assertion_hash = EXCLUDED.assertion_hash,
|
|
355
|
+
version_id = COALESCE(EXCLUDED.version_id, ${factAssertions}.version_id),
|
|
356
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${factAssertions}.metadata),
|
|
357
|
+
created_by_finalization_id = COALESCE(${factAssertions}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
358
|
+
created_by_compaction_run_id = COALESCE(${factAssertions}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id),
|
|
359
|
+
updated_at = now()
|
|
360
|
+
RETURNING *`,
|
|
361
|
+
[
|
|
362
|
+
tenantId,
|
|
363
|
+
input.canonicalKey,
|
|
364
|
+
input.scopeId,
|
|
365
|
+
input.subjectEntityId || input.subject_entity_id || null,
|
|
366
|
+
input.predicate,
|
|
367
|
+
input.objectKind || input.object_kind,
|
|
368
|
+
input.objectEntityId || input.object_entity_id || null,
|
|
369
|
+
toJsonOrNull(input.objectValueJson ?? input.object_value_json),
|
|
370
|
+
toJson(input.qualifiersJson ?? input.qualifiers_json, {}),
|
|
371
|
+
input.validFrom || input.valid_from || null,
|
|
372
|
+
input.validTo || input.valid_to || null,
|
|
373
|
+
input.observedAt || input.observed_at || null,
|
|
374
|
+
input.staleAfter || input.stale_after || null,
|
|
375
|
+
input.acceptedAt || input.accepted_at || (status === 'active' ? new Date().toISOString() : null),
|
|
376
|
+
input.revokedAt || input.revoked_at || null,
|
|
377
|
+
input.supersededAt || input.superseded_at || null,
|
|
378
|
+
status,
|
|
379
|
+
input.authority || 'verified_summary',
|
|
380
|
+
input.assertionHash || input.assertion_hash,
|
|
381
|
+
input.supersededBy || input.superseded_by || null,
|
|
382
|
+
input.versionId || input.version_id || null,
|
|
383
|
+
toJson(input.metadata, {}),
|
|
384
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
385
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
386
|
+
]
|
|
387
|
+
);
|
|
388
|
+
return result.rows[0] || null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function linkEvidence(input = {}) {
|
|
392
|
+
requireField(input, 'ownerKind');
|
|
393
|
+
requireField(input, 'ownerId');
|
|
394
|
+
requireField(input, 'sourceKind');
|
|
395
|
+
requireField(input, 'sourceRef');
|
|
396
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
397
|
+
const result = await pool.query(
|
|
398
|
+
`INSERT INTO ${evidenceRefs} (
|
|
399
|
+
tenant_id, owner_kind, owner_id, source_kind, source_ref,
|
|
400
|
+
relation_kind, weight, metadata, created_by_finalization_id,
|
|
401
|
+
created_by_compaction_run_id
|
|
402
|
+
)
|
|
403
|
+
VALUES ($1,$2,$3,$4,$5,COALESCE($6,'supporting'),COALESCE($7,1.0),COALESCE($8::jsonb,'{}'::jsonb),$9,$10)
|
|
404
|
+
ON CONFLICT (tenant_id, owner_kind, owner_id, source_kind, source_ref, relation_kind)
|
|
405
|
+
DO UPDATE SET weight = EXCLUDED.weight,
|
|
406
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${evidenceRefs}.metadata),
|
|
407
|
+
created_by_finalization_id = COALESCE(${evidenceRefs}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
408
|
+
created_by_compaction_run_id = COALESCE(${evidenceRefs}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
|
|
409
|
+
RETURNING *`,
|
|
410
|
+
[
|
|
411
|
+
tenantId,
|
|
412
|
+
input.ownerKind,
|
|
413
|
+
input.ownerId,
|
|
414
|
+
input.sourceKind,
|
|
415
|
+
input.sourceRef,
|
|
416
|
+
input.relationKind || 'supporting',
|
|
417
|
+
input.weight ?? 1.0,
|
|
418
|
+
toJson(input.metadata, {}),
|
|
419
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
420
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
421
|
+
]
|
|
422
|
+
);
|
|
423
|
+
return result.rows[0] || null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function recordFeedback(input = {}) {
|
|
427
|
+
requireField(input, 'targetKind');
|
|
428
|
+
requireField(input, 'targetId');
|
|
429
|
+
requireField(input, 'feedbackType');
|
|
430
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
431
|
+
const result = await pool.query(
|
|
432
|
+
`INSERT INTO ${feedback} (
|
|
433
|
+
tenant_id, target_kind, target_id, feedback_type, actor_kind, actor_id,
|
|
434
|
+
query_fingerprint, note, metadata
|
|
435
|
+
)
|
|
436
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,'user'),$6,$7,$8,COALESCE($9::jsonb,'{}'::jsonb))
|
|
437
|
+
RETURNING *`,
|
|
438
|
+
[
|
|
439
|
+
tenantId,
|
|
440
|
+
input.targetKind,
|
|
441
|
+
String(input.targetId),
|
|
442
|
+
input.feedbackType,
|
|
443
|
+
input.actorKind || 'user',
|
|
444
|
+
input.actorId || null,
|
|
445
|
+
input.queryFingerprint || null,
|
|
446
|
+
input.note || null,
|
|
447
|
+
toJson(input.metadata, {}),
|
|
448
|
+
]
|
|
449
|
+
);
|
|
450
|
+
return result.rows[0] || null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function findActiveByCanonicalKey(input = {}) {
|
|
454
|
+
requireField(input, 'canonicalKey');
|
|
455
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
456
|
+
const lockClause = input.forUpdate === true ? 'FOR UPDATE OF m' : '';
|
|
457
|
+
const result = await pool.query(
|
|
458
|
+
`SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
459
|
+
FROM ${memories} m
|
|
460
|
+
JOIN ${scopes} s ON s.id = m.scope_id
|
|
461
|
+
WHERE m.tenant_id = $1
|
|
462
|
+
AND m.canonical_key = $2
|
|
463
|
+
AND m.status = 'active'
|
|
464
|
+
ORDER BY m.accepted_at DESC NULLS LAST, m.id ASC
|
|
465
|
+
${lockClause}`,
|
|
466
|
+
[tenantId, input.canonicalKey]
|
|
467
|
+
);
|
|
468
|
+
return result.rows;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function findActiveFactByCanonicalKey(input = {}) {
|
|
472
|
+
requireField(input, 'canonicalKey');
|
|
473
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
474
|
+
const lockClause = input.forUpdate === true ? 'FOR UPDATE OF f' : '';
|
|
475
|
+
const result = await pool.query(
|
|
476
|
+
`SELECT f.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
477
|
+
FROM ${factAssertions} f
|
|
478
|
+
JOIN ${scopes} s ON s.id = f.scope_id
|
|
479
|
+
WHERE f.tenant_id = $1
|
|
480
|
+
AND f.canonical_key = $2
|
|
481
|
+
AND f.status = 'active'
|
|
482
|
+
ORDER BY f.accepted_at DESC NULLS LAST, f.id ASC
|
|
483
|
+
${lockClause}`,
|
|
484
|
+
[tenantId, input.canonicalKey]
|
|
485
|
+
);
|
|
486
|
+
return result.rows;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function lockCanonicalKey(input = {}) {
|
|
490
|
+
requireField(input, 'canonicalKey');
|
|
491
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
492
|
+
const [key1, key2] = advisoryLockKeys(
|
|
493
|
+
'aquifer.memory_records.active_canonical',
|
|
494
|
+
`${tenantId}:${input.canonicalKey}`,
|
|
495
|
+
);
|
|
496
|
+
await pool.query('SELECT pg_advisory_xact_lock($1, $2)', [key1, key2]);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function updateMemoryStatus(input = {}) {
|
|
500
|
+
requireField(input, 'memoryId');
|
|
501
|
+
requireField(input, 'status');
|
|
502
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
503
|
+
const visibleBootstrap = input.status === 'active' ? input.visibleInBootstrap === true : false;
|
|
504
|
+
const visibleRecall = input.status === 'active' ? input.visibleInRecall === true : false;
|
|
505
|
+
const result = await pool.query(
|
|
506
|
+
`UPDATE ${memories}
|
|
507
|
+
SET status = $3,
|
|
508
|
+
superseded_by = COALESCE($4, superseded_by),
|
|
509
|
+
valid_to = COALESCE($5, valid_to),
|
|
510
|
+
superseded_at = CASE
|
|
511
|
+
WHEN $3 = 'superseded' THEN COALESCE($8, superseded_at, now())
|
|
512
|
+
ELSE superseded_at
|
|
513
|
+
END,
|
|
514
|
+
revoked_at = CASE
|
|
515
|
+
WHEN $3 = 'revoked' THEN COALESCE($9, revoked_at, now())
|
|
516
|
+
ELSE revoked_at
|
|
517
|
+
END,
|
|
518
|
+
visible_in_bootstrap = $6,
|
|
519
|
+
visible_in_recall = $7,
|
|
520
|
+
updated_at = now()
|
|
521
|
+
WHERE tenant_id = $1 AND id = $2
|
|
522
|
+
RETURNING *`,
|
|
523
|
+
[
|
|
524
|
+
tenantId,
|
|
525
|
+
input.memoryId,
|
|
526
|
+
input.status,
|
|
527
|
+
input.supersededBy || null,
|
|
528
|
+
input.validTo || null,
|
|
529
|
+
visibleBootstrap,
|
|
530
|
+
visibleRecall,
|
|
531
|
+
input.supersededAt || input.superseded_at || null,
|
|
532
|
+
input.revokedAt || input.revoked_at || null,
|
|
533
|
+
]
|
|
534
|
+
);
|
|
535
|
+
return result.rows[0] || null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function updateMemoryStatusIfCurrent(input = {}) {
|
|
539
|
+
requireField(input, 'memoryId');
|
|
540
|
+
requireField(input, 'fromStatus');
|
|
541
|
+
requireField(input, 'status');
|
|
542
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
543
|
+
const visibleBootstrap = input.status === 'active' ? input.visibleInBootstrap === true : false;
|
|
544
|
+
const visibleRecall = input.status === 'active' ? input.visibleInRecall === true : false;
|
|
545
|
+
const result = await pool.query(
|
|
546
|
+
`UPDATE ${memories}
|
|
547
|
+
SET status = $4,
|
|
548
|
+
superseded_by = COALESCE($5, superseded_by),
|
|
549
|
+
valid_to = COALESCE($6, valid_to),
|
|
550
|
+
superseded_at = CASE
|
|
551
|
+
WHEN $4 = 'superseded' THEN COALESCE($9, superseded_at, now())
|
|
552
|
+
ELSE superseded_at
|
|
553
|
+
END,
|
|
554
|
+
revoked_at = CASE
|
|
555
|
+
WHEN $4 = 'revoked' THEN COALESCE($10, revoked_at, now())
|
|
556
|
+
ELSE revoked_at
|
|
557
|
+
END,
|
|
558
|
+
visible_in_bootstrap = $7,
|
|
559
|
+
visible_in_recall = $8,
|
|
560
|
+
updated_at = now()
|
|
561
|
+
WHERE tenant_id = $1 AND id = $2 AND status = $3
|
|
562
|
+
RETURNING *`,
|
|
563
|
+
[
|
|
564
|
+
tenantId,
|
|
565
|
+
input.memoryId,
|
|
566
|
+
input.fromStatus,
|
|
567
|
+
input.status,
|
|
568
|
+
input.supersededBy || null,
|
|
569
|
+
input.validTo || null,
|
|
570
|
+
visibleBootstrap,
|
|
571
|
+
visibleRecall,
|
|
572
|
+
input.supersededAt || input.superseded_at || null,
|
|
573
|
+
input.revokedAt || input.revoked_at || null,
|
|
574
|
+
]
|
|
575
|
+
);
|
|
576
|
+
return result.rows[0] || null;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async function updateFactAssertionStatus(input = {}) {
|
|
580
|
+
requireField(input, 'factId');
|
|
581
|
+
requireField(input, 'status');
|
|
582
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
583
|
+
const result = await pool.query(
|
|
584
|
+
`UPDATE ${factAssertions}
|
|
585
|
+
SET status = $3,
|
|
586
|
+
superseded_by = COALESCE($4, superseded_by),
|
|
587
|
+
valid_to = COALESCE($5, valid_to),
|
|
588
|
+
superseded_at = CASE
|
|
589
|
+
WHEN $3 = 'superseded' THEN COALESCE($6, superseded_at, now())
|
|
590
|
+
ELSE superseded_at
|
|
591
|
+
END,
|
|
592
|
+
revoked_at = CASE
|
|
593
|
+
WHEN $3 = 'revoked' THEN COALESCE($7, revoked_at, now())
|
|
594
|
+
ELSE revoked_at
|
|
595
|
+
END,
|
|
596
|
+
updated_at = now()
|
|
597
|
+
WHERE tenant_id = $1 AND id = $2
|
|
598
|
+
RETURNING *`,
|
|
599
|
+
[
|
|
600
|
+
tenantId,
|
|
601
|
+
input.factId,
|
|
602
|
+
input.status,
|
|
603
|
+
input.supersededBy || input.superseded_by || null,
|
|
604
|
+
input.validTo || input.valid_to || null,
|
|
605
|
+
input.supersededAt || input.superseded_at || null,
|
|
606
|
+
input.revokedAt || input.revoked_at || null,
|
|
607
|
+
]
|
|
608
|
+
);
|
|
609
|
+
return result.rows[0] || null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function listActive(input = {}) {
|
|
613
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
614
|
+
const params = [tenantId];
|
|
615
|
+
const where = [`m.tenant_id = $1`, `m.status = 'active'`];
|
|
616
|
+
if (input.asOf) {
|
|
617
|
+
params.push(input.asOf);
|
|
618
|
+
const at = `$${params.length}::timestamptz`;
|
|
619
|
+
where.push(`(m.valid_from IS NULL OR m.valid_from <= ${at})`);
|
|
620
|
+
where.push(`(m.valid_to IS NULL OR m.valid_to > ${at})`);
|
|
621
|
+
where.push(`(m.stale_after IS NULL OR m.stale_after > ${at})`);
|
|
622
|
+
}
|
|
623
|
+
if (input.scopeId) {
|
|
624
|
+
params.push(input.scopeId);
|
|
625
|
+
where.push(`m.scope_id = $${params.length}`);
|
|
626
|
+
}
|
|
627
|
+
if (Array.isArray(input.scopeKeys) && input.scopeKeys.length > 0) {
|
|
628
|
+
params.push(input.scopeKeys.map(value => String(value)));
|
|
629
|
+
where.push(`s.scope_key = ANY($${params.length}::text[])`);
|
|
630
|
+
}
|
|
631
|
+
if (input.visibleInBootstrap !== undefined) {
|
|
632
|
+
params.push(input.visibleInBootstrap === true);
|
|
633
|
+
where.push(`m.visible_in_bootstrap = $${params.length}`);
|
|
634
|
+
}
|
|
635
|
+
if (input.visibleInRecall !== undefined) {
|
|
636
|
+
params.push(input.visibleInRecall === true);
|
|
637
|
+
where.push(`m.visible_in_recall = $${params.length}`);
|
|
638
|
+
}
|
|
639
|
+
params.push(Math.max(1, Math.min(200, input.limit || 50)));
|
|
640
|
+
const orderBy = input.visibleInBootstrap === true
|
|
641
|
+
? BOOTSTRAP_ORDER_SQL
|
|
642
|
+
: `m.accepted_at DESC NULLS LAST, m.id ASC`;
|
|
643
|
+
const result = await pool.query(
|
|
644
|
+
`SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
645
|
+
FROM ${memories} m
|
|
646
|
+
JOIN ${scopes} s ON s.id = m.scope_id
|
|
647
|
+
WHERE ${where.join(' AND ')}
|
|
648
|
+
ORDER BY ${orderBy}
|
|
649
|
+
LIMIT $${params.length}`,
|
|
650
|
+
params
|
|
651
|
+
);
|
|
652
|
+
return result.rows;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function currentProjection(input = {}) {
|
|
656
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
657
|
+
let activeScopePath = normalizeScopePath(input.activeScopePath, input.activeScopeKey);
|
|
658
|
+
let activeScopeKey = input.activeScopeKey || activeScopePath[activeScopePath.length - 1] || null;
|
|
659
|
+
if (input.scopeId && !input.activeScopeKey && !input.activeScopePath) {
|
|
660
|
+
const scopeResult = await pool.query(
|
|
661
|
+
`SELECT scope_key FROM ${scopes} WHERE tenant_id = $1 AND id = $2 LIMIT 1`,
|
|
662
|
+
[tenantId, input.scopeId],
|
|
663
|
+
);
|
|
664
|
+
const scopedKey = scopeResult.rows[0]?.scope_key || null;
|
|
665
|
+
if (scopedKey) {
|
|
666
|
+
activeScopePath = [scopedKey];
|
|
667
|
+
activeScopeKey = scopedKey;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const limit = Math.max(1, Math.min(100, input.limit || 50));
|
|
671
|
+
const fetchLimit = Math.max(limit + 1, Math.min(200, Math.max(limit * 4, 40)));
|
|
672
|
+
const asOf = input.asOf || new Date().toISOString();
|
|
673
|
+
const params = [tenantId, activeScopePath, asOf];
|
|
674
|
+
const where = [
|
|
675
|
+
`m.tenant_id = $1`,
|
|
676
|
+
`m.status = 'active'`,
|
|
677
|
+
`s.scope_key = ANY($2::text[])`,
|
|
678
|
+
`(m.visible_in_bootstrap = true OR m.visible_in_recall = true)`,
|
|
679
|
+
`(m.valid_from IS NULL OR m.valid_from <= $3::timestamptz)`,
|
|
680
|
+
`(m.valid_to IS NULL OR m.valid_to > $3::timestamptz)`,
|
|
681
|
+
`(m.stale_after IS NULL OR m.stale_after > $3::timestamptz)`,
|
|
682
|
+
];
|
|
683
|
+
|
|
684
|
+
if (input.scopeId) {
|
|
685
|
+
params.push(input.scopeId);
|
|
686
|
+
where.push(`m.scope_id = $${params.length}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
params.push(fetchLimit);
|
|
690
|
+
const limitParam = `$${params.length}`;
|
|
691
|
+
const evidenceRefsSelect = input.includeEvidenceRefs === true
|
|
692
|
+
? `COALESCE((
|
|
693
|
+
SELECT jsonb_agg(
|
|
694
|
+
jsonb_build_object(
|
|
695
|
+
'id', e.id,
|
|
696
|
+
'sourceKind', e.source_kind,
|
|
697
|
+
'sourceRef', e.source_ref,
|
|
698
|
+
'relationKind', e.relation_kind,
|
|
699
|
+
'weight', e.weight,
|
|
700
|
+
'metadata', e.metadata
|
|
701
|
+
)
|
|
702
|
+
ORDER BY e.id ASC
|
|
703
|
+
)
|
|
704
|
+
FROM ${evidenceRefs} e
|
|
705
|
+
WHERE e.tenant_id = m.tenant_id
|
|
706
|
+
AND e.owner_kind = 'memory_record'
|
|
707
|
+
AND e.owner_id = m.id
|
|
708
|
+
), '[]'::jsonb)`
|
|
709
|
+
: `'[]'::jsonb`;
|
|
710
|
+
|
|
711
|
+
const result = await pool.query(
|
|
712
|
+
`SELECT
|
|
713
|
+
m.*,
|
|
714
|
+
s.scope_kind,
|
|
715
|
+
s.scope_key,
|
|
716
|
+
s.inheritance_mode AS scope_inheritance_mode,
|
|
717
|
+
${evidenceRefsSelect} AS evidence_refs
|
|
718
|
+
FROM ${memories} m
|
|
719
|
+
JOIN ${scopes} s ON s.id = m.scope_id
|
|
720
|
+
WHERE ${where.join(' AND ')}
|
|
721
|
+
ORDER BY array_position($2::text[], s.scope_key) DESC NULLS LAST,
|
|
722
|
+
${BOOTSTRAP_ORDER_SQL}
|
|
723
|
+
LIMIT ${limitParam}`,
|
|
724
|
+
params,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
const positions = new Map(activeScopePath.map((key, index) => [key, index]));
|
|
728
|
+
const applicable = resolveApplicableRecords(
|
|
729
|
+
result.rows
|
|
730
|
+
.map(normalizeCurrentMemoryRow)
|
|
731
|
+
.filter(row => isCurrentProjectionRow(row, asOf)),
|
|
732
|
+
{ activeScopeKey, activeScopePath },
|
|
733
|
+
).sort((left, right) => sortCurrentMemoryRecords(left, right, positions));
|
|
734
|
+
|
|
735
|
+
const selected = applicable.slice(0, limit);
|
|
736
|
+
const truncated = applicable.length > limit;
|
|
737
|
+
return {
|
|
738
|
+
memories: selected,
|
|
739
|
+
meta: {
|
|
740
|
+
source: 'memory_records',
|
|
741
|
+
servingContract: 'current_memory_v1',
|
|
742
|
+
count: selected.length,
|
|
743
|
+
activeScopeKey,
|
|
744
|
+
activeScopePath,
|
|
745
|
+
asOf,
|
|
746
|
+
truncated,
|
|
747
|
+
degraded: truncated,
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async function withTransaction(fn) {
|
|
753
|
+
if (inTransaction) {
|
|
754
|
+
return fn(api, { transactional: true });
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (!canTransact) {
|
|
758
|
+
return fn(api, { transactional: false });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const client = await pool.connect();
|
|
762
|
+
try {
|
|
763
|
+
await client.query('BEGIN');
|
|
764
|
+
const txRecords = createMemoryRecords({ pool: client, schema, defaultTenantId, inTransaction: true });
|
|
765
|
+
const result = await fn(txRecords, { transactional: true });
|
|
766
|
+
await client.query('COMMIT');
|
|
767
|
+
return result;
|
|
768
|
+
} catch (error) {
|
|
769
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
770
|
+
throw error;
|
|
771
|
+
} finally {
|
|
772
|
+
client.release();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const api = {
|
|
777
|
+
upsertScope,
|
|
778
|
+
createVersion,
|
|
779
|
+
upsertMemory,
|
|
780
|
+
upsertFactAssertion,
|
|
781
|
+
linkEvidence,
|
|
782
|
+
recordFeedback,
|
|
783
|
+
findActiveByCanonicalKey,
|
|
784
|
+
findActiveFactByCanonicalKey,
|
|
785
|
+
lockCanonicalKey,
|
|
786
|
+
updateMemoryStatus,
|
|
787
|
+
updateMemoryStatusIfCurrent,
|
|
788
|
+
updateFactAssertionStatus,
|
|
789
|
+
listActive,
|
|
790
|
+
currentProjection,
|
|
791
|
+
withTransaction,
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
return api;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
module.exports = { createMemoryRecords };
|