@sidurijs/core 1.0.0 → 1.0.2

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 (41) hide show
  1. package/LICENSE +190 -0
  2. package/dist/adversarial.test.js +27 -19
  3. package/dist/chat-contract.d.ts +9 -0
  4. package/dist/cognition-planner.d.ts +2 -2
  5. package/dist/container.d.ts +6 -4
  6. package/dist/container.js +1 -4
  7. package/dist/context-retriever.d.ts +5 -4
  8. package/dist/context-retriever.js +43 -25
  9. package/dist/conversational-teach.test.js +61 -53
  10. package/dist/evidence.d.ts +3 -3
  11. package/dist/gating.d.ts +2 -2
  12. package/dist/gating.js +1 -1
  13. package/dist/index.d.ts +43 -51
  14. package/dist/index.js +1 -1
  15. package/dist/intent-classifier.d.ts +1 -1
  16. package/dist/intent-classifier.js +4 -4
  17. package/dist/intent-classifier.test.js +1 -1
  18. package/dist/{memory-settler.d.ts → interaction-settler.d.ts} +12 -11
  19. package/dist/interaction-settler.js +173 -0
  20. package/dist/perception-cycle.test.js +18 -34
  21. package/dist/perception-pipeline.d.ts +6 -5
  22. package/dist/perception-pipeline.js +17 -15
  23. package/dist/prompt-compiler.d.ts +1 -1
  24. package/dist/prompt-compiler.js +11 -11
  25. package/dist/prompt-compiler.test.js +5 -5
  26. package/dist/proposals.d.ts +1 -2
  27. package/dist/response-envelope.d.ts +3 -3
  28. package/dist/response-envelope.js +5 -4
  29. package/dist/runtime-facades.test.js +12 -35
  30. package/dist/runtime.d.ts +16 -10
  31. package/dist/runtime.js +85 -107
  32. package/dist/schema-validator.test.js +3 -3
  33. package/dist/session-history.d.ts +3 -3
  34. package/dist/session-history.js +1 -1
  35. package/dist/siduri-db.d.ts +11 -29
  36. package/dist/siduri-db.js +159 -445
  37. package/dist/siduri-db.test.js +113 -281
  38. package/dist/teaching.d.ts +3 -3
  39. package/dist/teaching.js +1 -1
  40. package/package.json +7 -7
  41. package/dist/memory-settler.js +0 -161
