@shadowforge0/aquifer-memory 1.5.9 → 1.6.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 +96 -73
- package/README_CN.md +659 -0
- package/README_TW.md +680 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +374 -39
- package/consumers/codex-handoff.js +152 -0
- package/consumers/codex.js +1549 -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 +131 -7
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +44 -4
- package/consumers/shared/config.js +28 -0
- package/consumers/shared/factory.js +2 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +53 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +384 -18
- package/core/finalization-review.js +319 -0
- package/core/insights.js +210 -58
- package/core/mcp-manifest.js +69 -2
- package/core/memory-bootstrap.js +188 -0
- package/core/memory-consolidation.js +1236 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +581 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +350 -0
- package/core/storage.js +456 -2
- package/docs/getting-started.md +99 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +51 -2
- package/package.json +31 -9
- 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/backfill-canonical-key.js +250 -0
- package/scripts/codex-recovery.js +532 -0
- package/consumers/miranda/context-inject.js +0 -119
- 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
- package/scripts/queries.json +0 -45
- package/scripts/retro-recall-bench.js +0 -409
- package/scripts/sample-bench-queries.sql +0 -75
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
function requireField(obj, field) {
|
|
6
|
+
if (!obj || obj[field] === undefined || obj[field] === null || obj[field] === '') {
|
|
7
|
+
throw new Error(`${field} is required`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function toJson(value, fallback) {
|
|
12
|
+
return JSON.stringify(value === undefined ? fallback : value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toJsonOrNull(value) {
|
|
16
|
+
return value === undefined || value === null ? null : JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function advisoryLockKeys(namespace, value) {
|
|
20
|
+
const digest = crypto.createHash('sha256').update(`${namespace}:${value}`).digest();
|
|
21
|
+
return [digest.readInt32BE(0), digest.readInt32BE(4)];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BOOTSTRAP_ORDER_SQL = `
|
|
25
|
+
CASE m.memory_type
|
|
26
|
+
WHEN 'constraint' THEN 0
|
|
27
|
+
WHEN 'preference' THEN 1
|
|
28
|
+
WHEN 'state' THEN 2
|
|
29
|
+
WHEN 'open_loop' THEN 3
|
|
30
|
+
WHEN 'decision' THEN 4
|
|
31
|
+
WHEN 'fact' THEN 5
|
|
32
|
+
WHEN 'conclusion' THEN 6
|
|
33
|
+
WHEN 'entity_note' THEN 7
|
|
34
|
+
ELSE 99
|
|
35
|
+
END ASC,
|
|
36
|
+
CASE m.authority
|
|
37
|
+
WHEN 'user_explicit' THEN 0
|
|
38
|
+
WHEN 'executable_evidence' THEN 1
|
|
39
|
+
WHEN 'manual' THEN 2
|
|
40
|
+
WHEN 'system' THEN 3
|
|
41
|
+
WHEN 'verified_summary' THEN 4
|
|
42
|
+
WHEN 'llm_inference' THEN 5
|
|
43
|
+
WHEN 'raw_transcript' THEN 6
|
|
44
|
+
ELSE 99
|
|
45
|
+
END ASC,
|
|
46
|
+
m.accepted_at DESC NULLS LAST,
|
|
47
|
+
m.id ASC`;
|
|
48
|
+
|
|
49
|
+
function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = false }) {
|
|
50
|
+
const scopes = `${schema}.scopes`;
|
|
51
|
+
const versions = `${schema}.versions`;
|
|
52
|
+
const memories = `${schema}.memory_records`;
|
|
53
|
+
const factAssertions = `${schema}.fact_assertions_v1`;
|
|
54
|
+
const evidenceRefs = `${schema}.evidence_refs`;
|
|
55
|
+
const feedback = `${schema}.feedback`;
|
|
56
|
+
const canTransact = typeof pool.connect === 'function';
|
|
57
|
+
|
|
58
|
+
async function upsertScope(input = {}) {
|
|
59
|
+
requireField(input, 'scopeKind');
|
|
60
|
+
requireField(input, 'scopeKey');
|
|
61
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
62
|
+
const result = await pool.query(
|
|
63
|
+
`INSERT INTO ${scopes} (
|
|
64
|
+
tenant_id, scope_kind, scope_key, parent_scope_id, inheritance_mode,
|
|
65
|
+
context_key, topic_key, metadata, active_from, active_to
|
|
66
|
+
)
|
|
67
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,'defaultable'),$6,$7,COALESCE($8::jsonb,'{}'::jsonb),$9,$10)
|
|
68
|
+
ON CONFLICT (tenant_id, scope_kind, scope_key) DO UPDATE SET
|
|
69
|
+
parent_scope_id = COALESCE(EXCLUDED.parent_scope_id, ${scopes}.parent_scope_id),
|
|
70
|
+
inheritance_mode = EXCLUDED.inheritance_mode,
|
|
71
|
+
context_key = COALESCE(EXCLUDED.context_key, ${scopes}.context_key),
|
|
72
|
+
topic_key = COALESCE(EXCLUDED.topic_key, ${scopes}.topic_key),
|
|
73
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${scopes}.metadata),
|
|
74
|
+
active_from = COALESCE(EXCLUDED.active_from, ${scopes}.active_from),
|
|
75
|
+
active_to = COALESCE(EXCLUDED.active_to, ${scopes}.active_to)
|
|
76
|
+
RETURNING *`,
|
|
77
|
+
[
|
|
78
|
+
tenantId,
|
|
79
|
+
input.scopeKind,
|
|
80
|
+
input.scopeKey,
|
|
81
|
+
input.parentScopeId || null,
|
|
82
|
+
input.inheritanceMode || 'defaultable',
|
|
83
|
+
input.contextKey || null,
|
|
84
|
+
input.topicKey || null,
|
|
85
|
+
toJson(input.metadata, {}),
|
|
86
|
+
input.activeFrom || null,
|
|
87
|
+
input.activeTo || null,
|
|
88
|
+
]
|
|
89
|
+
);
|
|
90
|
+
return result.rows[0] || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function createVersion(input = {}) {
|
|
94
|
+
requireField(input, 'versionKind');
|
|
95
|
+
requireField(input, 'version');
|
|
96
|
+
requireField(input, 'versionHash');
|
|
97
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
98
|
+
const result = await pool.query(
|
|
99
|
+
`INSERT INTO ${versions} (
|
|
100
|
+
tenant_id, version_kind, version, version_hash, active, metadata,
|
|
101
|
+
released_at, retired_at
|
|
102
|
+
)
|
|
103
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,false),COALESCE($6::jsonb,'{}'::jsonb),COALESCE($7,now()),$8)
|
|
104
|
+
ON CONFLICT (tenant_id, version_kind, version_hash) DO UPDATE SET
|
|
105
|
+
version = EXCLUDED.version,
|
|
106
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${versions}.metadata),
|
|
107
|
+
retired_at = COALESCE(EXCLUDED.retired_at, ${versions}.retired_at)
|
|
108
|
+
RETURNING *`,
|
|
109
|
+
[
|
|
110
|
+
tenantId,
|
|
111
|
+
input.versionKind,
|
|
112
|
+
input.version,
|
|
113
|
+
input.versionHash,
|
|
114
|
+
input.active === true,
|
|
115
|
+
toJson(input.metadata, {}),
|
|
116
|
+
input.releasedAt || null,
|
|
117
|
+
input.retiredAt || null,
|
|
118
|
+
]
|
|
119
|
+
);
|
|
120
|
+
return result.rows[0] || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function upsertMemory(input = {}) {
|
|
124
|
+
requireField(input, 'memoryType');
|
|
125
|
+
requireField(input, 'canonicalKey');
|
|
126
|
+
requireField(input, 'scopeId');
|
|
127
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
128
|
+
const status = input.status || 'candidate';
|
|
129
|
+
const result = await pool.query(
|
|
130
|
+
`INSERT INTO ${memories} (
|
|
131
|
+
tenant_id, memory_type, canonical_key, scope_id, context_key, topic_key,
|
|
132
|
+
title, summary, payload, status, authority, accepted_at, valid_from,
|
|
133
|
+
valid_to, stale_after, superseded_by, backing_fact_id, observed_at,
|
|
134
|
+
revoked_at, superseded_at, version_id, visible_in_bootstrap,
|
|
135
|
+
visible_in_recall, rank_features, created_by_finalization_id,
|
|
136
|
+
created_by_compaction_run_id
|
|
137
|
+
)
|
|
138
|
+
VALUES (
|
|
139
|
+
$1,$2,$3,$4,$5,$6,$7,COALESCE($8,''),COALESCE($9::jsonb,'{}'::jsonb),
|
|
140
|
+
COALESCE($10,'candidate'),COALESCE($11,'llm_inference'),$12,$13,$14,$15,
|
|
141
|
+
$16,$17,$18,$19,$20,$21,COALESCE($22,false),COALESCE($23,false),COALESCE($24::jsonb,'{}'::jsonb),$25,$26
|
|
142
|
+
)
|
|
143
|
+
ON CONFLICT (tenant_id, canonical_key) WHERE status = 'active' DO UPDATE SET
|
|
144
|
+
scope_id = EXCLUDED.scope_id,
|
|
145
|
+
context_key = COALESCE(EXCLUDED.context_key, ${memories}.context_key),
|
|
146
|
+
topic_key = COALESCE(EXCLUDED.topic_key, ${memories}.topic_key),
|
|
147
|
+
title = COALESCE(EXCLUDED.title, ${memories}.title),
|
|
148
|
+
summary = COALESCE(NULLIF(EXCLUDED.summary, ''), ${memories}.summary),
|
|
149
|
+
payload = COALESCE(NULLIF(EXCLUDED.payload, '{}'::jsonb), ${memories}.payload),
|
|
150
|
+
authority = EXCLUDED.authority,
|
|
151
|
+
accepted_at = COALESCE(EXCLUDED.accepted_at, ${memories}.accepted_at),
|
|
152
|
+
valid_from = COALESCE(EXCLUDED.valid_from, ${memories}.valid_from),
|
|
153
|
+
valid_to = COALESCE(EXCLUDED.valid_to, ${memories}.valid_to),
|
|
154
|
+
stale_after = COALESCE(EXCLUDED.stale_after, ${memories}.stale_after),
|
|
155
|
+
version_id = COALESCE(EXCLUDED.version_id, ${memories}.version_id),
|
|
156
|
+
backing_fact_id = COALESCE(EXCLUDED.backing_fact_id, ${memories}.backing_fact_id),
|
|
157
|
+
observed_at = COALESCE(EXCLUDED.observed_at, ${memories}.observed_at),
|
|
158
|
+
revoked_at = COALESCE(EXCLUDED.revoked_at, ${memories}.revoked_at),
|
|
159
|
+
superseded_at = COALESCE(EXCLUDED.superseded_at, ${memories}.superseded_at),
|
|
160
|
+
visible_in_bootstrap = EXCLUDED.visible_in_bootstrap,
|
|
161
|
+
visible_in_recall = EXCLUDED.visible_in_recall,
|
|
162
|
+
rank_features = COALESCE(NULLIF(EXCLUDED.rank_features, '{}'::jsonb), ${memories}.rank_features),
|
|
163
|
+
created_by_finalization_id = COALESCE(${memories}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
164
|
+
created_by_compaction_run_id = COALESCE(${memories}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
|
|
165
|
+
RETURNING *`,
|
|
166
|
+
[
|
|
167
|
+
tenantId,
|
|
168
|
+
input.memoryType,
|
|
169
|
+
input.canonicalKey,
|
|
170
|
+
input.scopeId,
|
|
171
|
+
input.contextKey || null,
|
|
172
|
+
input.topicKey || null,
|
|
173
|
+
input.title || null,
|
|
174
|
+
input.summary || '',
|
|
175
|
+
toJson(input.payload, {}),
|
|
176
|
+
status,
|
|
177
|
+
input.authority || 'llm_inference',
|
|
178
|
+
input.acceptedAt || (status === 'active' ? new Date().toISOString() : null),
|
|
179
|
+
input.validFrom || null,
|
|
180
|
+
input.validTo || null,
|
|
181
|
+
input.staleAfter || null,
|
|
182
|
+
input.supersededBy || null,
|
|
183
|
+
input.backingFactId || input.backing_fact_id || null,
|
|
184
|
+
input.observedAt || input.observed_at || null,
|
|
185
|
+
input.revokedAt || input.revoked_at || null,
|
|
186
|
+
input.supersededAt || input.superseded_at || null,
|
|
187
|
+
input.versionId || null,
|
|
188
|
+
input.visibleInBootstrap === true,
|
|
189
|
+
input.visibleInRecall === true,
|
|
190
|
+
toJson(input.rankFeatures, {}),
|
|
191
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
192
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
193
|
+
]
|
|
194
|
+
);
|
|
195
|
+
return result.rows[0] || null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function upsertFactAssertion(input = {}) {
|
|
199
|
+
requireField(input, 'canonicalKey');
|
|
200
|
+
requireField(input, 'scopeId');
|
|
201
|
+
requireField(input, 'predicate');
|
|
202
|
+
requireField(input, 'objectKind');
|
|
203
|
+
requireField(input, 'assertionHash');
|
|
204
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
205
|
+
const status = input.status || 'active';
|
|
206
|
+
const result = await pool.query(
|
|
207
|
+
`INSERT INTO ${factAssertions} (
|
|
208
|
+
tenant_id, canonical_key, scope_id, subject_entity_id, predicate,
|
|
209
|
+
object_kind, object_entity_id, object_value_json, qualifiers_json,
|
|
210
|
+
valid_from, valid_to, observed_at, stale_after, accepted_at,
|
|
211
|
+
revoked_at, superseded_at, status, authority, assertion_hash,
|
|
212
|
+
superseded_by, version_id, metadata, created_by_finalization_id,
|
|
213
|
+
created_by_compaction_run_id
|
|
214
|
+
)
|
|
215
|
+
VALUES (
|
|
216
|
+
$1,$2,$3,$4,$5,$6,$7,$8::jsonb,COALESCE($9::jsonb,'{}'::jsonb),
|
|
217
|
+
$10,$11,$12,$13,$14,$15,$16,COALESCE($17,'active'),COALESCE($18,'verified_summary'),
|
|
218
|
+
$19,$20,$21,COALESCE($22::jsonb,'{}'::jsonb),$23,$24
|
|
219
|
+
)
|
|
220
|
+
ON CONFLICT (tenant_id, canonical_key) WHERE status = 'active' DO UPDATE SET
|
|
221
|
+
scope_id = EXCLUDED.scope_id,
|
|
222
|
+
subject_entity_id = COALESCE(EXCLUDED.subject_entity_id, ${factAssertions}.subject_entity_id),
|
|
223
|
+
predicate = EXCLUDED.predicate,
|
|
224
|
+
object_kind = EXCLUDED.object_kind,
|
|
225
|
+
object_entity_id = COALESCE(EXCLUDED.object_entity_id, ${factAssertions}.object_entity_id),
|
|
226
|
+
object_value_json = EXCLUDED.object_value_json,
|
|
227
|
+
qualifiers_json = COALESCE(NULLIF(EXCLUDED.qualifiers_json, '{}'::jsonb), ${factAssertions}.qualifiers_json),
|
|
228
|
+
valid_from = COALESCE(EXCLUDED.valid_from, ${factAssertions}.valid_from),
|
|
229
|
+
valid_to = COALESCE(EXCLUDED.valid_to, ${factAssertions}.valid_to),
|
|
230
|
+
observed_at = COALESCE(EXCLUDED.observed_at, ${factAssertions}.observed_at),
|
|
231
|
+
stale_after = COALESCE(EXCLUDED.stale_after, ${factAssertions}.stale_after),
|
|
232
|
+
accepted_at = COALESCE(EXCLUDED.accepted_at, ${factAssertions}.accepted_at),
|
|
233
|
+
revoked_at = COALESCE(EXCLUDED.revoked_at, ${factAssertions}.revoked_at),
|
|
234
|
+
superseded_at = COALESCE(EXCLUDED.superseded_at, ${factAssertions}.superseded_at),
|
|
235
|
+
authority = EXCLUDED.authority,
|
|
236
|
+
assertion_hash = EXCLUDED.assertion_hash,
|
|
237
|
+
version_id = COALESCE(EXCLUDED.version_id, ${factAssertions}.version_id),
|
|
238
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${factAssertions}.metadata),
|
|
239
|
+
created_by_finalization_id = COALESCE(${factAssertions}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
240
|
+
created_by_compaction_run_id = COALESCE(${factAssertions}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id),
|
|
241
|
+
updated_at = now()
|
|
242
|
+
RETURNING *`,
|
|
243
|
+
[
|
|
244
|
+
tenantId,
|
|
245
|
+
input.canonicalKey,
|
|
246
|
+
input.scopeId,
|
|
247
|
+
input.subjectEntityId || input.subject_entity_id || null,
|
|
248
|
+
input.predicate,
|
|
249
|
+
input.objectKind || input.object_kind,
|
|
250
|
+
input.objectEntityId || input.object_entity_id || null,
|
|
251
|
+
toJsonOrNull(input.objectValueJson ?? input.object_value_json),
|
|
252
|
+
toJson(input.qualifiersJson ?? input.qualifiers_json, {}),
|
|
253
|
+
input.validFrom || input.valid_from || null,
|
|
254
|
+
input.validTo || input.valid_to || null,
|
|
255
|
+
input.observedAt || input.observed_at || null,
|
|
256
|
+
input.staleAfter || input.stale_after || null,
|
|
257
|
+
input.acceptedAt || input.accepted_at || (status === 'active' ? new Date().toISOString() : null),
|
|
258
|
+
input.revokedAt || input.revoked_at || null,
|
|
259
|
+
input.supersededAt || input.superseded_at || null,
|
|
260
|
+
status,
|
|
261
|
+
input.authority || 'verified_summary',
|
|
262
|
+
input.assertionHash || input.assertion_hash,
|
|
263
|
+
input.supersededBy || input.superseded_by || null,
|
|
264
|
+
input.versionId || input.version_id || null,
|
|
265
|
+
toJson(input.metadata, {}),
|
|
266
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
267
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
268
|
+
]
|
|
269
|
+
);
|
|
270
|
+
return result.rows[0] || null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function linkEvidence(input = {}) {
|
|
274
|
+
requireField(input, 'ownerKind');
|
|
275
|
+
requireField(input, 'ownerId');
|
|
276
|
+
requireField(input, 'sourceKind');
|
|
277
|
+
requireField(input, 'sourceRef');
|
|
278
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
279
|
+
const result = await pool.query(
|
|
280
|
+
`INSERT INTO ${evidenceRefs} (
|
|
281
|
+
tenant_id, owner_kind, owner_id, source_kind, source_ref,
|
|
282
|
+
relation_kind, weight, metadata, created_by_finalization_id,
|
|
283
|
+
created_by_compaction_run_id
|
|
284
|
+
)
|
|
285
|
+
VALUES ($1,$2,$3,$4,$5,COALESCE($6,'supporting'),COALESCE($7,1.0),COALESCE($8::jsonb,'{}'::jsonb),$9,$10)
|
|
286
|
+
ON CONFLICT (tenant_id, owner_kind, owner_id, source_kind, source_ref, relation_kind)
|
|
287
|
+
DO UPDATE SET weight = EXCLUDED.weight,
|
|
288
|
+
metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${evidenceRefs}.metadata),
|
|
289
|
+
created_by_finalization_id = COALESCE(${evidenceRefs}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
|
|
290
|
+
created_by_compaction_run_id = COALESCE(${evidenceRefs}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
|
|
291
|
+
RETURNING *`,
|
|
292
|
+
[
|
|
293
|
+
tenantId,
|
|
294
|
+
input.ownerKind,
|
|
295
|
+
input.ownerId,
|
|
296
|
+
input.sourceKind,
|
|
297
|
+
input.sourceRef,
|
|
298
|
+
input.relationKind || 'supporting',
|
|
299
|
+
input.weight ?? 1.0,
|
|
300
|
+
toJson(input.metadata, {}),
|
|
301
|
+
input.createdByFinalizationId || input.created_by_finalization_id || null,
|
|
302
|
+
input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
|
|
303
|
+
]
|
|
304
|
+
);
|
|
305
|
+
return result.rows[0] || null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function recordFeedback(input = {}) {
|
|
309
|
+
requireField(input, 'targetKind');
|
|
310
|
+
requireField(input, 'targetId');
|
|
311
|
+
requireField(input, 'feedbackType');
|
|
312
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
313
|
+
const result = await pool.query(
|
|
314
|
+
`INSERT INTO ${feedback} (
|
|
315
|
+
tenant_id, target_kind, target_id, feedback_type, actor_kind, actor_id,
|
|
316
|
+
query_fingerprint, note, metadata
|
|
317
|
+
)
|
|
318
|
+
VALUES ($1,$2,$3,$4,COALESCE($5,'user'),$6,$7,$8,COALESCE($9::jsonb,'{}'::jsonb))
|
|
319
|
+
RETURNING *`,
|
|
320
|
+
[
|
|
321
|
+
tenantId,
|
|
322
|
+
input.targetKind,
|
|
323
|
+
String(input.targetId),
|
|
324
|
+
input.feedbackType,
|
|
325
|
+
input.actorKind || 'user',
|
|
326
|
+
input.actorId || null,
|
|
327
|
+
input.queryFingerprint || null,
|
|
328
|
+
input.note || null,
|
|
329
|
+
toJson(input.metadata, {}),
|
|
330
|
+
]
|
|
331
|
+
);
|
|
332
|
+
return result.rows[0] || null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function findActiveByCanonicalKey(input = {}) {
|
|
336
|
+
requireField(input, 'canonicalKey');
|
|
337
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
338
|
+
const lockClause = input.forUpdate === true ? 'FOR UPDATE OF m' : '';
|
|
339
|
+
const result = await pool.query(
|
|
340
|
+
`SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
341
|
+
FROM ${memories} m
|
|
342
|
+
JOIN ${scopes} s ON s.id = m.scope_id
|
|
343
|
+
WHERE m.tenant_id = $1
|
|
344
|
+
AND m.canonical_key = $2
|
|
345
|
+
AND m.status = 'active'
|
|
346
|
+
ORDER BY m.accepted_at DESC NULLS LAST, m.id ASC
|
|
347
|
+
${lockClause}`,
|
|
348
|
+
[tenantId, input.canonicalKey]
|
|
349
|
+
);
|
|
350
|
+
return result.rows;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function findActiveFactByCanonicalKey(input = {}) {
|
|
354
|
+
requireField(input, 'canonicalKey');
|
|
355
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
356
|
+
const lockClause = input.forUpdate === true ? 'FOR UPDATE OF f' : '';
|
|
357
|
+
const result = await pool.query(
|
|
358
|
+
`SELECT f.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
359
|
+
FROM ${factAssertions} f
|
|
360
|
+
JOIN ${scopes} s ON s.id = f.scope_id
|
|
361
|
+
WHERE f.tenant_id = $1
|
|
362
|
+
AND f.canonical_key = $2
|
|
363
|
+
AND f.status = 'active'
|
|
364
|
+
ORDER BY f.accepted_at DESC NULLS LAST, f.id ASC
|
|
365
|
+
${lockClause}`,
|
|
366
|
+
[tenantId, input.canonicalKey]
|
|
367
|
+
);
|
|
368
|
+
return result.rows;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function lockCanonicalKey(input = {}) {
|
|
372
|
+
requireField(input, 'canonicalKey');
|
|
373
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
374
|
+
const [key1, key2] = advisoryLockKeys(
|
|
375
|
+
'aquifer.memory_records.active_canonical',
|
|
376
|
+
`${tenantId}:${input.canonicalKey}`,
|
|
377
|
+
);
|
|
378
|
+
await pool.query('SELECT pg_advisory_xact_lock($1, $2)', [key1, key2]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function updateMemoryStatus(input = {}) {
|
|
382
|
+
requireField(input, 'memoryId');
|
|
383
|
+
requireField(input, 'status');
|
|
384
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
385
|
+
const visibleBootstrap = input.status === 'active' ? input.visibleInBootstrap === true : false;
|
|
386
|
+
const visibleRecall = input.status === 'active' ? input.visibleInRecall === true : false;
|
|
387
|
+
const result = await pool.query(
|
|
388
|
+
`UPDATE ${memories}
|
|
389
|
+
SET status = $3,
|
|
390
|
+
superseded_by = COALESCE($4, superseded_by),
|
|
391
|
+
valid_to = COALESCE($5, valid_to),
|
|
392
|
+
superseded_at = CASE
|
|
393
|
+
WHEN $3 = 'superseded' THEN COALESCE($8, superseded_at, now())
|
|
394
|
+
ELSE superseded_at
|
|
395
|
+
END,
|
|
396
|
+
revoked_at = CASE
|
|
397
|
+
WHEN $3 = 'revoked' THEN COALESCE($9, revoked_at, now())
|
|
398
|
+
ELSE revoked_at
|
|
399
|
+
END,
|
|
400
|
+
visible_in_bootstrap = $6,
|
|
401
|
+
visible_in_recall = $7,
|
|
402
|
+
updated_at = now()
|
|
403
|
+
WHERE tenant_id = $1 AND id = $2
|
|
404
|
+
RETURNING *`,
|
|
405
|
+
[
|
|
406
|
+
tenantId,
|
|
407
|
+
input.memoryId,
|
|
408
|
+
input.status,
|
|
409
|
+
input.supersededBy || null,
|
|
410
|
+
input.validTo || null,
|
|
411
|
+
visibleBootstrap,
|
|
412
|
+
visibleRecall,
|
|
413
|
+
input.supersededAt || input.superseded_at || null,
|
|
414
|
+
input.revokedAt || input.revoked_at || null,
|
|
415
|
+
]
|
|
416
|
+
);
|
|
417
|
+
return result.rows[0] || null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function updateMemoryStatusIfCurrent(input = {}) {
|
|
421
|
+
requireField(input, 'memoryId');
|
|
422
|
+
requireField(input, 'fromStatus');
|
|
423
|
+
requireField(input, 'status');
|
|
424
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
425
|
+
const visibleBootstrap = input.status === 'active' ? input.visibleInBootstrap === true : false;
|
|
426
|
+
const visibleRecall = input.status === 'active' ? input.visibleInRecall === true : false;
|
|
427
|
+
const result = await pool.query(
|
|
428
|
+
`UPDATE ${memories}
|
|
429
|
+
SET status = $4,
|
|
430
|
+
superseded_by = COALESCE($5, superseded_by),
|
|
431
|
+
valid_to = COALESCE($6, valid_to),
|
|
432
|
+
superseded_at = CASE
|
|
433
|
+
WHEN $4 = 'superseded' THEN COALESCE($9, superseded_at, now())
|
|
434
|
+
ELSE superseded_at
|
|
435
|
+
END,
|
|
436
|
+
revoked_at = CASE
|
|
437
|
+
WHEN $4 = 'revoked' THEN COALESCE($10, revoked_at, now())
|
|
438
|
+
ELSE revoked_at
|
|
439
|
+
END,
|
|
440
|
+
visible_in_bootstrap = $7,
|
|
441
|
+
visible_in_recall = $8,
|
|
442
|
+
updated_at = now()
|
|
443
|
+
WHERE tenant_id = $1 AND id = $2 AND status = $3
|
|
444
|
+
RETURNING *`,
|
|
445
|
+
[
|
|
446
|
+
tenantId,
|
|
447
|
+
input.memoryId,
|
|
448
|
+
input.fromStatus,
|
|
449
|
+
input.status,
|
|
450
|
+
input.supersededBy || null,
|
|
451
|
+
input.validTo || null,
|
|
452
|
+
visibleBootstrap,
|
|
453
|
+
visibleRecall,
|
|
454
|
+
input.supersededAt || input.superseded_at || null,
|
|
455
|
+
input.revokedAt || input.revoked_at || null,
|
|
456
|
+
]
|
|
457
|
+
);
|
|
458
|
+
return result.rows[0] || null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function updateFactAssertionStatus(input = {}) {
|
|
462
|
+
requireField(input, 'factId');
|
|
463
|
+
requireField(input, 'status');
|
|
464
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
465
|
+
const result = await pool.query(
|
|
466
|
+
`UPDATE ${factAssertions}
|
|
467
|
+
SET status = $3,
|
|
468
|
+
superseded_by = COALESCE($4, superseded_by),
|
|
469
|
+
valid_to = COALESCE($5, valid_to),
|
|
470
|
+
superseded_at = CASE
|
|
471
|
+
WHEN $3 = 'superseded' THEN COALESCE($6, superseded_at, now())
|
|
472
|
+
ELSE superseded_at
|
|
473
|
+
END,
|
|
474
|
+
revoked_at = CASE
|
|
475
|
+
WHEN $3 = 'revoked' THEN COALESCE($7, revoked_at, now())
|
|
476
|
+
ELSE revoked_at
|
|
477
|
+
END,
|
|
478
|
+
updated_at = now()
|
|
479
|
+
WHERE tenant_id = $1 AND id = $2
|
|
480
|
+
RETURNING *`,
|
|
481
|
+
[
|
|
482
|
+
tenantId,
|
|
483
|
+
input.factId,
|
|
484
|
+
input.status,
|
|
485
|
+
input.supersededBy || input.superseded_by || null,
|
|
486
|
+
input.validTo || input.valid_to || null,
|
|
487
|
+
input.supersededAt || input.superseded_at || null,
|
|
488
|
+
input.revokedAt || input.revoked_at || null,
|
|
489
|
+
]
|
|
490
|
+
);
|
|
491
|
+
return result.rows[0] || null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function listActive(input = {}) {
|
|
495
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
496
|
+
const params = [tenantId];
|
|
497
|
+
const where = [`m.tenant_id = $1`, `m.status = 'active'`];
|
|
498
|
+
if (input.asOf) {
|
|
499
|
+
params.push(input.asOf);
|
|
500
|
+
const at = `$${params.length}::timestamptz`;
|
|
501
|
+
where.push(`(m.valid_from IS NULL OR m.valid_from <= ${at})`);
|
|
502
|
+
where.push(`(m.valid_to IS NULL OR m.valid_to > ${at})`);
|
|
503
|
+
where.push(`(m.stale_after IS NULL OR m.stale_after > ${at})`);
|
|
504
|
+
}
|
|
505
|
+
if (input.scopeId) {
|
|
506
|
+
params.push(input.scopeId);
|
|
507
|
+
where.push(`m.scope_id = $${params.length}`);
|
|
508
|
+
}
|
|
509
|
+
if (Array.isArray(input.scopeKeys) && input.scopeKeys.length > 0) {
|
|
510
|
+
params.push(input.scopeKeys.map(value => String(value)));
|
|
511
|
+
where.push(`s.scope_key = ANY($${params.length}::text[])`);
|
|
512
|
+
}
|
|
513
|
+
if (input.visibleInBootstrap !== undefined) {
|
|
514
|
+
params.push(input.visibleInBootstrap === true);
|
|
515
|
+
where.push(`m.visible_in_bootstrap = $${params.length}`);
|
|
516
|
+
}
|
|
517
|
+
if (input.visibleInRecall !== undefined) {
|
|
518
|
+
params.push(input.visibleInRecall === true);
|
|
519
|
+
where.push(`m.visible_in_recall = $${params.length}`);
|
|
520
|
+
}
|
|
521
|
+
params.push(Math.max(1, Math.min(200, input.limit || 50)));
|
|
522
|
+
const orderBy = input.visibleInBootstrap === true
|
|
523
|
+
? BOOTSTRAP_ORDER_SQL
|
|
524
|
+
: `m.accepted_at DESC NULLS LAST, m.id ASC`;
|
|
525
|
+
const result = await pool.query(
|
|
526
|
+
`SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
527
|
+
FROM ${memories} m
|
|
528
|
+
JOIN ${scopes} s ON s.id = m.scope_id
|
|
529
|
+
WHERE ${where.join(' AND ')}
|
|
530
|
+
ORDER BY ${orderBy}
|
|
531
|
+
LIMIT $${params.length}`,
|
|
532
|
+
params
|
|
533
|
+
);
|
|
534
|
+
return result.rows;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function withTransaction(fn) {
|
|
538
|
+
if (inTransaction) {
|
|
539
|
+
return fn(api, { transactional: true });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!canTransact) {
|
|
543
|
+
return fn(api, { transactional: false });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const client = await pool.connect();
|
|
547
|
+
try {
|
|
548
|
+
await client.query('BEGIN');
|
|
549
|
+
const txRecords = createMemoryRecords({ pool: client, schema, defaultTenantId, inTransaction: true });
|
|
550
|
+
const result = await fn(txRecords, { transactional: true });
|
|
551
|
+
await client.query('COMMIT');
|
|
552
|
+
return result;
|
|
553
|
+
} catch (error) {
|
|
554
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
555
|
+
throw error;
|
|
556
|
+
} finally {
|
|
557
|
+
client.release();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const api = {
|
|
562
|
+
upsertScope,
|
|
563
|
+
createVersion,
|
|
564
|
+
upsertMemory,
|
|
565
|
+
upsertFactAssertion,
|
|
566
|
+
linkEvidence,
|
|
567
|
+
recordFeedback,
|
|
568
|
+
findActiveByCanonicalKey,
|
|
569
|
+
findActiveFactByCanonicalKey,
|
|
570
|
+
lockCanonicalKey,
|
|
571
|
+
updateMemoryStatus,
|
|
572
|
+
updateMemoryStatusIfCurrent,
|
|
573
|
+
updateFactAssertionStatus,
|
|
574
|
+
listActive,
|
|
575
|
+
withTransaction,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
return api;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
module.exports = { createMemoryRecords };
|