hippo-memory 1.15.0 → 1.17.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.
Files changed (97) hide show
  1. package/README.md +862 -861
  2. package/dist/audit.d.ts +1 -1
  3. package/dist/audit.d.ts.map +1 -1
  4. package/dist/audit.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +1244 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/customer-notes.d.ts +95 -0
  9. package/dist/customer-notes.d.ts.map +1 -0
  10. package/dist/customer-notes.js +296 -0
  11. package/dist/customer-notes.js.map +1 -0
  12. package/dist/db.d.ts.map +1 -1
  13. package/dist/db.js +731 -1
  14. package/dist/db.js.map +1 -1
  15. package/dist/graph-extract.d.ts +55 -0
  16. package/dist/graph-extract.d.ts.map +1 -0
  17. package/dist/graph-extract.js +259 -0
  18. package/dist/graph-extract.js.map +1 -0
  19. package/dist/graph-recall.d.ts +41 -0
  20. package/dist/graph-recall.d.ts.map +1 -0
  21. package/dist/graph-recall.js +246 -0
  22. package/dist/graph-recall.js.map +1 -0
  23. package/dist/graph.d.ts +137 -0
  24. package/dist/graph.d.ts.map +1 -0
  25. package/dist/graph.js +433 -0
  26. package/dist/graph.js.map +1 -0
  27. package/dist/incidents.d.ts +100 -0
  28. package/dist/incidents.d.ts.map +1 -0
  29. package/dist/incidents.js +322 -0
  30. package/dist/incidents.js.map +1 -0
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/memory.d.ts +6 -0
  36. package/dist/memory.d.ts.map +1 -1
  37. package/dist/memory.js +6 -0
  38. package/dist/memory.js.map +1 -1
  39. package/dist/policies.d.ts +149 -0
  40. package/dist/policies.d.ts.map +1 -0
  41. package/dist/policies.js +380 -0
  42. package/dist/policies.js.map +1 -0
  43. package/dist/processes.d.ts +104 -0
  44. package/dist/processes.d.ts.map +1 -0
  45. package/dist/processes.js +330 -0
  46. package/dist/processes.js.map +1 -0
  47. package/dist/project-briefs.d.ts +126 -0
  48. package/dist/project-briefs.d.ts.map +1 -0
  49. package/dist/project-briefs.js +453 -0
  50. package/dist/project-briefs.js.map +1 -0
  51. package/dist/search.d.ts +7 -0
  52. package/dist/search.d.ts.map +1 -1
  53. package/dist/search.js.map +1 -1
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +1028 -16
  56. package/dist/server.js.map +1 -1
  57. package/dist/skills.d.ts +98 -0
  58. package/dist/skills.d.ts.map +1 -0
  59. package/dist/skills.js +339 -0
  60. package/dist/skills.js.map +1 -0
  61. package/dist/src/audit.js.map +1 -1
  62. package/dist/src/cli.js +1244 -3
  63. package/dist/src/cli.js.map +1 -1
  64. package/dist/src/customer-notes.js +296 -0
  65. package/dist/src/customer-notes.js.map +1 -0
  66. package/dist/src/db.js +731 -1
  67. package/dist/src/db.js.map +1 -1
  68. package/dist/src/graph-extract.js +259 -0
  69. package/dist/src/graph-extract.js.map +1 -0
  70. package/dist/src/graph-recall.js +246 -0
  71. package/dist/src/graph-recall.js.map +1 -0
  72. package/dist/src/graph.js +433 -0
  73. package/dist/src/graph.js.map +1 -0
  74. package/dist/src/incidents.js +322 -0
  75. package/dist/src/incidents.js.map +1 -0
  76. package/dist/src/index.js +1 -0
  77. package/dist/src/index.js.map +1 -1
  78. package/dist/src/memory.js +6 -0
  79. package/dist/src/memory.js.map +1 -1
  80. package/dist/src/policies.js +380 -0
  81. package/dist/src/policies.js.map +1 -0
  82. package/dist/src/processes.js +330 -0
  83. package/dist/src/processes.js.map +1 -0
  84. package/dist/src/project-briefs.js +453 -0
  85. package/dist/src/project-briefs.js.map +1 -0
  86. package/dist/src/search.js.map +1 -1
  87. package/dist/src/server.js +1028 -16
  88. package/dist/src/server.js.map +1 -1
  89. package/dist/src/skills.js +339 -0
  90. package/dist/src/skills.js.map +1 -0
  91. package/dist/src/version.js +1 -1
  92. package/dist/version.d.ts +1 -1
  93. package/dist/version.js +1 -1
  94. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  95. package/extensions/openclaw-plugin/package.json +1 -1
  96. package/openclaw.plugin.json +1 -1
  97. package/package.json +2 -2
