@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/LICENSE +190 -0
- package/dist/container.d.ts +6 -4
- package/dist/container.js +1 -4
- package/dist/context-retriever.d.ts +3 -2
- package/dist/context-retriever.js +24 -3
- package/dist/conversational-teach.test.js +40 -32
- package/dist/index.d.ts +37 -27
- package/dist/index.js +1 -0
- package/dist/interaction-settler.d.ts +58 -0
- package/dist/interaction-settler.js +177 -0
- package/dist/memory-settler.d.ts +3 -47
- package/dist/memory-settler.js +17 -157
- package/dist/perception-pipeline.d.ts +5 -3
- package/dist/perception-pipeline.js +10 -7
- package/dist/response-envelope.d.ts +1 -1
- package/dist/response-envelope.js +1 -0
- package/dist/runtime-facades.test.js +12 -35
- package/dist/runtime.d.ts +16 -10
- package/dist/runtime.js +85 -107
- package/dist/session-history.d.ts +3 -3
- package/dist/session-history.js +1 -1
- package/dist/siduri-db.d.ts +8 -28
- package/dist/siduri-db.js +179 -434
- package/dist/siduri-db.test.js +113 -281
- package/package.json +7 -7
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
|
-
--
|
|
167
|
-
CREATE TABLE IF NOT EXISTS
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
207
|
-
INSERT INTO
|
|
208
|
-
VALUES('delete', old.rowid, old.
|
|
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
|
|
212
|
-
INSERT INTO
|
|
213
|
-
VALUES('delete', old.rowid, old.
|
|
214
|
-
INSERT INTO
|
|
215
|
-
VALUES (new.rowid, new.
|
|
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
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
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
|
-
//
|
|
979
|
+
// Archive Domain Methods (RFC VX-26-13: Audited interaction ledger and cold search)
|
|
918
980
|
// ==========================================
|
|
919
|
-
|
|
981
|
+
recordArchiveEvent(event) {
|
|
920
982
|
const stmt = this.db.prepare(`
|
|
921
|
-
INSERT INTO
|
|
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
|
-
|
|
927
|
-
const stmt = this.db.prepare('SELECT * FROM
|
|
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:
|
|
995
|
+
payload: safeJsonParse(row.payload, {})
|
|
934
996
|
}));
|
|
935
997
|
}
|
|
936
|
-
|
|
937
|
-
const stmt = this.db.prepare('SELECT * FROM
|
|
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:
|
|
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
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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) {
|