@siduri-x/core 2.0.0 → 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.
@@ -31,7 +31,7 @@ describe('validateCompanionConfig', () => {
31
31
  properties: {
32
32
  provider: {
33
33
  type: 'string',
34
- enum: ['postgres', 'in-memory', 'none'],
34
+ enum: ['sqlite', 'in-memory', 'none'],
35
35
  },
36
36
  maxConnections: { type: 'number' },
37
37
  },
@@ -107,7 +107,7 @@ describe('validateCompanionConfig', () => {
107
107
  name: 'Test',
108
108
  organs: {
109
109
  memory: {
110
- provider: 'postgres',
110
+ provider: 'sqlite',
111
111
  maxConnections: 'ten', // should be number
112
112
  },
113
113
  },
@@ -17,10 +17,10 @@ export interface SelfDirective {
17
17
  companionId: string;
18
18
  priority: number;
19
19
  directive: string;
20
- status: 'ACTIVE' | 'DISABLED' | 'SUPERSEDED';
21
- category: 'behavioral' | 'guardrail' | 'relational';
20
+ status: 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
21
+ category: 'behavioral' | 'guardrail' | 'relational' | string;
22
22
  supersedesId?: string;
23
- createdAt: string;
23
+ createdAt?: string;
24
24
  }
25
25
  export interface SelfRelationship {
26
26
  companionId: string;
@@ -77,12 +77,14 @@ export interface MemoryClaim {
77
77
  subject: string;
78
78
  predicate: string;
79
79
  value: string;
80
- status: 'PENDING' | 'APPROVED' | 'REJECTED';
80
+ status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'SESSION_ONLY' | 'SUPERSEDED' | 'REVOKED' | 'EXPIRED';
81
81
  confidence: number;
82
82
  validFrom?: string;
83
83
  validUntil?: string;
84
84
  evidence?: string[];
85
85
  assertedAt: string;
86
+ supersedes?: string;
87
+ sourceEventId?: string;
86
88
  }
87
89
  export interface SiduriDatabaseOptions {
88
90
  dbPath?: string;
@@ -98,7 +100,12 @@ export declare class SiduriDatabase {
98
100
  setPersonality(companionId: string, traits: PersonalityTraits): void;
99
101
  getActiveDirectives(companionId: string): SelfDirective[];
100
102
  commitDirective(directive: SelfDirective): void;
101
- disableDirective(id: string): void;
103
+ getDirective(id: string, companionId?: string): SelfDirective | undefined;
104
+ approveDirective(id: string, companionId?: string): void;
105
+ rejectDirective(id: string, companionId?: string): void;
106
+ revokeDirective(id: string, companionId?: string): void;
107
+ expireDirective(id: string, companionId?: string): void;
108
+ disableDirective(id: string, companionId?: string): void;
102
109
  getRelationship(companionId: string, entityId: string): SelfRelationship | undefined;
103
110
  upsertRelationship(rel: SelfRelationship): void;
104
111
  getInventory(companionId: string, domain?: string): LifeInventoryItem[];
@@ -111,9 +118,21 @@ export declare class SiduriDatabase {
111
118
  upsertPreference(pref: LifePreference): void;
112
119
  recordEvent(event: EpisodicEvent): void;
113
120
  getRecentEvents(companionId: string, limit?: number): EpisodicEvent[];
114
- proposeClaim(claim: Omit<MemoryClaim, 'status'>): MemoryClaim;
115
- approveClaim(id: string): void;
116
- rejectClaim(id: string): void;
121
+ getEvent(id: string): EpisodicEvent | undefined;
122
+ proposeClaim(claim: Omit<MemoryClaim, 'status' | 'confidence' | 'assertedAt'> & {
123
+ confidence?: number;
124
+ assertedAt?: string;
125
+ supersedes?: string;
126
+ sourceEventId?: string;
127
+ }): MemoryClaim;
128
+ approveClaim(id: string, companionId?: string): void;
129
+ rejectClaim(id: string, companionId?: string): void;
130
+ revokeClaim(id: string, companionId?: string): void;
131
+ expireClaim(id: string, companionId?: string): void;
132
+ markClaimSessionOnly(id: string, companionId?: string): void;
117
133
  searchClaims(companionId: string, query: string, limit?: number): MemoryClaim[];
134
+ getPendingClaims(companionId: string, limit?: number): MemoryClaim[];
118
135
  getApprovedClaims(companionId: string, limit?: number): MemoryClaim[];
136
+ getClaim(id: string): MemoryClaim | undefined;
137
+ resetMemory(companionId: string): void;
119
138
  }
package/dist/siduri-db.js CHANGED
@@ -114,7 +114,9 @@ class SiduriDatabase {
114
114
  valid_from TEXT,
115
115
  valid_until TEXT,
116
116
  evidence TEXT,
117
- asserted_at TEXT DEFAULT (datetime('now'))
117
+ asserted_at TEXT DEFAULT (datetime('now')),
118
+ supersedes TEXT,
119
+ source_event_id TEXT
118
120
  );
119
121
 
120
122
  -- FTS5 Virtual Table for Memory Claims
@@ -145,6 +147,18 @@ class SiduriDatabase {
145
147
  END;
146
148
  `;
147
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
+ }
148
162
  }
149
163
  close() {
150
164
  this.db.close();
@@ -228,9 +242,106 @@ class SiduriDatabase {
228
242
  `);
229
243
  stmt.run(directive.id, directive.companionId, directive.priority, directive.directive, directive.status, directive.category, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
230
244
  }
231
- disableDirective(id) {
232
- const stmt = this.db.prepare("UPDATE self_directives SET status = 'DISABLED' WHERE id = ?");
233
- stmt.run(id);
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
+ }
234
345
  }
235
346
  getRelationship(companionId, entityId) {
236
347
  const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?');
@@ -380,33 +491,116 @@ class SiduriDatabase {
380
491
  payload: JSON.parse(row.payload)
381
492
  }));
382
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
+ }
383
507
  proposeClaim(claim) {
384
508
  const id = claim.id || crypto.randomUUID();
385
509
  const status = 'PENDING';
510
+ const confidence = claim.confidence ?? 1.0;
511
+ const assertedAt = claim.assertedAt || new Date().toISOString();
386
512
  const stmt = this.db.prepare(`
387
- INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at)
388
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')))
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')), ?, ?)
389
515
  `);
390
- stmt.run(id, claim.companionId, claim.subject, claim.predicate, claim.value, status, claim.confidence ?? 1.0, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, claim.assertedAt || null);
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);
391
517
  return {
392
518
  ...claim,
393
519
  id,
394
- status
520
+ status,
521
+ confidence,
522
+ assertedAt,
523
+ supersedes: claim.supersedes,
524
+ sourceEventId: claim.sourceEventId,
395
525
  };
396
526
  }
397
- approveClaim(id) {
398
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'APPROVED' WHERE id = ?");
399
- stmt.run(id);
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
+ }
400
558
  }
401
- rejectClaim(id) {
402
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'REJECTED' WHERE id = ?");
403
- stmt.run(id);
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
+ }
404
598
  }
405
599
  searchClaims(companionId, query, limit = 20) {
406
600
  const stmt = this.db.prepare(`
407
601
  SELECT c.* FROM memory_claims c
408
602
  JOIN memory_search s ON c.rowid = s.rowid
409
- WHERE c.companion_id = ? AND memory_search MATCH ?
603
+ WHERE c.companion_id = ? AND c.status = 'APPROVED' AND memory_search MATCH ?
410
604
  ORDER BY rank
411
605
  LIMIT ?
412
606
  `);
@@ -421,7 +615,27 @@ class SiduriDatabase {
421
615
  validFrom: row.valid_from || undefined,
422
616
  validUntil: row.valid_until || undefined,
423
617
  evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
424
- assertedAt: row.asserted_at
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,
425
639
  }));
426
640
  }
427
641
  getApprovedClaims(companionId, limit = 50) {
@@ -437,8 +651,37 @@ class SiduriDatabase {
437
651
  validFrom: row.valid_from || undefined,
438
652
  validUntil: row.valid_until || undefined,
439
653
  evidence: row.evidence ? JSON.parse(row.evidence) : undefined,
440
- assertedAt: row.asserted_at
654
+ assertedAt: row.asserted_at,
655
+ supersedes: row.supersedes || undefined,
656
+ sourceEventId: row.source_event_id || undefined,
441
657
  }));
442
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
+ }
443
686
  }
444
687
  exports.SiduriDatabase = SiduriDatabase;