@sidurijs/core 1.0.0 → 1.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.
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
  }
@@ -296,27 +265,30 @@ class SiduriDatabase {
296
265
  // Column already exists
297
266
  }
298
267
  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(`
268
+ // Reconcile legacy creator claims if memory_claims table still exists from earlier versions
269
+ const hasMemoryClaims = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_claims'").get();
270
+ if (hasMemoryClaims) {
271
+ const creatorClaims = this.db.prepare(`
272
+ SELECT * FROM memory_claims
273
+ WHERE predicate = 'stated_relationship'
274
+ AND LOWER(value) LIKE '%creator%'
275
+ AND LOWER(status) = 'approved'
276
+ `).all();
277
+ for (const claim of creatorClaims) {
278
+ const identity = this.db.prepare(`SELECT * FROM self_identity WHERE companion_id = ?`).get(claim.companion_id);
279
+ if (identity && !identity.origin) {
280
+ const nameClaim = this.db.prepare(`
310
281
  SELECT value FROM memory_claims
311
282
  WHERE companion_id = ? AND subject = ? AND predicate = 'name' AND LOWER(status) = 'approved'
312
283
  ORDER BY asserted_at DESC LIMIT 1
313
284
  `).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);
285
+ const originName = nameClaim?.value || (claim.subject.startsWith('actor:') ? claim.subject.slice(6) : claim.subject);
286
+ this.db.prepare(`UPDATE self_identity SET origin = ? WHERE companion_id = ?`).run(originName, claim.companion_id);
287
+ }
288
+ const rel = this.db.prepare(`SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?`).get(claim.companion_id, claim.subject);
289
+ if (rel && (rel.role !== 'creator' || rel.stance !== 'familiar_loyal')) {
290
+ 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);
291
+ }
320
292
  }
321
293
  }
322
294
  }
@@ -499,7 +471,7 @@ class SiduriDatabase {
499
471
  });
500
472
  }
501
473
  }
502
- // Single-value directive canonical supersession (e.g. role, language, response style)
474
+ // Single-value directive canonical supersession and direct Self mutation (RFC VX-26-13)
503
475
  const roleMatch = (row.directive || '').match(/^acknowledge role as\s+([^"”'.]+)/i);
504
476
  if (roleMatch) {
505
477
  const existingDirectives = this.getAllDirectives(companionId || 'default');
@@ -511,6 +483,20 @@ class SiduriDatabase {
511
483
  this.supersedeDirective(d.id, companionId);
512
484
  }
513
485
  }
486
+ const newRole = roleMatch[1].trim();
487
+ if (newRole) {
488
+ const targetId = companionId || row.companion_id || 'default';
489
+ const currentIdentity = this.getIdentity(targetId) || {
490
+ companionId: targetId,
491
+ name: '',
492
+ version: '1.0.0',
493
+ updatedAt: new Date().toISOString(),
494
+ };
495
+ currentIdentity.role = newRole;
496
+ currentIdentity.archetype = newRole;
497
+ currentIdentity.updatedAt = new Date().toISOString();
498
+ this.setIdentity(currentIdentity);
499
+ }
514
500
  }
515
501
  // Companion name directive detection: "Address companion as X", "Your name is X", "Call yourself X", "Acknowledge name as X"
516
502
  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 +515,82 @@ class SiduriDatabase {
529
515
  this.setIdentity(currentIdentity);
530
516
  }
531
517
  }
518
+ // Relationship directive direct domain routing:
519
+ // "Recognize <actor> stated relationship as <role>", "Recognize <actor> relationship as <role>", "Recognize <actor> as <role>"
520
+ const relMatch = (row.directive || '').match(/^(?:recognize\s+(\S+)\s+(?:stated\s+)?relationship\s+as|recognize\s+(\S+)\s+as)\s+([^"”'.]+)/i);
521
+ if (relMatch) {
522
+ const rawActor = (relMatch[1] || relMatch[2] || '').trim();
523
+ const roleOrStance = (relMatch[3] || '').trim();
524
+ if (rawActor && roleOrStance) {
525
+ const targetId = companionId || row.companion_id || 'default';
526
+ const isCreator = roleOrStance.toLowerCase().includes('creator');
527
+ const existingRel = this.getRelationship(targetId, rawActor);
528
+ const stance = isCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
529
+ const trustScore = isCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
530
+ const familiarity = isCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
531
+ const interactionConventions = isCreator
532
+ ? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
533
+ : (existingRel?.interactionConventions || []);
534
+ this.upsertRelationship({
535
+ companionId: targetId,
536
+ entityId: rawActor,
537
+ entityType: 'human',
538
+ name: existingRel?.name,
539
+ affiliation: existingRel?.affiliation,
540
+ role: roleOrStance,
541
+ stance,
542
+ trustScore,
543
+ familiarity,
544
+ interactionConventions,
545
+ });
546
+ if (isCreator) {
547
+ const currentIdentity = this.getIdentity(targetId) || {
548
+ companionId: targetId,
549
+ name: '',
550
+ version: '1.0.0',
551
+ updatedAt: new Date().toISOString(),
552
+ };
553
+ const creatorName = existingRel?.name || (rawActor.startsWith('actor:') ? rawActor.slice(6) : rawActor);
554
+ currentIdentity.origin = creatorName !== 'user' && creatorName !== 'primary' ? creatorName : roleOrStance;
555
+ currentIdentity.updatedAt = new Date().toISOString();
556
+ this.setIdentity(currentIdentity);
557
+ }
558
+ }
559
+ }
560
+ // User name address directive direct domain routing: "Address <actor> as <name>"
561
+ const userAddrMatch = (row.directive || '').match(/^address\s+(actor:\S+|\S+)\s+as\s+([^"”'.]+)/i);
562
+ if (userAddrMatch) {
563
+ const actorId = userAddrMatch[1].trim();
564
+ const userName = userAddrMatch[2].trim();
565
+ if (actorId && userName) {
566
+ const targetId = companionId || row.companion_id || 'default';
567
+ const existingRel = this.getRelationship(targetId, actorId);
568
+ const isCreator = existingRel?.role === 'creator' || (existingRel?.stance === 'familiar_loyal' && existingRel?.trustScore === 1.0);
569
+ this.upsertRelationship({
570
+ companionId: targetId,
571
+ entityId: actorId,
572
+ entityType: 'human',
573
+ name: userName,
574
+ affiliation: existingRel?.affiliation,
575
+ role: existingRel?.role || (isCreator ? 'creator' : 'user'),
576
+ stance: existingRel?.stance || (isCreator ? 'familiar_loyal' : 'neutral'),
577
+ trustScore: existingRel?.trustScore ?? (isCreator ? 1.0 : 0.8),
578
+ familiarity: existingRel?.familiarity ?? (isCreator ? 0.9 : 0.5),
579
+ interactionConventions: existingRel?.interactionConventions || [],
580
+ });
581
+ if (isCreator) {
582
+ const currentIdentity = this.getIdentity(targetId) || {
583
+ companionId: targetId,
584
+ name: '',
585
+ version: '1.0.0',
586
+ updatedAt: new Date().toISOString(),
587
+ };
588
+ currentIdentity.origin = userName;
589
+ currentIdentity.updatedAt = new Date().toISOString();
590
+ this.setIdentity(currentIdentity);
591
+ }
592
+ }
593
+ }
532
594
  if (companionId) {
533
595
  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
596
  stmt.run(targetPriority, id, companionId);
@@ -914,27 +976,27 @@ class SiduriDatabase {
914
976
  return res.changes > 0;
915
977
  }
916
978
  // ==========================================
917
- // Memory Domain Methods
979
+ // Archive Domain Methods (RFC VX-26-13: Audited interaction ledger and cold search)
918
980
  // ==========================================
919
- recordEvent(event) {
981
+ recordArchiveEvent(event) {
920
982
  const stmt = this.db.prepare(`
921
- INSERT INTO memory_events (id, companion_id, source_type, occurred_at, payload)
983
+ INSERT INTO archive_events (id, companion_id, source_type, occurred_at, payload)
922
984
  VALUES (?, ?, ?, coalesce(?, datetime('now')), ?)
923
985
  `);
924
- stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, JSON.stringify(event.payload));
986
+ stmt.run(event.id, event.companionId, event.sourceType, event.occurredAt || null, typeof event.payload === 'string' ? event.payload : JSON.stringify(event.payload));
925
987
  }
926
- getRecentEvents(companionId, limit = 50) {
927
- const stmt = this.db.prepare('SELECT * FROM memory_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
988
+ getRecentArchiveEvents(companionId, limit = 50) {
989
+ const stmt = this.db.prepare('SELECT * FROM archive_events WHERE companion_id = ? ORDER BY occurred_at DESC LIMIT ?');
928
990
  return stmt.all(companionId, limit).map((row) => ({
929
991
  id: row.id,
930
992
  companionId: row.companion_id,
931
993
  sourceType: row.source_type,
932
994
  occurredAt: row.occurred_at,
933
- payload: JSON.parse(row.payload)
995
+ payload: safeJsonParse(row.payload, {})
934
996
  }));
935
997
  }
936
- getEvent(id) {
937
- const stmt = this.db.prepare('SELECT * FROM memory_events WHERE id = ?');
998
+ getArchiveEvent(id) {
999
+ const stmt = this.db.prepare('SELECT * FROM archive_events WHERE id = ?');
938
1000
  const row = stmt.get(id);
939
1001
  if (!row)
940
1002
  return undefined;
@@ -943,332 +1005,13 @@ class SiduriDatabase {
943
1005
  companionId: row.companion_id,
944
1006
  sourceType: row.source_type,
945
1007
  occurredAt: row.occurred_at,
946
- payload: JSON.parse(row.payload)
947
- };
948
- }
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,
1008
+ payload: safeJsonParse(row.payload, {})
964
1009
  };
965
1010
  }
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);
1011
+ searchArchiveEvents(companionId, query, limit = 50) {
1012
+ if (!query || !query.trim()) {
1013
+ return this.getRecentArchiveEvents(companionId, limit);
1056
1014
  }
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);
1245
- }
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
1015
  const cleanTokens = query
1273
1016
  .replace(/[^\p{L}\p{N}\s_]/gu, ' ')
1274
1017
  .trim()
@@ -1276,44 +1019,46 @@ class SiduriDatabase {
1276
1019
  .filter((t) => t.length > 0)
1277
1020
  .map((t) => `"${t.replace(/"/g, '""')}"`);