package/dist/graph.js ADDED
@@ -0,0 +1,433 @@
1
+ /**
2
+ * E3.3 graph layer over consolidated state - the graph-on-consolidated guard.
3
+ * (docs/plans/2026-06-01-e3-graph-guard.md).
4
+ *
5
+ * A graph of canonical `entities` (person/project/customer/system/policy/decision) and
6
+ * `relations` (owns/supersedes/depends-on/blocked-by/references) sits ON TOP OF
7
+ * consolidated memories. The hard rule: the graph NEVER indexes the raw layer - every
8
+ * entity and relation references a memory whose `kind IN ('distilled','superseded')`,
9
+ * never `kind='raw'`. Enforced at the DB level (CHECK on source_kind/kind + BEFORE
10
+ * INSERT and BEFORE UPDATE triggers that tie source_kind/kind to the FK'd memory's
11
+ * actual kind and enforce tenant-match - relations also reject cross-tenant edges), so
12
+ * the forbidden state is unrepresentable regardless of code path. These helpers
13
+ * surface the same guard as clear throws BEFORE hitting the trigger backstop.
14
+ *
15
+ * Scope (E3.3 first slice): the substrate + the guard + a thin insert/load/enqueue
16
+ * API. The `graph_extraction_queue` is the interface the deferred `hippo sleep`
17
+ * enqueue-hook + E3.1 entity extraction will call. No operator surface (CLI/HTTP/SDK)
18
+ * until E3.2 multi-hop recall.
19
+ */
20
+ import { openHippoDb, closeHippoDb } from './db.js';
21
+ import { assertTenantId } from './store.js';
22
+ export const GRAPH_ENTITY_TYPES = new Set([
23
+ 'person', 'project', 'customer', 'system', 'policy', 'decision',
24
+ ]);
25
+ export const GRAPH_RELATION_TYPES = new Set([
26
+ 'owns', 'supersedes', 'depends-on', 'blocked-by', 'references',
27
+ ]);
28
+ export const VALID_QUEUE_STATES = new Set([
29
+ 'pending', 'processed', 'skipped',
30
+ ]);
31
+ export const MAX_ENTITY_NAME_LEN = 512;
32
+ function rowToEntity(row) {
33
+ return {
34
+ id: row.id,
35
+ tenantId: row.tenant_id,
36
+ entityType: row.entity_type,
37
+ name: row.name,
38
+ memoryId: row.memory_id,
39
+ sourceKind: row.source_kind,
40
+ createdAt: row.created_at,
41
+ };
42
+ }
43
+ function rowToRelation(row) {
44
+ return {
45
+ id: row.id,
46
+ tenantId: row.tenant_id,
47
+ fromEntityId: row.from_entity_id,
48
+ toEntityId: row.to_entity_id,
49
+ relType: row.rel_type,
50
+ memoryId: row.memory_id,
51
+ sourceKind: row.source_kind,
52
+ createdAt: row.created_at,
53
+ };
54
+ }
55
+ function rowToQueueItem(row) {
56
+ return {
57
+ id: row.id,
58
+ tenantId: row.tenant_id,
59
+ memoryId: row.memory_id,
60
+ kind: row.kind,
61
+ status: row.status,
62
+ enqueuedAt: row.enqueued_at,
63
+ processedAt: row.processed_at,
64
+ };
65
+ }
66
+ const ENTITY_COLS = `id, tenant_id, entity_type, name, memory_id, source_kind, created_at`;
67
+ const RELATION_COLS = `id, tenant_id, from_entity_id, to_entity_id, rel_type, memory_id, source_kind, created_at`;
68
+ const QUEUE_COLS = `id, tenant_id, memory_id, kind, status, enqueued_at, processed_at`;
69
+ /**
70
+ * Look up a memory and assert it is a valid CONSOLIDATED source for the graph: it
71
+ * exists, belongs to `tenantId`, and is not raw. Returns its kind (the source_kind to
72
+ * store). Throws a clear error otherwise. This is the code-level mirror of the DB
73
+ * trigger guard (which is the unbypassable backstop).
74
+ */
75
+ function resolveConsolidatedSource(db, tenantId, memoryId, label) {
76
+ const row = db.prepare(`SELECT kind, tenant_id FROM memories WHERE id = ?`).get(memoryId);
77
+ if (!row) {
78
+ throw new Error(`${label}: source memory ${memoryId} not found`);
79
+ }
80
+ if (row.tenant_id !== tenantId) {
81
+ throw new Error(`${label}: source memory ${memoryId} belongs to another tenant`);
82
+ }
83
+ if (row.kind === 'raw') {
84
+ throw new Error(`${label}: source memory ${memoryId} is raw; the graph indexes consolidated state only`);
85
+ }
86
+ if (row.kind !== 'distilled' && row.kind !== 'superseded') {
87
+ throw new Error(`${label}: source memory ${memoryId} has unsupported kind '${row.kind}'`);
88
+ }
89
+ return row.kind;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Public API
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Insert a graph entity extracted from a consolidated memory. Throws if the source
96
+ * memory is missing / cross-tenant / raw (the DB trigger is the backstop).
97
+ */
98
+ export function insertEntity(hippoRoot, tenantId, opts) {
99
+ assertTenantId('insertEntity', tenantId);
100
+ if (!GRAPH_ENTITY_TYPES.has(opts.entityType)) {
101
+ throw new Error(`insertEntity: entityType must be one of ${Array.from(GRAPH_ENTITY_TYPES).join('|')}; got ${opts.entityType}`);
102
+ }
103
+ const name = (opts.name ?? '').trim();
104
+ if (name.length === 0)
105
+ throw new Error('insertEntity: name is required');
106
+ if (name.length > MAX_ENTITY_NAME_LEN) {
107
+ throw new Error(`insertEntity: name exceeds the ${MAX_ENTITY_NAME_LEN}-char cap`);
108
+ }
109
+ const now = new Date().toISOString();
110
+ const db = openHippoDb(hippoRoot);
111
+ try {
112
+ const sourceKind = resolveConsolidatedSource(db, tenantId, opts.memoryId, 'insertEntity');
113
+ const result = db.prepare(`
114
+ INSERT INTO entities(tenant_id, entity_type, name, memory_id, source_kind, created_at)
115
+ VALUES (?, ?, ?, ?, ?, ?)
116
+ `).run(tenantId, opts.entityType, name, opts.memoryId, sourceKind, now);
117
+ const id = Number(result.lastInsertRowid ?? 0);
118
+ const row = db.prepare(`SELECT ${ENTITY_COLS} FROM entities WHERE id = ?`).get(id);
119
+ if (!row)
120
+ throw new Error('insertEntity: failed to reload inserted entity');
121
+ return rowToEntity(row);
122
+ }
123
+ finally {
124
+ closeHippoDb(db);
125
+ }
126
+ }
127
+ /**
128
+ * Insert a graph relation between two entities, sourced from a consolidated memory.
129
+ * Both entities must exist in the same tenant; the source memory must be consolidated.
130
+ */
131
+ export function insertRelation(hippoRoot, tenantId, opts) {
132
+ assertTenantId('insertRelation', tenantId);
133
+ if (!GRAPH_RELATION_TYPES.has(opts.relType)) {
134
+ throw new Error(`insertRelation: relType must be one of ${Array.from(GRAPH_RELATION_TYPES).join('|')}; got ${opts.relType}`);
135
+ }
136
+ const now = new Date().toISOString();
137
+ const db = openHippoDb(hippoRoot);
138
+ try {
139
+ for (const [eid, role] of [[opts.fromEntityId, 'from'], [opts.toEntityId, 'to']]) {
140
+ const ent = db.prepare(`SELECT tenant_id FROM entities WHERE id = ?`).get(eid);
141
+ if (!ent)
142
+ throw new Error(`insertRelation: ${role}_entity ${eid} not found`);
143
+ if (ent.tenant_id !== tenantId) {
144
+ throw new Error(`insertRelation: ${role}_entity ${eid} belongs to another tenant (no cross-tenant edges)`);
145
+ }
146
+ }
147
+ const sourceKind = resolveConsolidatedSource(db, tenantId, opts.memoryId, 'insertRelation');
148
+ const result = db.prepare(`
149
+ INSERT INTO relations(tenant_id, from_entity_id, to_entity_id, rel_type, memory_id, source_kind, created_at)
150
+ VALUES (?, ?, ?, ?, ?, ?, ?)
151
+ `).run(tenantId, opts.fromEntityId, opts.toEntityId, opts.relType, opts.memoryId, sourceKind, now);
152
+ const id = Number(result.lastInsertRowid ?? 0);
153
+ const row = db.prepare(`SELECT ${RELATION_COLS} FROM relations WHERE id = ?`).get(id);
154
+ if (!row)
155
+ throw new Error('insertRelation: failed to reload inserted relation');
156
+ return rowToRelation(row);
157
+ }
158
+ finally {
159
+ closeHippoDb(db);
160
+ }
161
+ }
162
+ export function loadEntityById(hippoRoot, tenantId, id) {
163
+ assertTenantId('loadEntityById', tenantId);
164
+ const db = openHippoDb(hippoRoot);
165
+ try {
166
+ const row = db.prepare(`SELECT ${ENTITY_COLS} FROM entities WHERE id = ? AND tenant_id = ?`)
167
+ .get(id, tenantId);
168
+ return row ? rowToEntity(row) : null;
169
+ }
170
+ finally {
171
+ closeHippoDb(db);
172
+ }
173
+ }
174
+ export function loadEntities(hippoRoot, tenantId, opts = {}) {
175
+ assertTenantId('loadEntities', tenantId);
176
+ const limit = opts.limit ?? 100;
177
+ if (opts.entityType && !GRAPH_ENTITY_TYPES.has(opts.entityType)) {
178
+ throw new Error(`loadEntities: entityType must be one of ${Array.from(GRAPH_ENTITY_TYPES).join('|')}; got ${opts.entityType}`);
179
+ }
180
+ const db = openHippoDb(hippoRoot);
181
+ try {
182
+ const clauses = ['tenant_id = ?'];
183
+ const params = [tenantId];
184
+ if (opts.entityType) {
185
+ clauses.push('entity_type = ?');
186
+ params.push(opts.entityType);
187
+ }
188
+ params.push(limit);
189
+ const rows = db.prepare(`
190
+ SELECT ${ENTITY_COLS} FROM entities
191
+ WHERE ${clauses.join(' AND ')}
192
+ ORDER BY created_at DESC, id DESC
193
+ LIMIT ?
194
+ `).all(...params);
195
+ return rows.map(rowToEntity);
196
+ }
197
+ finally {
198
+ closeHippoDb(db);
199
+ }
200
+ }
201
+ export function loadRelations(hippoRoot, tenantId, opts = {}) {
202
+ assertTenantId('loadRelations', tenantId);
203
+ const limit = opts.limit ?? 100;
204
+ const db = openHippoDb(hippoRoot);
205
+ try {
206
+ const clauses = ['tenant_id = ?'];
207
+ const params = [tenantId];
208
+ if (opts.fromEntityId !== undefined) {
209
+ clauses.push('from_entity_id = ?');
210
+ params.push(opts.fromEntityId);
211
+ }
212
+ params.push(limit);
213
+ const rows = db.prepare(`
214
+ SELECT ${RELATION_COLS} FROM relations
215
+ WHERE ${clauses.join(' AND ')}
216
+ ORDER BY created_at DESC, id DESC
217
+ LIMIT ?
218
+ `).all(...params);
219
+ return rows.map(rowToRelation);
220
+ }
221
+ finally {
222
+ closeHippoDb(db);
223
+ }
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // E3.2 multi-hop recall read helpers (SELECT-only; the check-graph-writes lint
227
+ // permits these here and in the read-only consumer src/graph-recall.ts).
228
+ // ---------------------------------------------------------------------------
229
+ /** Chunk size for IN-list queries: well under SQLite's 999-bound-variable default
230
+ * (leaves headroom for the tenant_id param + the doubled list in neighbour lookups). */
231
+ const IN_LIST_CHUNK = 400;
232
+ /**
233
+ * Map consolidated source memory ids -> their graph entities. The SEED step of E3.2
234
+ * multi-hop recall (recall result memory ids -> entities to traverse from). Tenant-
235
+ * scoped, read-only; chunks the IN-list under the SQLite variable cap.
236
+ */
237
+ export function loadEntitiesByMemoryId(hippoRoot, tenantId, memoryIds) {
238
+ assertTenantId('loadEntitiesByMemoryId', tenantId);
239
+ if (memoryIds.length === 0)
240
+ return [];
241
+ const db = openHippoDb(hippoRoot);
242
+ try {
243
+ const out = [];
244
+ for (let i = 0; i < memoryIds.length; i += IN_LIST_CHUNK) {
245
+ const slice = memoryIds.slice(i, i + IN_LIST_CHUNK);
246
+ const ph = slice.map(() => '?').join(',');
247
+ const rows = db.prepare(`
248
+ SELECT ${ENTITY_COLS} FROM entities
249
+ WHERE tenant_id = ? AND memory_id IN (${ph})
250
+ `).all(tenantId, ...slice);
251
+ out.push(...rows.map(rowToEntity));
252
+ }
253
+ return out;
254
+ }
255
+ finally {
256
+ closeHippoDb(db);
257
+ }
258
+ }
259
+ /**
260
+ * Load entities by their primary ids. Resolves the entity rows reached during the BFS
261
+ * (whose `memory_id` maps back to a recall result). Tenant-scoped, read-only.
262
+ */
263
+ export function loadEntitiesByIds(hippoRoot, tenantId, ids) {
264
+ assertTenantId('loadEntitiesByIds', tenantId);
265
+ if (ids.length === 0)
266
+ return [];
267
+ const db = openHippoDb(hippoRoot);
268
+ try {
269
+ const out = [];
270
+ for (let i = 0; i < ids.length; i += IN_LIST_CHUNK) {
271
+ const slice = ids.slice(i, i + IN_LIST_CHUNK);
272
+ const ph = slice.map(() => '?').join(',');
273
+ const rows = db.prepare(`
274
+ SELECT ${ENTITY_COLS} FROM entities
275
+ WHERE tenant_id = ? AND id IN (${ph})
276
+ `).all(tenantId, ...slice);
277
+ out.push(...rows.map(rowToEntity));
278
+ }
279
+ return out;
280
+ }
281
+ finally {
282
+ closeHippoDb(db);
283
+ }
284
+ }
285
+ /**
286
+ * All relations touching ANY of `entityIds` in EITHER direction (from OR to) — the
287
+ * per-hop neighbour query for E3.2 multi-hop traversal. ONE query for the whole frontier
288
+ * (not one per node): this is the bidirectional read `loadRelations` (from-only) lacks,
289
+ * and avoids an N+1 across BFS frontier nodes. `limit` caps rows for the frontier and
290
+ * must be a non-negative integer (the raw `LIMIT ?` rejects a fractional value).
291
+ */
292
+ export function loadNeighborRelations(hippoRoot, tenantId, entityIds, opts = {}) {
293
+ assertTenantId('loadNeighborRelations', tenantId);
294
+ if (entityIds.length === 0)
295
+ return [];
296
+ const limit = opts.limit ?? 1000;
297
+ if (!Number.isInteger(limit) || limit < 0) {
298
+ throw new Error(`loadNeighborRelations: limit must be a non-negative integer; got ${limit}`);
299
+ }
300
+ const db = openHippoDb(hippoRoot);
301
+ try {
302
+ // `limit` is applied PER CHUNK; a frontier spanning >IN_LIST_CHUNK ids could return
303
+ // up to limit*chunks rows before the by-id dedup below. Harmless for E3.2 (the
304
+ // frontier is bounded by maxNeighbors <= 200 << IN_LIST_CHUNK, so a single chunk,
305
+ // and the BFS re-enforces the per-hop fanout cap), but note the semantics if a
306
+ // tighter total cap is ever needed.
307
+ const byId = new Map();
308
+ for (let i = 0; i < entityIds.length; i += IN_LIST_CHUNK) {
309
+ const slice = entityIds.slice(i, i + IN_LIST_CHUNK);
310
+ const ph = slice.map(() => '?').join(',');
311
+ const rows = db.prepare(`
312
+ SELECT ${RELATION_COLS} FROM relations
313
+ WHERE tenant_id = ? AND (from_entity_id IN (${ph}) OR to_entity_id IN (${ph}))
314
+ ORDER BY created_at DESC, id DESC
315
+ LIMIT ?
316
+ `).all(tenantId, ...slice, ...slice, limit);
317
+ for (const r of rows)
318
+ byId.set(r.id, rowToRelation(r));
319
+ }
320
+ return Array.from(byId.values());
321
+ }
322
+ finally {
323
+ closeHippoDb(db);
324
+ }
325
+ }
326
+ // ---------------------------------------------------------------------------
327
+ // Extraction queue (the interface the deferred sleep enqueue-hook + E3.1 will use)
328
+ // ---------------------------------------------------------------------------
329
+ /**
330
+ * Enqueue a consolidated memory for later graph extraction. Rejects a raw / missing /
331
+ * cross-tenant memory (the DB trigger is the backstop). The producer hook in
332
+ * `hippo sleep` is deferred (E3.1); this is the API it will call.
333
+ */
334
+ export function enqueueExtraction(hippoRoot, tenantId, memoryId) {
335
+ assertTenantId('enqueueExtraction', tenantId);
336
+ const now = new Date().toISOString();
337
+ const db = openHippoDb(hippoRoot);
338
+ try {
339
+ const kind = resolveConsolidatedSource(db, tenantId, memoryId, 'enqueueExtraction');
340
+ const result = db.prepare(`
341
+ INSERT INTO graph_extraction_queue(tenant_id, memory_id, kind, status, enqueued_at, processed_at)
342
+ VALUES (?, ?, ?, 'pending', ?, NULL)
343
+ `).run(tenantId, memoryId, kind, now);
344
+ const id = Number(result.lastInsertRowid ?? 0);
345
+ const row = db.prepare(`SELECT ${QUEUE_COLS} FROM graph_extraction_queue WHERE id = ?`).get(id);
346
+ if (!row)
347
+ throw new Error('enqueueExtraction: failed to reload queue item');
348
+ return rowToQueueItem(row);
349
+ }
350
+ finally {
351
+ closeHippoDb(db);
352
+ }
353
+ }
354
+ export function loadExtractionQueue(hippoRoot, tenantId, opts = {}) {
355
+ assertTenantId('loadExtractionQueue', tenantId);
356
+ const limit = opts.limit ?? 100;
357
+ if (opts.status && !VALID_QUEUE_STATES.has(opts.status)) {
358
+ throw new Error(`loadExtractionQueue: status must be one of ${Array.from(VALID_QUEUE_STATES).join('|')}; got ${opts.status}`);
359
+ }
360
+ const db = openHippoDb(hippoRoot);
361
+ try {
362
+ const clauses = ['tenant_id = ?'];
363
+ const params = [tenantId];
364
+ if (opts.status) {
365
+ clauses.push('status = ?');
366
+ params.push(opts.status);
367
+ }
368
+ params.push(limit);
369
+ const rows = db.prepare(`
370
+ SELECT ${QUEUE_COLS} FROM graph_extraction_queue
371
+ WHERE ${clauses.join(' AND ')}
372
+ ORDER BY enqueued_at ASC, id ASC
373
+ LIMIT ?
374
+ `).all(...params);
375
+ return rows.map(rowToQueueItem);
376
+ }
377
+ finally {
378
+ closeHippoDb(db);
379
+ }
380
+ }
381
+ /**
382
+ * Mark a queue item terminal (processed | skipped). Only `status`/`processed_at`
383
+ * change, so the consolidated-source guard trigger (which fires on
384
+ * memory_id/kind/tenant_id changes) is not involved. CAS on the current status to a
385
+ * non-terminal 'pending'.
386
+ */
387
+ export function markExtractionProcessed(hippoRoot, tenantId, id, status = 'processed') {
388
+ assertTenantId('markExtractionProcessed', tenantId);
389
+ const now = new Date().toISOString();
390
+ const db = openHippoDb(hippoRoot);
391
+ try {
392
+ const updated = db.prepare(`
393
+ UPDATE graph_extraction_queue
394
+ SET status = ?, processed_at = ?
395
+ WHERE id = ? AND tenant_id = ? AND status = 'pending'
396
+ `).run(status, now, id, tenantId);
397
+ if (updated.changes === 0) {
398
+ const existing = db.prepare(`SELECT status FROM graph_extraction_queue WHERE id = ? AND tenant_id = ?`)
399
+ .get(id, tenantId);
400
+ if (!existing)
401
+ throw new Error(`markExtractionProcessed: queue item ${id} not found for tenant ${tenantId}`);
402
+ throw new Error(`markExtractionProcessed: queue item ${id} is not pending (status='${existing.status}')`);
403
+ }
404
+ const row = db.prepare(`SELECT ${QUEUE_COLS} FROM graph_extraction_queue WHERE id = ? AND tenant_id = ?`)
405
+ .get(id, tenantId);
406
+ if (!row)
407
+ throw new Error(`markExtractionProcessed: queue item ${id} not found after UPDATE`);
408
+ return rowToQueueItem(row);
409
+ }
410
+ finally {
411
+ closeHippoDb(db);
412
+ }
413
+ }
414
+ /**
415
+ * Delete ALL entities for a tenant (relations cascade via the from/to FKs). Returns
416
+ * the number of entities deleted. The rebuild primitive for graph extraction: the
417
+ * deterministic graph is a pure derived function of the consolidated objects, so an
418
+ * extract clears then re-derives. Lives in graph.ts (the sole sanctioned graph
419
+ * writer), so the E3.3 CI lint permits this `DELETE FROM entities`. Does NOT touch
420
+ * graph_extraction_queue (the enqueue-hook's domain).
421
+ */
422
+ export function clearGraph(hippoRoot, tenantId) {
423
+ assertTenantId('clearGraph', tenantId);
424
+ const db = openHippoDb(hippoRoot);
425
+ try {
426
+ const res = db.prepare(`DELETE FROM entities WHERE tenant_id = ?`).run(tenantId);
427
+ return Number(res.changes ?? 0);
428
+ }
429
+ finally {
430
+ closeHippoDb(db);
431
+ }
432
+ }
433
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * E2 incident first-class object (docs/plans/2026-05-29-e2-incident-object.md).
3
+ *
4
+ * An incident is a postmortem capsule: a recorded operational event with a
5
+ * lifecycle and optional linked receipts (the memories that are its evidence).
6
+ * The `incidents` table is the source of truth: an incident stays `open`
7
+ * regardless of memory decay. A memory row still mirrors the incident for
8
+ * recall surfaces but is NOT canonical — memory_id is NULLABLE with ON DELETE
9
+ * SET NULL so forget/consolidate/archive gracefully orphans the incident row.
10
+ *
11
+ * Lifecycle: open -> resolved (a resolution was recorded; the incident stays on
12
+ * record with resolution_text + resolved_at) or open|resolved -> closed
13
+ * (retired with closed_at). This is NOT decision's supersede: there is no
14
+ * superseded_by self-FK, no supersede CAS, and no supersede trigger.
15
+ *
16
+ * Tenant scoping: every helper requires tenantId. BEFORE INSERT/UPDATE triggers
17
+ * enforce incidents.tenant_id == the referenced memory's tenant_id. Mirrors the
18
+ * v30 decisions pattern (src/decisions.ts).
19
+ *
20
+ * Dual-write atomicity: `saveIncident` writes the memory + incidents row inside
21
+ * writeEntry's SAVEPOINT 'write_entry' (store.ts) via the afterWrite hook, so a
22
+ * failure in any step rolls all of them back. Pattern matches saveDecision.
23
+ *
24
+ * linked_memory_ids ("linked receipts"): a JSON-encoded array of memory ids on
25
+ * the row, default `[]`. On save, every id must exist in the SAME tenant; a
26
+ * cross-tenant or nonexistent id is rejected (throw) before the insert.
27
+ */
28
+ export type IncidentStatus = 'open' | 'resolved' | 'closed';
29
+ export declare const VALID_INCIDENT_STATES: ReadonlySet<IncidentStatus>;
30
+ export interface Incident {
31
+ id: number;
32
+ /** Nullable: ON DELETE SET NULL lets memory deletion (forget / consolidate /
33
+ * archive) proceed without breaking the incident row. */
34
+ memoryId: string | null;
35
+ tenantId: string;
36
+ incidentText: string;
37
+ context: string | null;
38
+ status: IncidentStatus;
39
+ /** Set only when status === 'resolved'. */
40
+ resolutionText: string | null;
41
+ resolvedAt: string | null;
42
+ closedAt: string | null;
43
+ /** Linked receipts: memory ids that are this incident's evidence. */
44
+ linkedMemoryIds: string[];
45
+ createdAt: string;
46
+ }
47
+ export interface SaveIncidentOpts {
48
+ incidentText: string;
49
+ context?: string;
50
+ /** Memory ids (linked receipts) that are this incident's evidence. Each must
51
+ * exist in the same tenant; cross-tenant/nonexistent ids are rejected. */
52
+ linkedMemoryIds?: string[];
53
+ /** Extra memory tags merged after ['incident'] (the CLI passes path-context
54
+ * tags; HTTP/SDK pass none). */
55
+ extraTags?: string[];
56
+ }
57
+ export interface ListIncidentsOpts {
58
+ status?: IncidentStatus;
59
+ limit?: number;
60
+ }
61
+ /**
62
+ * Create an incident. Writes the memory mirror + the incidents row atomically
63
+ * inside writeEntry's SAVEPOINT 'write_entry'.
64
+ *
65
+ * The memory mirror: tags ['incident', ...extraTags], source 'incident',
66
+ * confidence 'verified', half_life INCIDENT_HALF_LIFE_DAYS, content =
67
+ * "<text>\n\nContext: <context>" when context is given.
68
+ *
69
+ * linked_memory_ids are validated BEFORE insert: each must exist in the SAME
70
+ * tenant. A cross-tenant or nonexistent id throws and rolls back the whole
71
+ * write. The validated ids are stored as JSON.stringify(validated).
72
+ */
73
+ export declare function saveIncident(hippoRoot: string, tenantId: string, opts: SaveIncidentOpts, actor?: string): Incident;
74
+ /**
75
+ * Resolve an open incident (open -> resolved). Records resolution_text +
76
+ * resolved_at; the incident stays on record. CAS guard: WHERE status='open';
77
+ * 0 changes distinguishes not-found from not-open so callers surface the right
78
+ * error. Emits incident_resolve.
79
+ */
80
+ export declare function resolveIncident(hippoRoot: string, tenantId: string, id: number, resolutionText: string, actor?: string): Incident;
81
+ /**
82
+ * Close (retire) an incident from open or resolved (open|resolved -> closed).
83
+ * Updates closed_at only; the memory mirror is not mutated. CAS guard: WHERE
84
+ * status IN ('open','resolved'); 0 changes distinguishes not-found from
85
+ * wrong-state. Emits incident_close.
86
+ */
87
+ export declare function closeIncident(hippoRoot: string, tenantId: string, id: number, actor?: string): Incident;
88
+ export declare function loadIncidentById(hippoRoot: string, tenantId: string, id: number): Incident | null;
89
+ export declare function loadIncidents(hippoRoot: string, tenantId: string, opts?: ListIncidentsOpts): Incident[];
90
+ export declare function loadOpenIncidents(hippoRoot: string, tenantId: string, opts?: {
91
+ limit?: number;
92
+ }): Incident[];
93
+ /**
94
+ * Resolve a memory id to the table id of the OPEN incident backed by that
95
+ * memory, or null when the memory has no open incident row. Extracted so a
96
+ * memory-id-based lookup is unit-testable at the store layer (mirror of
97
+ * resolveActiveDecisionIdByMemory).
98
+ */
99
+ export declare function resolveActiveIncidentIdByMemory(hippoRoot: string, tenantId: string, memoryId: string): number | null;
100
+ //# sourceMappingURL=incidents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"incidents.d.ts","sourceRoot":"","sources":["../src/incidents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE5D,eAAO,MAAM,qBAAqB,EAAE,WAAW,CAAC,cAAc,CAI5D,CAAC;AAEH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX;8DAC0D;IAC1D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qEAAqE;IACrE,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;+EAC2E;IAC3E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;qCACiC;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyDD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,gBAAgB,EACtB,KAAK,GAAE,MAAc,GACpB,QAAQ,CAmFV;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,EACtB,KAAK,GAAE,MAAc,GACpB,QAAQ,CAqDV;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,EACV,KAAK,GAAE,MAAc,GACpB,QAAQ,CAkDV;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,QAAQ,GAAG,IAAI,CAUjB;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,iBAAsB,GAC3B,QAAQ,EAAE,CA8BZ;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,QAAQ,EAAE,CAEZ;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAWf"}