package/dist/siduri-db.js CHANGED
@@ -163,8 +163,8 @@ class SiduriDatabase {
163
163
  CREATE INDEX IF NOT EXISTS idx_life_events_comp ON life_events(companion_id, stream, timestamp);
164
164
  CREATE INDEX IF NOT EXISTS idx_life_tasks_comp ON life_tasks(companion_id, status);
165
165
 
166
- -- Memory Tables
167
- CREATE TABLE IF NOT EXISTS memory_events (
166
+ -- Archive Tables (RFC VX-26-13: Audited interaction ledger and cold search)
167
+ CREATE TABLE IF NOT EXISTS archive_events (
168
168
  id TEXT PRIMARY KEY,
169
169
  companion_id TEXT NOT NULL,
170
170
  source_type TEXT NOT NULL,
@@ -172,47 +172,28 @@ class SiduriDatabase {
172
172
  payload TEXT NOT NULL
173
173
  );
174
174
 
175
- CREATE TABLE IF NOT EXISTS memory_claims (
176
- id TEXT PRIMARY KEY,
177
- companion_id TEXT NOT NULL,
178
- subject TEXT NOT NULL,
179
- predicate TEXT NOT NULL,
180
- value TEXT NOT NULL,
181
- status TEXT DEFAULT 'pending',
182
- confidence REAL DEFAULT 1.0,
183
- valid_from TEXT,
184
- valid_until TEXT,
185
- evidence TEXT,
186
- asserted_at TEXT DEFAULT (datetime('now')),
187
- supersedes TEXT,
188
- source_event_id TEXT
189
- );
190
-
191
- -- FTS5 Virtual Table for Memory Claims
192
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_search USING fts5(
193
- subject,
194
- predicate,
195
- value,
196
- content='memory_claims',
175
+ CREATE VIRTUAL TABLE IF NOT EXISTS archive_search USING fts5(
176
+ source_type,
177
+ payload,
178
+ content='archive_events',
197
179
  content_rowid='rowid'
198
180
  );
199
181
 
200
- -- FTS5 Triggers
201
- CREATE TRIGGER IF NOT EXISTS memory_claims_ai AFTER INSERT ON memory_claims BEGIN
202
- INSERT INTO memory_search(rowid, subject, predicate, value)
203
- VALUES (new.rowid, new.subject, new.predicate, new.value);
182
+ CREATE TRIGGER IF NOT EXISTS archive_events_ai AFTER INSERT ON archive_events BEGIN
183
+ INSERT INTO archive_search(rowid, source_type, payload)
184
+ VALUES (new.rowid, new.source_type, new.payload);
204
185
  END;
205
186
 
206
- CREATE TRIGGER IF NOT EXISTS memory_claims_ad AFTER DELETE ON memory_claims BEGIN
207
- INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
208
- VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
187
+ CREATE TRIGGER IF NOT EXISTS archive_events_ad AFTER DELETE ON archive_events BEGIN
188
+ INSERT INTO archive_search(archive_search, rowid, source_type, payload)
189
+ VALUES('delete', old.rowid, old.source_type, old.payload);
209
190
  END;
210
191
 
211
- CREATE TRIGGER IF NOT EXISTS memory_claims_au AFTER UPDATE ON memory_claims BEGIN
212
- INSERT INTO memory_search(memory_search, rowid, subject, predicate, value)
213
- VALUES('delete', old.rowid, old.subject, old.predicate, old.value);
214
- INSERT INTO memory_search(rowid, subject, predicate, value)
215
- VALUES (new.rowid, new.subject, new.predicate, new.value);
192
+ CREATE TRIGGER IF NOT EXISTS archive_events_au AFTER UPDATE ON archive_events BEGIN
193
+ INSERT INTO archive_search(archive_search, rowid, source_type, payload)
194
+ VALUES('delete', old.rowid, old.source_type, old.payload);
195
+ INSERT INTO archive_search(rowid, source_type, payload)
196
+ VALUES (new.rowid, new.source_type, new.payload);
216
197
  END;
217
198
 
218
199
  -- System Logs Table
@@ -229,18 +210,6 @@ class SiduriDatabase {
229
210
  CREATE INDEX IF NOT EXISTS idx_system_logs_comp ON system_logs(companion_id, level, created_at DESC);
230
211
  `;
231
212
  this.db.exec(schema);
232
- try {
233
- this.db.exec("ALTER TABLE memory_claims ADD COLUMN supersedes TEXT");
234
- }
235
- catch {
236
- // Column already exists
237
- }
238
- try {
239
- this.db.exec("ALTER TABLE memory_claims ADD COLUMN source_event_id TEXT");
240
- }
241
- catch {
242
- // Column already exists
243
- }
244
213
  try {
245
214
  this.db.exec("ALTER TABLE self_identity ADD COLUMN origin TEXT");
246
215
  }
@@ -295,34 +264,6 @@ class SiduriDatabase {
295
264
  catch {
296
265
  // Column already exists
297
266
  }
298
- try {
299
- // Reconcile creator claims that may have been recorded before origin dual-promotion
300
- const creatorClaims = this.db.prepare(`
301
- SELECT * FROM memory_claims
302
- WHERE predicate = 'stated_relationship'
303
- AND LOWER(value) LIKE '%creator%'
304
- AND LOWER(status) = 'approved'
305
- `).all();
306
- for (const claim of creatorClaims) {
307
- const identity = this.db.prepare(`SELECT * FROM self_identity WHERE companion_id = ?`).get(claim.companion_id);
308
- if (identity && !identity.origin) {
309
- const nameClaim = this.db.prepare(`
310
- SELECT value FROM memory_claims
311
- WHERE companion_id = ? AND subject = ? AND predicate = 'name' AND LOWER(status) = 'approved'
312
- ORDER BY asserted_at DESC LIMIT 1
313
- `).get(claim.companion_id, claim.subject);
314
- const originName = nameClaim?.value || (claim.subject.startsWith('actor:') ? claim.subject.slice(6) : claim.subject);
315
- this.db.prepare(`UPDATE self_identity SET origin = ? WHERE companion_id = ?`).run(originName, claim.companion_id);
316
- }
317
- const rel = this.db.prepare(`SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?`).get(claim.companion_id, claim.subject);
318
- if (rel && (rel.role !== 'creator' || rel.stance !== 'familiar_loyal')) {
319
- this.db.prepare(`UPDATE self_relationships SET role = 'creator', stance = 'familiar_loyal', trust_score = 1.0 WHERE companion_id = ? AND entity_id = ?`).run(claim.companion_id, claim.subject);
320
- }
321
- }
322
- }
323
- catch {
324
- // Best-effort auto-reconciliation
325
- }
326
267
  }
327
268
  close() {
328
269
  this.db.close();
@@ -499,7 +440,7 @@ class SiduriDatabase {
499
440
  });
500
441
  }
501
442
  }
502
- // Single-value directive canonical supersession (e.g. role, language, response style)
443
+ // Single-value directive canonical supersession and direct Self mutation (RFC VX-26-13)
503
444
  const roleMatch = (row.directive || '').match(/^acknowledge role as\s+([^"”'.]+)/i);
504
445
  if (roleMatch) {
505
446
  const existingDirectives = this.getAllDirectives(companionId || 'default');
@@ -511,6 +452,20 @@ class SiduriDatabase {
511
452
  this.supersedeDirective(d.id, companionId);
512
453
  }
513
454
  }
455
+ const newRole = roleMatch[1].trim();
456
+ if (newRole) {
457
+ const targetId = companionId || row.companion_id || 'default';
458
+ const currentIdentity = this.getIdentity(targetId) || {
459
+ companionId: targetId,
460
+ name: '',
461
+ version: '1.0.0',
462
+ updatedAt: new Date().toISOString(),
463
+ };
464
+ currentIdentity.role = newRole;
465
+ currentIdentity.archetype = newRole;
466
+ currentIdentity.updatedAt = new Date().toISOString();
467
+ this.setIdentity(currentIdentity);
468
+ }
514
469
  }
515
470
  // Companion name directive detection: "Address companion as X", "Your name is X", "Call yourself X", "Acknowledge name as X"
516
471
  const compNameMatch = (row.directive || '').match(/^(?:address\s+companion\s+as|your\s+name\s+is|call\s+yourself|acknowledge\s+name\s+as|companion\s+name\s+is)\s+["“']?([^"”'.]+)["”']?/i);
@@ -529,6 +484,82 @@ class SiduriDatabase {
529
484
  this.setIdentity(currentIdentity);
530
485
  }
531
486
  }
487
+ // Relationship directive direct domain routing:
488
+ // "Recognize <actor> stated relationship as <role>", "Recognize <actor> relationship as <role>", "Recognize <actor> as <role>"
489
+ const relMatch = (row.directive || '').match(/^(?:recognize\s+(\S+)\s+(?:stated\s+)?relationship\s+as|recognize\s+(\S+)\s+as)\s+([^"”'.]+)/i);
490
+ if (relMatch) {
491
+ const rawActor = (relMatch[1] || relMatch[2] || '').trim();
492
+ const roleOrStance = (relMatch[3] || '').trim();
493
+ if (rawActor && roleOrStance) {
494
+ const targetId = companionId || row.companion_id || 'default';
495
+ const isCreator = roleOrStance.toLowerCase().includes('creator');
496
+ const existingRel = this.getRelationship(targetId, rawActor);
497
+ const stance = isCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
498
+ const trustScore = isCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
499
+ const familiarity = isCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
500
+ const interactionConventions = isCreator
501
+ ? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
502
+ : (existingRel?.interactionConventions || []);
503
+ this.upsertRelationship({
504
+ companionId: targetId,
505
+ entityId: rawActor,
506
+ entityType: 'human',
507
+ name: existingRel?.name,
508
+ affiliation: existingRel?.affiliation,
509
+ role: roleOrStance,
510
+ stance,
511
+ trustScore,
512
+ familiarity,
513
+ interactionConventions,
514
+ });
515
+ if (isCreator) {
516
+ const currentIdentity = this.getIdentity(targetId) || {
517
+ companionId: targetId,
518
+ name: '',
519
+ version: '1.0.0',
520
+ updatedAt: new Date().toISOString(),
521
+ };
522
+ const creatorName = existingRel?.name || (rawActor.startsWith('actor:') ? rawActor.slice(6) : rawActor);
523
+ currentIdentity.origin = creatorName !== 'user' && creatorName !== 'primary' ? creatorName : roleOrStance;
524
+ currentIdentity.updatedAt = new Date().toISOString();
525
+ this.setIdentity(currentIdentity);
526
+ }
527
+ }
528
+ }
529
+ // User name address directive direct domain routing: "Address <actor> as <name>"
530
+ const userAddrMatch = (row.directive || '').match(/^address\s+(actor:\S+|\S+)\s+as\s+([^"”'.]+)/i);
531
+ if (userAddrMatch) {
532
+ const actorId = userAddrMatch[1].trim();
533
+ const userName = userAddrMatch[2].trim();
534
+ if (actorId && userName) {
535
+ const targetId = companionId || row.companion_id || 'default';
536
+ const existingRel = this.getRelationship(targetId, actorId);
537
+ const isCreator = existingRel?.role === 'creator' || (existingRel?.stance === 'familiar_loyal' && existingRel?.trustScore === 1.0);
538
+ this.upsertRelationship({
539
+ companionId: targetId,
540
+ entityId: actorId,
541
+ entityType: 'human',
542
+ name: userName,
543
+ affiliation: existingRel?.affiliation,
544
+ role: existingRel?.role || (isCreator ? 'creator' : 'user'),
545
+ stance: existingRel?.stance || (isCreator ? 'familiar_loyal' : 'neutral'),
546
+ trustScore: existingRel?.trustScore ?? (isCreator ? 1.0 : 0.8),
547
+ familiarity: existingRel?.familiarity ?? (isCreator ? 0.9 : 0.5),
548
+ interactionConventions: existingRel?.interactionConventions || [],
549
+ });
550
+ if (isCreator) {
551
+ const currentIdentity = this.getIdentity(targetId) || {
552
+ companionId: targetId,
553
+ name: '',
554
+ version: '1.0.0',
555
+ updatedAt: new Date().toISOString(),
556
+ };
557
+ currentIdentity.origin = userName;
558
+ currentIdentity.updatedAt = new Date().toISOString();
559
+ this.setIdentity(currentIdentity);
560
+ }
561
+ }
562
+ }
532
563
  if (companionId) {
533
564
  const stmt = this.db.prepare("UPDATE self_directives SET status = 'active', priority = MAX(priority, ?) WHERE id = ? AND companion_id = ? AND LOWER(status) IN ('pending', 'superseded')");
534
565
  stmt.run(targetPriority, id, companionId);
@@ -914,27 +945,27 @@ class SiduriDatabase {
914
945
  return res.changes > 0;
915
946
  }
916
947
  // ==========================================
917
- // Memory Domain Methods
948
+ // Archive Domain Methods (RFC VX-26-13: Audited interaction ledger and cold search)
918
949
  // ==========================================
919
- recordEvent(event) {
950
+ recordArchiveEvent(event) {
920
951
  const stmt = this.db.prepare(`
921
- INSERT INTO memory_events (id, companion_id, source_type, occurred_at, payload)
952
+ INSERT INTO archive_events (id, companion_id, source_type, occurred_at, payload)
922
953
  VALUES (?, ?, ?, coalesce(?, datetime('now')), ?)
923
954
  `);
924
- stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, JSON.stringify(event.payload));
955
+ stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, typeof event.payload === 'string' ? event.payload : JSON.stringify(event.payload));
925
956
  }
926
- getRecentEvents(companionId, limit = 50) {
927
- const stmt = this.db.prepare('SELECT * FROM memory_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
957
+ getRecentArchiveEvents(companionId, limit = 50) {
958
+ const stmt = this.db.prepare('SELECT * FROM archive_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
928
959
  return stmt.all(companionId, limit).map((row) => ({
929
960
  id: row.id,
930
961
  companionId: row.companion_id,
931
962
  sourceType: row.source_type,
932
963
  occurredAt: row.occurred_at,
933
- payload: JSON.parse(row.payload)
964
+ payload: safeJsonParse(row.payload, {})
934
965
  }));
935
966
  }
936
- getEvent(id) {
937
- const stmt = this.db.prepare('SELECT * FROM memory_events WHERE id = ?');
967
+ getArchiveEvent(id) {
968
+ const stmt = this.db.prepare('SELECT * FROM archive_events WHERE id = ?');
938
969
  const row = stmt.get(id);
939
970
  if (!row)
940
971
  return undefined;
@@ -943,332 +974,13 @@ class SiduriDatabase {
943
974
  companionId: row.companion_id,
944
975
  sourceType: row.source_type,
945
976
  occurredAt: row.occurred_at,
946
- payload: JSON.parse(row.payload)
977
+ payload: safeJsonParse(row.payload, {})
947
978
  };
948
979
  }
949
- rowToMemoryClaim(row) {
950
- return {
951
- id: row.id,
952
- companionId: row.companion_id,
953
- subject: row.subject,
954
- predicate: row.predicate,
955
- value: row.value,
956
- status: normalizeStatus(row.status, 'pending'),
957
- confidence: row.confidence,
958
- validFrom: row.valid_from || undefined,
959
- validUntil: row.valid_until || undefined,
960
- evidence: row.evidence ? (typeof row.evidence === 'string' ? JSON.parse(row.evidence) : row.evidence) : undefined,
961
- assertedAt: row.asserted_at,
962
- supersedes: row.supersedes || undefined,
963
- sourceEventId: row.source_event_id || undefined,
964
- };
965
- }
966
- proposeClaim(claim) {
967
- const id = claim.id || crypto.randomUUID();
968
- const status = normalizeStatus(claim.status, 'pending');
969
- const confidence = claim.confidence ?? 1.0;
970
- const assertedAt = claim.assertedAt || new Date().toISOString();
971
- const stmt = this.db.prepare(`
972
- INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at, supersedes, source_event_id)
973
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?, ?)
974
- `);
975
- stmt.run(id, claim.companionId || 'default', 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);
976
- return {
977
- ...claim,
978
- id,
979
- status: status,
980
- confidence,
981
- assertedAt,
982
- supersedes: claim.supersedes,
983
- sourceEventId: claim.sourceEventId,
984
- };
985
- }
986
- approveClaim(id, companionId) {
987
- const findClaim = companionId
988
- ? this.db.prepare("SELECT id, subject, predicate, status, supersedes FROM memory_claims WHERE id = ? AND companion_id = ?")
989
- : this.db.prepare("SELECT id, subject, predicate, status, supersedes FROM memory_claims WHERE id = ?");
990
- const row = (companionId ? findClaim.get(id, companionId) : findClaim.get(id));
991
- if (!row) {
992
- return;
993
- }
994
- if (normalizeStatus(row.status) !== 'pending') {
995
- if (normalizeStatus(row.status) === 'approved') {
996
- return;
997
- }
998
- throw new Error(`Cannot approve claim '${id}': invalid transition from status '${row.status}' to 'approved' (only pending claims can be approved)`);
999
- }
1000
- // Explicit supersession transition: if claim explicitly targets an earlier claim
1001
- if (row.supersedes) {
1002
- const supersededId = row.supersedes;
1003
- if (companionId) {
1004
- const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'superseded' WHERE id = ? AND companion_id = ?");
1005
- supersedeStmt.run(supersededId, companionId);
1006
- }
1007
- else {
1008
- const supersedeStmt = this.db.prepare("UPDATE memory_claims SET status = 'superseded' WHERE id = ?");
1009
- supersedeStmt.run(supersededId);
1010
- }
1011
- }
1012
- // Automatic supersession for single-value predicates:
1013
- // When a single-value predicate (e.g. name, role, creator, preferred_address) is approved,
1014
- // transition previous active/approved claims on the same subject & predicate to 'superseded'
1015
- const SINGLE_VALUE_PREDICATES = new Set([
1016
- 'name',
1017
- 'preferred_name',
1018
- 'preferred_address',
1019
- 'preferred_form_of_address',
1020
- 'form_of_address',
1021
- 'title',
1022
- 'honorific',
1023
- 'role',
1024
- 'archetype',
1025
- 'origin',
1026
- 'created_by',
1027
- 'stated_relationship',
1028
- 'relationship',
1029
- 'relationship_to_siduri',
1030
- 'ethos',
1031
- 'server',
1032
- 'uid',
1033
- 'preferred_language',
1034
- ]);
1035
- const lowerPred = (row.predicate || '').toLowerCase();
1036
- if (SINGLE_VALUE_PREDICATES.has(lowerPred)) {
1037
- const effCompanionId = companionId || 'default';
1038
- const autoSupersedeStmt = this.db.prepare(`
1039
- UPDATE memory_claims
1040
- SET status = 'superseded'
1041
- WHERE companion_id = ?
1042
- AND LOWER(subject) = LOWER(?)
1043
- AND LOWER(predicate) = LOWER(?)
1044
- AND id != ?
1045
- AND LOWER(status) IN ('approved', 'pending')
1046
- `);
1047
- autoSupersedeStmt.run(effCompanionId, row.subject, row.predicate, id);
1048
- }
1049
- if (companionId) {
1050
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'approved' WHERE id = ? AND companion_id = ? AND LOWER(status) = 'pending'");
1051
- stmt.run(id, companionId);
1052
- }
1053
- else {
1054
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'approved' WHERE id = ? AND LOWER(status) = 'pending'");
1055
- stmt.run(id);
1056
- }
1057
- // Canonically promote approved claim to Self domain state (identity, role, relationships)
1058
- const approvedClaim = this.getClaim(id);
1059
- if (approvedClaim) {
1060
- this.promoteClaimToSelf(approvedClaim);
1061
- }
1062
- }
1063
- /**
1064
- * Canonically promotes a Claim into Self domain tables.
1065
- */
1066
- promoteClaimToSelf(claim) {
1067
- const companionId = claim.companionId || 'default';
1068
- const subject = (claim.subject || '').toLowerCase();
1069
- const predicate = (claim.predicate || '').toLowerCase();
1070
- const value = claim.value || '';
1071
- if (!value)
1072
- return;
1073
- // 1. Identity mutations: companion identity/role/origin/name/ethos
1074
- const isCompanionTarget = subject.startsWith('companion') ||
1075
- subject === 'siduri' ||
1076
- subject === 'self' ||
1077
- subject === 'assistant' ||
1078
- subject === 'persona' ||
1079
- subject === 'ai' ||
1080
- subject === 'me' ||
1081
- ((predicate === 'name' || predicate === 'role' || predicate === 'archetype' || predicate === 'ethos' || predicate === 'origin') &&
1082
- !subject.startsWith('actor:') &&
1083
- subject !== 'user' &&
1084
- subject !== 'primary_user' &&
1085
- subject !== 'owner' &&
1086
- subject !== 'creator');
1087
- if (isCompanionTarget) {
1088
- const existing = this.getIdentity(companionId) || {
1089
- companionId,
1090
- name: '',
1091
- version: '1.0.0',
1092
- updatedAt: new Date().toISOString(),
1093
- };
1094
- if (predicate === 'role' || predicate === 'archetype') {
1095
- existing.archetype = value;
1096
- existing.role = value;
1097
- this.setIdentity(existing);
1098
- this.commitDirective({
1099
- id: `dir-role-${claim.id || Date.now()}`,
1100
- companionId,
1101
- priority: 70,
1102
- directive: `Acknowledge role as ${value}`,
1103
- status: 'active',
1104
- category: 'relational',
1105
- createdAt: new Date().toISOString(),
1106
- });
1107
- }
1108
- else if (predicate === 'origin' || predicate === 'created_by') {
1109
- existing.origin = value;
1110
- this.setIdentity(existing);
1111
- }
1112
- else if (predicate === 'name') {
1113
- existing.name = value;
1114
- this.setIdentity(existing);
1115
- }
1116
- else if (predicate === 'ethos') {
1117
- existing.ethos = value;
1118
- this.setIdentity(existing);
1119
- }
1120
- return;
1121
- }
1122
- // 2. Relationship mutations: creator or user stated relationship, name, preferred address, or affiliation
1123
- const isPreferredAddress = [
1124
- 'preferred_address',
1125
- 'preferred_form_of_address',
1126
- 'preferred_name',
1127
- 'form_of_address',
1128
- 'title',
1129
- 'honorific',
1130
- 'call_me',
1131
- 'addressed_as',
1132
- ].includes(predicate);
1133
- const isName = (predicate === 'name' && (subject.startsWith('actor:') || subject === 'user' || subject === 'primary_user')) || isPreferredAddress;
1134
- if (claim.claimType === 'relationship' ||
1135
- predicate === 'stated_relationship' ||
1136
- predicate === 'relationship' ||
1137
- predicate === 'relationship_to_siduri' ||
1138
- predicate === 'affiliation' ||
1139
- isName) {
1140
- const rawSubject = (claim.subject || 'actor:user').replace(/^actor:actor:/, 'actor:');
1141
- const isCreator = value.toLowerCase().includes('creator');
1142
- const isAffil = predicate === 'affiliation';
1143
- const existingRel = this.getRelationship(companionId, rawSubject);
1144
- const isPriorCreator = existingRel?.role === 'creator' || (existingRel?.stance === 'familiar_loyal' && existingRel.trustScore === 1.0);
1145
- const role = isCreator
1146
- ? 'creator'
1147
- : (isPriorCreator ? 'creator' : (existingRel?.role && existingRel.role !== 'user' ? existingRel.role : (isName || isAffil ? existingRel?.role || 'user' : value)));
1148
- const name = isName ? (isPreferredAddress && existingRel?.name ? existingRel.name : value) : existingRel?.name;
1149
- const affiliation = isAffil ? value : existingRel?.affiliation;
1150
- const stance = isCreator || isPriorCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
1151
- const trustScore = isCreator || isPriorCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
1152
- const familiarity = isCreator || isPriorCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
1153
- const conventions = isCreator || isPriorCreator
1154
- ? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
1155
- : [...(existingRel?.interactionConventions || [])];
1156
- if (isPreferredAddress) {
1157
- const filteredConvs = conventions.filter((c) => !c.toLowerCase().startsWith('address as'));
1158
- filteredConvs.push(`Address as ${value}`);
1159
- conventions.length = 0;
1160
- conventions.push(...filteredConvs);
1161
- }
1162
- this.upsertRelationship({
1163
- companionId,
1164
- entityId: rawSubject,
1165
- entityType: 'human',
1166
- name,
1167
- affiliation,
1168
- role,
1169
- stance,
1170
- trustScore,
1171
- familiarity,
1172
- interactionConventions: conventions,
1173
- });
1174
- // Dual promotion: If this actor is established as creator, also populate companion's origin in self_identity
1175
- if (isCreator || (isName && isPriorCreator)) {
1176
- const existingIdentity = this.getIdentity(companionId) || {
1177
- companionId,
1178
- name: 'Siduri',
1179
- version: '1.0.0',
1180
- updatedAt: new Date().toISOString(),
1181
- };
1182
- const creatorName = name || existingRel?.name || (rawSubject.startsWith('actor:') && rawSubject !== 'actor:user' && rawSubject !== 'actor:primary' ? rawSubject.slice(6) : value);
1183
- existingIdentity.origin = creatorName !== 'user' && creatorName !== 'primary' ? creatorName : value;
1184
- existingIdentity.updatedAt = new Date().toISOString();
1185
- this.setIdentity(existingIdentity);
1186
- }
1187
- if (isName) {
1188
- const priority = isPreferredAddress ? 85 : 75;
1189
- const directiveId = isPreferredAddress ? `dir-prefaddr-${claim.id || Date.now()}` : `dir-name-${claim.id || Date.now()}`;
1190
- // If preferred address, supersede/revoke older address directives for this subject
1191
- if (isPreferredAddress) {
1192
- const existingDirectives = this.getAllDirectives(companionId);
1193
- for (const d of existingDirectives) {
1194
- const lowerDir = (d.directive || '').toLowerCase();
1195
- const lowerSubj = rawSubject.toLowerCase();
1196
- if ((d.status === 'active' || d.status === 'pending') &&
1197
- d.id !== directiveId &&
1198
- (lowerDir.startsWith(`address ${lowerSubj} as`) ||
1199
- lowerDir.startsWith('address the user as') ||
1200
- lowerDir.startsWith('address actor:user as') ||
1201
- lowerDir.startsWith('address user as'))) {
1202
- this.supersedeDirective(d.id, companionId);
1203
- }
1204
- }
1205
- }
1206
- this.commitDirective({
1207
- id: directiveId,
1208
- companionId,
1209
- priority,
1210
- directive: `Address ${rawSubject} as ${value}`,
1211
- status: 'active',
1212
- category: 'relational',
1213
- createdAt: new Date().toISOString(),
1214
- });
1215
- }
1216
- return;
1217
- }
1218
- // 3. Behavioral rule claim
1219
- if (predicate === 'behavioral_rule' || predicate === 'rule') {
1220
- this.commitDirective({
1221
- id: `dir-rule-${claim.id || Date.now()}`,
1222
- companionId,
1223
- priority: 60,
1224
- directive: value,
1225
- status: 'active',
1226
- category: 'behavioral',
1227
- createdAt: new Date().toISOString(),
1228
- });
1229
- }
1230
- }
1231
- rejectClaim(id, companionId) {
1232
- if (companionId) {
1233
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'rejected' WHERE id = ? AND companion_id = ?");
1234
- stmt.run(id, companionId);
1235
- }
1236
- else {
1237
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'rejected' WHERE id = ?");
1238
- stmt.run(id);
1239
- }
1240
- }
1241
- revokeClaim(id, companionId) {
1242
- if (companionId) {
1243
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'revoked' WHERE id = ? AND companion_id = ?");
1244
- stmt.run(id, companionId);
980
+ searchArchiveEvents(companionId, query, limit = 50) {
981
+ if (!query || !query.trim()) {
982
+ return this.getRecentArchiveEvents(companionId, limit);
1245
983
  }
1246
- else {
1247
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'revoked' WHERE id = ?");
1248
- stmt.run(id);
1249
- }
1250
- }
1251
- expireClaim(id, companionId) {
1252
- if (companionId) {
1253
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'expired' WHERE id = ? AND companion_id = ?");
1254
- stmt.run(id, companionId);
1255
- }
1256
- else {
1257
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'expired' WHERE id = ?");
1258
- stmt.run(id);
1259
- }
1260
- }
1261
- markClaimSessionOnly(id, companionId) {
1262
- if (companionId) {
1263
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'session-only' WHERE id = ? AND companion_id = ?");
1264
- stmt.run(id, companionId);
1265
- }
1266
- else {
1267
- const stmt = this.db.prepare("UPDATE memory_claims SET status = 'session-only' WHERE id = ?");
1268
- stmt.run(id);
1269
- }
1270
- }
1271
- searchClaims(companionId, query, limit = 20) {
1272
984
  const cleanTokens = query
1273
985
  .replace(/[^\p{L}\p{N}\s_]/gu, ' ')
1274
986
  .trim()
@@ -1276,44 +988,46 @@ class SiduriDatabase {
1276
988
  .filter((t) => t.length > 0)
1277
989
  .map((t) => `"${t.replace(/"/g, '""')}"`);
1278
990
  if (cleanTokens.length === 0)
1279
- return [];
991
+ return this.getRecentArchiveEvents(companionId, limit);
1280
992
  const ftsQuery = cleanTokens.join(' OR ');
1281
- const stmt = this.db.prepare(`
1282
- SELECT c.* FROM memory_claims c
1283
- JOIN memory_search s ON c.rowid = s.rowid
1284
- WHERE c.companion_id = ? AND LOWER(c.status) = 'approved' AND memory_search MATCH ?
1285
- ORDER BY rank
1286
- LIMIT ?
1287
- `);
1288
- return stmt.all(companionId, ftsQuery, limit).map((row) => this.rowToMemoryClaim(row));
1289
- }
1290
- getPendingClaims(companionId, limit = 50) {
1291
- const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND LOWER(status) = 'pending' ORDER BY asserted_at DESC LIMIT ?");
1292
- return stmt.all(companionId, limit).map((row) => this.rowToMemoryClaim(row));
1293
- }
1294
- getApprovedClaims(companionId, limit = 50) {
1295
- const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? AND LOWER(status) = 'approved' ORDER BY asserted_at DESC LIMIT ?");
1296
- return stmt.all(companionId, limit).map((row) => this.rowToMemoryClaim(row));
1297
- }
1298
- getAllClaims(companionId, limit = 100) {
1299
- const stmt = companionId
1300
- ? this.db.prepare("SELECT * FROM memory_claims WHERE companion_id = ? ORDER BY asserted_at DESC LIMIT ?")
1301
- : this.db.prepare("SELECT * FROM memory_claims ORDER BY asserted_at DESC LIMIT ?");
1302
- const rows = companionId ? stmt.all(companionId, limit) : stmt.all(limit);
1303
- return rows.map((row) => this.rowToMemoryClaim(row));
1304
- }
1305
- getClaim(id) {
1306
- const stmt = this.db.prepare("SELECT * FROM memory_claims WHERE id = ?");
1307
- const row = stmt.get(id);
1308
- if (!row)
1309
- return undefined;
1310
- return this.rowToMemoryClaim(row);
993
+ try {
994
+ const stmt = this.db.prepare(`
995
+ SELECT e.* FROM archive_events e
996
+ JOIN archive_search s ON e.rowid = s.rowid
997
+ WHERE e.companion_id = ? AND archive_search MATCH ?
998
+ ORDER BY rank
999
+ LIMIT ?
1000
+ `);
1001
+ return stmt.all(companionId, ftsQuery, limit).map((row) => ({
1002
+ id: row.id,
1003
+ companionId: row.companion_id,
1004
+ sourceType: row.source_type,
1005
+ occurredAt: row.occurred_at,
1006
+ payload: safeJsonParse(row.payload, {})
1007
+ }));
1008
+ }
1009
+ catch {
1010
+ const stmt = this.db.prepare(`
1011
+ SELECT * FROM archive_events
1012
+ WHERE companion_id = ? AND (source_type LIKE ? OR payload LIKE ?)
1013
+ ORDER BY occurred_at DESC
1014
+ LIMIT ?
1015
+ `);
1016
+ const pattern = `%${query.trim()}%`;
1017
+ return stmt.all(companionId, pattern, pattern, limit).map((row) => ({
1018
+ id: row.id,
1019
+ companionId: row.companion_id,
1020
+ sourceType: row.source_type,
1021
+ occurredAt: row.occurred_at,
1022
+ payload: safeJsonParse(row.payload, {})
1023
+ }));
1024
+ }
1311
1025
  }
1312
- resetMemory(companionId) {
1313
- const deleteClaims = this.db.prepare("DELETE FROM memory_claims WHERE companion_id = ?");
1314
- deleteClaims.run(companionId);
1315
- const deleteEvents = this.db.prepare("DELETE FROM memory_events WHERE companion_id = ?");
1316
- deleteEvents.run(companionId);
1026
+ resetArchive(companionId) {
1027
+ try {
1028
+ this.db.prepare("DELETE FROM archive_events WHERE companion_id = ?").run(companionId);
1029
+ }
1030
+ catch { }
1317
1031
  }
1318
1032
  // --- System Logs ---
1319
1033
  insertLog(entry) {