1278
1021
  if (cleanTokens.length === 0)
1279
- return [];
1022
+ return this.getRecentArchiveEvents(companionId, limit);
1280
1023
  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);
1024
+ try {
1025
+ const stmt = this.db.prepare(`
1026
+ SELECT e.* FROM archive_events e
1027
+ JOIN archive_search s ON e.rowid = s.rowid
1028
+ WHERE e.companion_id = ? AND archive_search MATCH ?
1029
+ ORDER BY rank
1030
+ LIMIT ?
1031
+ `);
1032
+ return stmt.all(companionId, ftsQuery, limit).map((row) => ({
1033
+ id: row.id,
1034
+ companionId: row.companion_id,
1035
+ sourceType: row.source_type,
1036
+ occurredAt: row.occurred_at,
1037
+ payload: safeJsonParse(row.payload, {})
1038
+ }));
1039
+ }
1040
+ catch {
1041
+ const stmt = this.db.prepare(`
1042
+ SELECT * FROM archive_events
1043
+ WHERE companion_id = ? AND (source_type LIKE ? OR payload LIKE ?)
1044
+ ORDER BY occurred_at DESC
1045
+ LIMIT ?
1046
+ `);
1047
+ const pattern = `%${query.trim()}%`;
1048
+ return stmt.all(companionId, pattern, pattern, limit).map((row) => ({
1049
+ id: row.id,
1050
+ companionId: row.companion_id,
1051
+ sourceType: row.source_type,
1052
+ occurredAt: row.occurred_at,
1053
+ payload: safeJsonParse(row.payload, {})
1054
+ }));
1055
+ }
1311
1056
  }
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);
1057
+ resetArchive(companionId) {
1058
+ try {
1059
+ this.db.prepare("DELETE FROM archive_events WHERE companion_id = ?").run(companionId);
1060
+ }
1061
+ catch { }
1317
1062
  }
1318
1063
  // --- System Logs ---
1319
1064
  insertLog(entry) {