@siduri-x/core 1.0.9 → 2.0.1

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.
@@ -0,0 +1,687 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SiduriDatabase = void 0;
4
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
5
+ const crypto = require('crypto');
6
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
7
+ const { DatabaseSync } = require('node:sqlite');
8
+ class SiduriDatabase {
9
+ db;
10
+ constructor(options = {}) {
11
+ const dbPath = options.dbPath || ':memory:';
12
+ this.db = new DatabaseSync(dbPath);
13
+ this.db.exec('PRAGMA journal_mode = WAL');
14
+ this.initSchema();
15
+ }
16
+ initSchema() {
17
+ const schema = `
18
+ -- Self Tables
19
+ CREATE TABLE IF NOT EXISTS self_identity (
20
+ companion_id TEXT PRIMARY KEY,
21
+ name TEXT NOT NULL,
22
+ archetype TEXT,
23
+ version TEXT NOT NULL,
24
+ updated_at TEXT DEFAULT (datetime('now'))
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS self_personality (
28
+ companion_id TEXT PRIMARY KEY,
29
+ warmth REAL DEFAULT 0.5,
30
+ formality REAL DEFAULT 0.5,
31
+ sarcasm REAL DEFAULT 0.5,
32
+ verbosity REAL DEFAULT 0.5,
33
+ curiosity REAL DEFAULT 0.5,
34
+ updated_at TEXT DEFAULT (datetime('now'))
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS self_directives (
38
+ id TEXT PRIMARY KEY,
39
+ companion_id TEXT NOT NULL,
40
+ priority INTEGER DEFAULT 50,
41
+ directive TEXT NOT NULL,
42
+ status TEXT DEFAULT 'ACTIVE',
43
+ category TEXT DEFAULT 'behavioral',
44
+ supersedes_id TEXT,
45
+ created_at TEXT DEFAULT (datetime('now'))
46
+ );
47
+
48
+ CREATE TABLE IF NOT EXISTS self_relationships (
49
+ companion_id TEXT NOT NULL,
50
+ entity_id TEXT NOT NULL,
51
+ entity_type TEXT NOT NULL,
52
+ trust_score REAL DEFAULT 0.5,
53
+ familiarity REAL DEFAULT 0.5,
54
+ interaction_conventions TEXT,
55
+ PRIMARY KEY(companion_id, entity_id)
56
+ );
57
+
58
+ -- Knowledge Tables
59
+ CREATE TABLE IF NOT EXISTS life_inventory (
60
+ id TEXT PRIMARY KEY,
61
+ companion_id TEXT NOT NULL,
62
+ domain TEXT NOT NULL,
63
+ entity_name TEXT NOT NULL,
64
+ properties TEXT NOT NULL,
65
+ updated_at TEXT DEFAULT (datetime('now'))
66
+ );
67
+
68
+ CREATE TABLE IF NOT EXISTS life_finance (
69
+ id TEXT PRIMARY KEY,
70
+ companion_id TEXT NOT NULL,
71
+ category TEXT NOT NULL,
72
+ amount REAL NOT NULL,
73
+ currency TEXT DEFAULT 'USD',
74
+ timestamp TEXT DEFAULT (datetime('now')),
75
+ metadata TEXT
76
+ );
77
+
78
+ CREATE TABLE IF NOT EXISTS life_schedule (
79
+ id TEXT PRIMARY KEY,
80
+ companion_id TEXT NOT NULL,
81
+ title TEXT NOT NULL,
82
+ start_time TEXT NOT NULL,
83
+ end_time TEXT,
84
+ is_recurring INTEGER DEFAULT 0,
85
+ status TEXT DEFAULT 'active'
86
+ );
87
+
88
+ CREATE TABLE IF NOT EXISTS life_preferences (
89
+ id TEXT PRIMARY KEY,
90
+ companion_id TEXT NOT NULL,
91
+ preference_key TEXT NOT NULL,
92
+ preference_value TEXT NOT NULL,
93
+ category TEXT NOT NULL,
94
+ updated_at TEXT DEFAULT (datetime('now'))
95
+ );
96
+
97
+ -- Memory Tables
98
+ CREATE TABLE IF NOT EXISTS memory_events (
99
+ id TEXT PRIMARY KEY,
100
+ companion_id TEXT NOT NULL,
101
+ source_type TEXT NOT NULL,
102
+ occurred_at TEXT DEFAULT (datetime('now')),
103
+ payload TEXT NOT NULL
104
+ );
105
+
106
+ CREATE TABLE IF NOT EXISTS memory_claims (
107
+ id TEXT PRIMARY KEY,
108
+ companion_id TEXT NOT NULL,
109
+ subject TEXT NOT NULL,
110
+ predicate TEXT NOT NULL,
111
+ value TEXT NOT NULL,
112
+ status TEXT DEFAULT 'PENDING',
113
+ confidence REAL DEFAULT 1.0,
114
+ valid_from TEXT,
115
+ valid_until TEXT,
116
+ evidence TEXT,
117
+ asserted_at TEXT DEFAULT (datetime('now')),
118
+ supersedes TEXT,
119
+ source_event_id TEXT
120
+ );
121
+
122
+ -- FTS5 Virtual Table for Memory Claims
123
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_search USING fts5(
124
+ subject,
125
+ predicate,
126
+ value,
127
+ content='memory_claims',
128
+ content_rowid='rowid'
129
+ );
130
+
131
+ -- FTS5 Triggers
132
+ CREATE TRIGGER IF NOT EXISTS memory_claims_ai AFTER INSERT ON memory_claims BEGIN
133
+ INSERT INTO memory_search(rowid, subject, predicate, value)
134
+ VALUES (new.rowid, new.subject, new.predicate, new.value);
135
+ END;
136
+
137
+ CREATE TRIGGER IF NOT EXISTS memory_claims_ad AFTER DELETE ON memory_claims BEGIN
138
+ INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
139
+ VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
140
+ END;
141
+
142
+ CREATE TRIGGER IF NOT EXISTS memory_claims_au AFTER UPDATE ON memory_claims BEGIN
143
+ INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
144
+ VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
145
+ INSERT INTO memory_search(rowid, subject, predicate, value)
146
+ VALUES (new.rowid, new.subject, new.predicate, new.value);
147
+ END;
148
+ `;
149
+ this.db.exec(schema);
150
+ try {
151
+ this.db.exec("ALTER TABLE memory_claims ADD COLUMN supersedes TEXT");
152
+ }
153
+ catch {
154
+ // Column already exists
155
+ }
156
+ try {
157
+ this.db.exec("ALTER TABLE memory_claims ADD COLUMN source_event_id TEXT");
158
+ }
159
+ catch {
160
+ // Column already exists
161
+ }
162
+ }
163
+ close() {
164
+ this.db.close();
165
+ }
166
+ // ==========================================
167
+ // Self Domain Methods
168
+ // ==========================================
169
+ getIdentity(companionId) {
170
+ const stmt = this.db.prepare('SELECT * FROM self_identity WHERE companion_id = ?');
171
+ const row = stmt.get(companionId);
172
+ if (!row)
173
+ return undefined;
174
+ return {
175
+ companionId: row.companion_id,
176
+ name: row.name,
177
+ archetype: row.archetype || undefined,
178
+ version: row.version,
179
+ updatedAt: row.updated_at
180
+ };
181
+ }
182
+ setIdentity(identity) {
183
+ const stmt = this.db.prepare(`
184
+ INSERT INTO self_identity (companion_id, name, archetype, version, updated_at)
185
+ VALUES (?, ?, ?, ?, datetime('now'))
186
+ ON CONFLICT(companion_id) DO UPDATE SET
187
+ name = excluded.name,
188
+ archetype = excluded.archetype,
189
+ version = excluded.version,
190
+ updated_at = datetime('now')
191
+ `);
192
+ stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.version);
193
+ }
194
+ getPersonality(companionId) {
195
+ const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
196
+ const row = stmt.get(companionId);
197
+ if (!row)
198
+ return undefined;
199
+ return {
200
+ warmth: row.warmth,
201
+ formality: row.formality,
202
+ sarcasm: row.sarcasm,
203
+ verbosity: row.verbosity,
204
+ curiosity: row.curiosity
205
+ };
206
+ }
207
+ setPersonality(companionId, traits) {
208
+ const stmt = this.db.prepare(`
209
+ INSERT INTO self_personality (companion_id, warmth, formality, sarcasm, verbosity, curiosity, updated_at)
210
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
211
+ ON CONFLICT(companion_id) DO UPDATE SET
212
+ warmth = excluded.warmth,
213
+ formality = excluded.formality,
214
+ sarcasm = excluded.sarcasm,
215
+ verbosity = excluded.verbosity,
216
+ curiosity = excluded.curiosity,
217
+ updated_at = datetime('now')
218
+ `);
219
+ stmt.run(companionId, traits.warmth, traits.formality, traits.sarcasm, traits.verbosity, traits.curiosity);
220
+ }
221
+ getActiveDirectives(companionId) {
222
+ const stmt = this.db.prepare(`
223
+ SELECT * FROM self_directives
224
+ WHERE companion_id = ? AND status = 'ACTIVE'
225
+ ORDER BY priority DESC, created_at ASC
226
+ `);
227
+ return stmt.all(companionId).map((row) => ({
228
+ id: row.id,
229
+ companionId: row.companion_id,
230
+ priority: row.priority,
231
+ directive: row.directive,
232
+ status: row.status,
233
+ category: row.category,
234
+ supersedesId: row.supersedes_id || undefined,
235
+ createdAt: row.created_at
236
+ }));
237
+ }
238
+ commitDirective(directive) {
239
+ const stmt = this.db.prepare(`
240
+ INSERT INTO self_directives (id, companion_id, priority, directive, status, category, supersedes_id, created_at)
241
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
242
+ `);
243
+ stmt.run(directive.id, directive.companionId, directive.priority, directive.directive, directive.status, directive.category, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
244
+ }
245
+ getDirective(id, companionId) {
246
+ const stmt = companionId
247
+ ? this.db.prepare('SELECT * FROM self_directives WHERE id = ? AND companion_id = ?')
248
+ : this.db.prepare('SELECT * FROM self_directives WHERE id = ?');
249
+ const row = (companionId ? stmt.get(id, companionId) : stmt.get(id));
250
+ if (!row)
251
+ return undefined;
252
+ return {
253
+ id: row.id,
254
+ companionId: row.companion_id,
255
+ priority: row.priority,
256
+ directive: row.directive,
257
+ status: row.status,
258
+ category: row.category,
259
+ supersedesId: row.supersedes_id || undefined,
260
+ createdAt: row.created_at,
261
+ };
262
+ }
263
+ approveDirective(id, companionId) {
264
+ const findStmt = companionId
265
+ ? this.db.prepare("SELECT id, companion_id, status, supersedes_id FROM self_directives WHERE id = ? AND companion_id = ?")
266
+ : this.db.prepare("SELECT id, companion_id, status, supersedes_id FROM self_directives WHERE id = ?");
267
+ const row = (companionId ? findStmt.get(id, companionId) : findStmt.get(id));
268
+ if (!row) {
269
+ return;
270
+ }
271
+ if (row.status !== 'PENDING') {
272
+ throw new Error(`Cannot approve directive '${id}': invalid transition from status '${row.status}' to 'ACTIVE' (only PENDING directives can be approved)`);
273
+ }
274
+ // If this directive supersedes an earlier directive, transition that prior directive to SUPERSEDED
275
+ if (row.supersedes_id) {
276
+ const supersededId = row.supersedes_id;
277
+ const effectiveCompanionId = companionId || row.companion_id;
278
+ if (effectiveCompanionId) {
279
+ const supersedeStmt = this.db.prepare("UPDATE self_directives SET status = 'SUPERSEDED' WHERE id = ? AND companion_id = ?");
280
+ supersedeStmt.run(supersededId, effectiveCompanionId);
281
+ }
282
+ else {
283
+ const supersedeStmt = this.db.prepare("UPDATE self_directives SET status = 'SUPERSEDED' WHERE id = ?");
284
+ supersedeStmt.run(supersededId);
285
+ }
286
+ }
287
+ if (companionId) {
288
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'ACTIVE' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
289
+ stmt.run(id, companionId);
290
+ }
291
+ else {
292
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'ACTIVE' WHERE id = ? AND status = 'PENDING'");
293
+ stmt.run(id);
294
+ }
295
+ }
296
+ rejectDirective(id, companionId) {
297
+ const findStmt = companionId
298
+ ? this.db.prepare("SELECT id, companion_id, status FROM self_directives WHERE id = ? AND companion_id = ?")
299
+ : this.db.prepare("SELECT id, companion_id, status FROM self_directives WHERE id = ?");
300
+ const row = (companionId ? findStmt.get(id, companionId) : findStmt.get(id));
301
+ if (!row) {
302
+ return;
303
+ }
304
+ if (row.status !== 'PENDING') {
305
+ throw new Error(`Cannot reject directive '${id}': invalid transition from status '${row.status}' to 'REJECTED' (only PENDING directives can be rejected)`);
306
+ }
307
+ if (companionId) {
308
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'REJECTED' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
309
+ stmt.run(id, companionId);
310
+ }
311
+ else {
312
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'REJECTED' WHERE id = ? AND status = 'PENDING'");
313
+ stmt.run(id);
314
+ }
315
+ }
316
+ revokeDirective(id, companionId) {
317
+ if (companionId) {
318
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'REVOKED' WHERE id = ? AND companion_id = ?");
319
+ stmt.run(id, companionId);
320
+ }
321
+ else {
322
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'REVOKED' WHERE id = ?");
323
+ stmt.run(id);
324
+ }
325
+ }
326
+ expireDirective(id, companionId) {
327
+ if (companionId) {
328
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'EXPIRED' WHERE id = ? AND companion_id = ?");
329
+ stmt.run(id, companionId);
330
+ }
331
+ else {
332
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'EXPIRED' WHERE id = ?");
333
+ stmt.run(id);
334
+ }
335
+ }
336
+ disableDirective(id, companionId) {
337
+ if (companionId) {
338
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'DISABLED' WHERE id = ? AND companion_id = ?");
339
+ stmt.run(id, companionId);
340
+ }
341
+ else {
342
+ const stmt = this.db.prepare("UPDATE self_directives SET status = 'DISABLED' WHERE id = ?");
343
+ stmt.run(id);
344
+ }
345
+ }
346
+ getRelationship(companionId, entityId) {
347
+ const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?');
348
+ const row = stmt.get(companionId, entityId);
349
+ if (!row)
350
+ return undefined;
351
+ return {
352
+ companionId: row.companion_id,
353
+ entityId: row.entity_id,
354
+ entityType: row.entity_type,
355
+ trustScore: row.trust_score,
356
+ familiarity: row.familiarity,
357
+ interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : []
358
+ };
359
+ }
360
+ upsertRelationship(rel) {
361
+ const stmt = this.db.prepare(`
362
+ INSERT INTO self_relationships (companion_id, entity_id, entity_type, trust_score, familiarity, interaction_conventions)
363
+ VALUES (?, ?, ?, ?, ?, ?)
364
+ ON CONFLICT(companion_id, entity_id) DO UPDATE SET
365
+ entity_type = excluded.entity_type,
366
+ trust_score = excluded.trust_score,
367
+ familiarity = excluded.familiarity,
368
+ interaction_conventions = excluded.interaction_conventions
369
+ `);
370
+ stmt.run(rel.companionId, rel.entityId, rel.entityType, rel.trustScore, rel.familiarity, JSON.stringify(rel.interactionConventions || []));
371
+ }
372
+ // ==========================================
373
+ // Knowledge / Life DB Methods
374
+ // ==========================================
375
+ getInventory(companionId, domain) {
376
+ let stmt;
377
+ let rows;
378
+ if (domain) {
379
+ stmt = this.db.prepare('SELECT * FROM life_inventory WHERE companion_id = ? AND domain = ?');
380
+ rows = stmt.all(companionId, domain);
381
+ }
382
+ else {
383
+ stmt = this.db.prepare('SELECT * FROM life_inventory WHERE companion_id = ?');
384
+ rows = stmt.all(companionId);
385
+ }
386
+ return rows.map((row) => ({
387
+ id: row.id,
388
+ companionId: row.companion_id,
389
+ domain: row.domain,
390
+ entityName: row.entity_name,
391
+ properties: JSON.parse(row.properties),
392
+ updatedAt: row.updated_at
393
+ }));
394
+ }
395
+ upsertInventoryItem(item) {
396
+ const stmt = this.db.prepare(`
397
+ INSERT INTO life_inventory (id, companion_id, domain, entity_name, properties, updated_at)
398
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
399
+ ON CONFLICT(id) DO UPDATE SET
400
+ domain = excluded.domain,
401
+ entity_name = excluded.entity_name,
402
+ properties = excluded.properties,
403
+ updated_at = datetime('now')
404
+ `);
405
+ stmt.run(item.id, item.companionId, item.domain, item.entityName, JSON.stringify(item.properties));
406
+ }
407
+ getFinanceEntries(companionId, limit = 50) {
408
+ const stmt = this.db.prepare('SELECT * FROM life_finance WHERE companion_id = ? ORDER BY timestamp DESC LIMIT ?');
409
+ return stmt.all(companionId, limit).map((row) => ({
410
+ id: row.id,
411
+ companionId: row.companion_id,
412
+ category: row.category,
413
+ amount: row.amount,
414
+ currency: row.currency,
415
+ timestamp: row.timestamp,
416
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
417
+ }));
418
+ }
419
+ addFinanceEntry(entry) {
420
+ const stmt = this.db.prepare(`
421
+ INSERT INTO life_finance (id, companion_id, category, amount, currency, timestamp, metadata)
422
+ VALUES (?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?)
423
+ `);
424
+ stmt.run(entry.id, entry.companionId, entry.category, entry.amount, entry.currency || 'USD', entry.timestamp || null, entry.metadata ? JSON.stringify(entry.metadata) : null);
425
+ }
426
+ getSchedule(companionId) {
427
+ const stmt = this.db.prepare('SELECT * FROM life_schedule WHERE companion_id = ? AND status = ? ORDER BY start_time ASC');
428
+ return stmt.all(companionId, 'active').map((row) => ({
429
+ id: row.id,
430
+ companionId: row.companion_id,
431
+ title: row.title,
432
+ startTime: row.start_time,
433
+ endTime: row.end_time || undefined,
434
+ isRecurring: row.is_recurring === 1,
435
+ status: row.status
436
+ }));
437
+ }
438
+ upsertScheduleItem(item) {
439
+ const stmt = this.db.prepare(`
440
+ INSERT INTO life_schedule (id, companion_id, title, start_time, end_time, is_recurring, status)
441
+ VALUES (?, ?, ?, ?, ?, ?, ?)
442
+ ON CONFLICT(id) DO UPDATE SET
443
+ title = excluded.title,
444
+ start_time = excluded.start_time,
445
+ end_time = excluded.end_time,
446
+ is_recurring = excluded.is_recurring,
447
+ status = excluded.status
448
+ `);
449
+ stmt.run(item.id, item.companionId, item.title, item.startTime, item.endTime || null, item.isRecurring ? 1 : 0, item.status || 'active');
450
+ }
451
+ getPreferences(companionId) {
452
+ const stmt = this.db.prepare('SELECT * FROM life_preferences WHERE companion_id = ?');
453
+ return stmt.all(companionId).map((row) => ({
454
+ id: row.id,
455
+ companionId: row.companion_id,
456
+ preferenceKey: row.preference_key,
457
+ preferenceValue: row.preference_value,
458
+ category: row.category,
459
+ updatedAt: row.updated_at
460
+ }));
461
+ }
462
+ upsertPreference(pref) {
463
+ const stmt = this.db.prepare(`
464
+ INSERT INTO life_preferences (id, companion_id, preference_key, preference_value, category, updated_at)
465
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
466
+ ON CONFLICT(id) DO UPDATE SET
467
+ preference_key = excluded.preference_key,
468
+ preference_value = excluded.preference_value,
469
+ category = excluded.category,
470
+ updated_at = datetime('now')
471
+ `);
472
+ stmt.run(pref.id, pref.companionId, pref.preferenceKey, pref.preferenceValue, pref.category);
473
+ }
474
+ // ==========================================
475
+ // Memory Domain Methods
476
+ // ==========================================
477
+ recordEvent(event) {
478
+ const stmt = this.db.prepare(`
479
+ INSERT INTO memory_events (id, companion_id, source_type, occurred_at, payload)
480
+ VALUES (?, ?, ?, coalesce(?, datetime('now')), ?)
481
+ `);
482
+ stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, JSON.stringify(event.payload));
483
+ }
484
+ getRecentEvents(companionId, limit = 50) {
485
+ const stmt = this.db.prepare('SELECT * FROM memory_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
486
+ return stmt.all(companionId, limit).map((row) => ({
487
+ id: row.id,
488
+ companionId: row.companion_id,
489
+ sourceType: row.source_type,
490
+ occurredAt: row.occurred_at,
491
+ payload: JSON.parse(row.payload)
492
+ }));
493
+ }
494
+ getEvent(id) {
495
+ const stmt = this.db.prepare('SELECT * FROM memory_events WHERE id = ?');
496
+ const row = stmt.get(id);
497
+ if (!row)
498
+ return undefined;
499
+ return {
500
+ id: row.id,
501
+ companionId: row.companion_id,
502
+ sourceType: row.source_type,
503
+ occurredAt: row.occurred_at,
504
+ payload: JSON.parse(row.payload)
505
+ };
506
+ }
507
+ proposeClaim(claim) {
508
+ const id = claim.id || crypto.randomUUID();
509
+ const status = 'PENDING';
510
+ const confidence = claim.confidence ?? 1.0;
511
+ const assertedAt = claim.assertedAt || new Date().toISOString();
512
+ const stmt = this.db.prepare(`
513
+ INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at, supersedes, source_event_id)
514
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?, ?)
515
+ `);
516
+ stmt.run(id, claim.companionId, claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
517
+ return {
518
+ ...claim,
519
+ id,
520
+ status,
521
+ confidence,
522
+ assertedAt,
523
+ supersedes: claim.supersedes,
524
+ sourceEventId: claim.sourceEventId,
525
+ };
526
+ }
527
+ approveClaim(id, companionId) {
528
+ const findClaim = companionId
529
+ ? this.db.prepare("SELECT id, status, supersedes FROM memory_claims WHERE id = ? AND companion_id = ?")
530
+ : this.db.prepare("SELECT id, status, supersedes FROM memory_claims WHERE id = ?");
531
+ const row = (companionId ? findClaim.get(id, companionId) : findClaim.get(id));
532
+ if (!row) {
533
+ return;
534
+ }
535
+ if (row.status !== 'PENDING') {
536
+ throw new Error(`Cannot approve claim '${id}': invalid transition from status '${row.status}' to 'APPROVED' (only PENDING claims can be approved)`);
537
+ }
538
+ // If this claim supersedes an earlier claim, transition that prior claim to SUPERSEDED
539
+ if (row.supersedes) {
540
+ const supersededId = row.supersedes;
541
+ if (companionId) {
542
+ const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'SUPERSEDED' WHERE id = ? AND companion_id = ?");
543
+ supersedeStmt.run(supersededId, companionId);
544
+ }
545
+ else {
546
+ const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'SUPERSEDED' WHERE id = ?");
547
+ supersedeStmt.run(supersededId);
548
+ }
549
+ }
550
+ if (companionId) {
551
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'APPROVED' WHERE id = ? AND companion_id = ? AND status = 'PENDING'");
552
+ stmt.run(id, companionId);
553
+ }
554
+ else {
555
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'APPROVED' WHERE id = ? AND status = 'PENDING'");
556
+ stmt.run(id);
557
+ }
558
+ }
559
+ rejectClaim(id, companionId) {
560
+ if (companionId) {
561
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REJECTED' WHERE id = ? AND companion_id = ?");
562
+ stmt.run(id, companionId);
563
+ }
564
+ else {
565
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REJECTED' WHERE id = ?");
566
+ stmt.run(id);
567
+ }
568
+ }
569
+ revokeClaim(id, companionId) {
570
+ if (companionId) {
571
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REVOKED' WHERE id = ? AND companion_id = ?");
572
+ stmt.run(id, companionId);
573
+ }
574
+ else {
575
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REVOKED' WHERE id = ?");
576
+ stmt.run(id);
577
+ }
578
+ }
579
+ expireClaim(id, companionId) {
580
+ if (companionId) {
581
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'EXPIRED' WHERE id = ? AND companion_id = ?");
582
+ stmt.run(id, companionId);
583
+ }
584
+ else {
585
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'EXPIRED' WHERE id = ?");
586
+ stmt.run(id);
587
+ }
588
+ }
589
+ markClaimSessionOnly(id, companionId) {
590
+ if (companionId) {
591
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'SESSION_ONLY' WHERE id = ? AND companion_id = ?");
592
+ stmt.run(id, companionId);
593
+ }
594
+ else {
595
+ const stmt = this.db.prepare("UPDATE memory_claims SET status = 'SESSION_ONLY' WHERE id = ?");
596
+ stmt.run(id);
597
+ }
598
+ }
599
+ searchClaims(companionId, query, limit = 20) {
600
+ const stmt = this.db.prepare(`
601
+ SELECT c.* FROM memory_claims c
602
+ JOIN memory_search s ON c.rowid = s.rowid
603
+ WHERE c.companion_id = ? AND c.status = 'APPROVED' AND memory_search MATCH ?
604
+ ORDER BY rank
605
+ LIMIT ?
606
+ `);
607
+ return stmt.all(companionId, query, limit).map((row) => ({
608
+ id: row.id,
609
+ companionId: row.companion_id,
610
+ subject: row.subject,
611
+ predicate: row.predicate,
612
+ value: row.value,
613
+ status: row.status,
614
+ confidence: row.confidence,
615
+ validFrom: row.valid_from || undefined,
616
+ validUntil: row.valid_until || undefined,
617
+ evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
618
+ assertedAt: row.asserted_at,
619
+ supersedes: row.supersedes || undefined,
620
+ sourceEventId: row.source_event_id || undefined,
621
+ }));
622
+ }
623
+ getPendingClaims(companionId, limit = 50) {
624
+ const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND status = 'PENDING' ORDER BY asserted_at DESC LIMIT ?");
625
+ return stmt.all(companionId, limit).map((row) => ({
626
+ id: row.id,
627
+ companionId: row.companion_id,
628
+ subject: row.subject,
629
+ predicate: row.predicate,
630
+ value: row.value,
631
+ status: row.status,
632
+ confidence: row.confidence,
633
+ validFrom: row.valid_from || undefined,
634
+ validUntil: row.valid_until || undefined,
635
+ evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
636
+ assertedAt: row.asserted_at,
637
+ supersedes: row.supersedes || undefined,
638
+ sourceEventId: row.source_event_id || undefined,
639
+ }));
640
+ }
641
+ getApprovedClaims(companionId, limit = 50) {
642
+ const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND status = 'APPROVED' ORDER BY asserted_at DESC LIMIT ?");
643
+ return stmt.all(companionId, limit).map((row) => ({
644
+ id: row.id,
645
+ companionId: row.companion_id,
646
+ subject: row.subject,
647
+ predicate: row.predicate,
648
+ value: row.value,
649
+ status: row.status,
650
+ confidence: row.confidence,
651
+ validFrom: row.valid_from || undefined,
652
+ validUntil: row.valid_until || undefined,
653
+ evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
654
+ assertedAt: row.asserted_at,
655
+ supersedes: row.supersedes || undefined,
656
+ sourceEventId: row.source_event_id || undefined,
657
+ }));
658
+ }
659
+ getClaim(id) {
660
+ const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE id = ?");
661
+ const row = stmt.get(id);
662
+ if (!row)
663
+ return undefined;
664
+ return {
665
+ id: row.id,
666
+ companionId: row.companion_id,
667
+ subject: row.subject,
668
+ predicate: row.predicate,
669
+ value: row.value,
670
+ status: row.status,
671
+ confidence: row.confidence,
672
+ validFrom: row.valid_from || undefined,
673
+ validUntil: row.valid_until || undefined,
674
+ evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
675
+ assertedAt: row.asserted_at,
676
+ supersedes: row.supersedes || undefined,
677
+ sourceEventId: row.source_event_id || undefined,
678
+ };
679
+ }
680
+ resetMemory(companionId) {
681
+ const deleteClaims = this.db.prepare("DELETE FROM memory_claims WHERE companion_id = ?");
682
+ deleteClaims.run(companionId);
683
+ const deleteEvents = this.db.prepare("DELETE FROM memory_events WHERE companion_id = ?");
684
+ deleteEvents.run(companionId);
685
+ }
686
+ }
687
+ exports.SiduriDatabase = SiduriDatabase;
@@ -0,0 +1 @@
1
+ export {};