@siduri-x/core 2.0.7 → 2.0.9
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/action-policy.js +9 -9
- package/dist/conversational-teach.test.js +162 -0
- package/dist/input-normalizer.js +26 -10
- package/dist/input-normalizer.test.js +9 -0
- package/dist/memory-settler.js +2 -2
- package/dist/runtime.js +22 -6
- package/dist/siduri-db.js +55 -7
- package/package.json +1 -1
package/dist/action-policy.js
CHANGED
|
@@ -113,7 +113,7 @@ class ActionPolicyEngine {
|
|
|
113
113
|
}
|
|
114
114
|
// Channel check if tool restricts channels
|
|
115
115
|
if (toolDef.allowedChannels && toolDef.allowedChannels.length > 0) {
|
|
116
|
-
const channel = effectiveContext.conversation
|
|
116
|
+
const channel = effectiveContext.conversation?.channel || 'direct';
|
|
117
117
|
if (!toolDef.allowedChannels.includes(channel)) {
|
|
118
118
|
const decision = {
|
|
119
119
|
allowed: false,
|
|
@@ -248,10 +248,10 @@ class ActionPolicyEngine {
|
|
|
248
248
|
providerId,
|
|
249
249
|
parametersHash: paramsHash,
|
|
250
250
|
companionId: effectiveContext.companionId,
|
|
251
|
-
actorId: effectiveContext.actor
|
|
252
|
-
sessionId: effectiveContext.actor.
|
|
253
|
-
channel: effectiveContext.conversation
|
|
254
|
-
correlationId: effectiveContext.conversation.
|
|
251
|
+
actorId: effectiveContext.actor?.actorId || 'owner-user',
|
|
252
|
+
sessionId: effectiveContext.actor?.sessionId || `sess-${effectiveContext.companionId}`,
|
|
253
|
+
channel: effectiveContext.conversation?.channel || 'direct',
|
|
254
|
+
correlationId: effectiveContext.conversation?.correlationId || `corr-${Date.now()}`,
|
|
255
255
|
riskLevel,
|
|
256
256
|
issuedAt,
|
|
257
257
|
expiresAt,
|
|
@@ -413,10 +413,10 @@ class ActionPolicyEngine {
|
|
|
413
413
|
actionId: action.actionId,
|
|
414
414
|
toolName: action.toolName,
|
|
415
415
|
companionId: context?.companionId || 'unknown',
|
|
416
|
-
actorId: context?.actor
|
|
417
|
-
sessionId: context?.actor
|
|
418
|
-
channel: context?.conversation
|
|
419
|
-
correlationId: context?.conversation
|
|
416
|
+
actorId: context?.actor?.actorId,
|
|
417
|
+
sessionId: context?.actor?.sessionId,
|
|
418
|
+
channel: context?.conversation?.channel,
|
|
419
|
+
correlationId: context?.conversation?.correlationId,
|
|
420
420
|
riskLevel: decision?.riskLevel || 'LOW',
|
|
421
421
|
lifecycle,
|
|
422
422
|
decision,
|
|
@@ -122,6 +122,9 @@ describe('Conversational Teach Mode End-to-End Lifecycle', () => {
|
|
|
122
122
|
if (ctx.identity.archetype || ctx.identity.role) {
|
|
123
123
|
parts.push(`- Role: ${ctx.identity.role || ctx.identity.archetype}`);
|
|
124
124
|
}
|
|
125
|
+
if (ctx.identity.origin) {
|
|
126
|
+
parts.push(`- Origin/Created By: ${ctx.identity.origin}`);
|
|
127
|
+
}
|
|
125
128
|
}
|
|
126
129
|
if (ctx.relationship) {
|
|
127
130
|
const nameStr = ctx.relationship.name ? ` [Name: ${ctx.relationship.name}]` : '';
|
|
@@ -640,4 +643,163 @@ describe('Conversational Teach Mode End-to-End Lifecycle', () => {
|
|
|
640
643
|
expect(lastCallCtx.systemPrompt).toContain('Kur Zagin');
|
|
641
644
|
db.close();
|
|
642
645
|
});
|
|
646
|
+
// =========================================================================
|
|
647
|
+
// Test I: Benchmark replication (name, creator, new chat turn-1 recognition)
|
|
648
|
+
// =========================================================================
|
|
649
|
+
it('Test I: reproduces benchmark scenario: teaches name, teaches creator relationship, and verifies new session recognizes creator in origin and relationship', async () => {
|
|
650
|
+
const db = new index_1.SiduriDatabase({ dbPath });
|
|
651
|
+
const companionId = 'comp-test-i';
|
|
652
|
+
const self = createSelfRepository(db);
|
|
653
|
+
const memory = createMemoryOrgan(db);
|
|
654
|
+
const behavior = createBehaviorCompiler();
|
|
655
|
+
const mockBrain = {
|
|
656
|
+
generatePlan: jest.fn().mockImplementation(async (brainCtx) => {
|
|
657
|
+
const lastMsg = brainCtx.recentMessages?.[brainCtx.recentMessages.length - 1]?.content || '';
|
|
658
|
+
const memoryProposals = [];
|
|
659
|
+
const behaviorProposals = [];
|
|
660
|
+
if (/your name is Siduri/i.test(lastMsg)) {
|
|
661
|
+
memoryProposals.push({
|
|
662
|
+
subject: 'companion:self',
|
|
663
|
+
predicate: 'name',
|
|
664
|
+
value: 'Siduri',
|
|
665
|
+
});
|
|
666
|
+
behaviorProposals.push({
|
|
667
|
+
directive: 'Address self as Siduri',
|
|
668
|
+
category: 'relational',
|
|
669
|
+
subject: 'companion:self',
|
|
670
|
+
predicate: 'name',
|
|
671
|
+
value: 'Siduri',
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
if (/i am Kur Zagin, your creator/i.test(lastMsg)) {
|
|
675
|
+
memoryProposals.push({
|
|
676
|
+
subject: 'actor:kur_zagin',
|
|
677
|
+
predicate: 'name',
|
|
678
|
+
value: 'Kur Zagin',
|
|
679
|
+
});
|
|
680
|
+
memoryProposals.push({
|
|
681
|
+
subject: 'actor:kur_zagin',
|
|
682
|
+
predicate: 'stated_relationship',
|
|
683
|
+
value: 'creator of companion Siduri',
|
|
684
|
+
});
|
|
685
|
+
behaviorProposals.push({
|
|
686
|
+
directive: 'Acknowledge actor:kur_zagin as creator of companion Siduri',
|
|
687
|
+
category: 'relational',
|
|
688
|
+
subject: 'actor:kur_zagin',
|
|
689
|
+
predicate: 'stated_relationship',
|
|
690
|
+
value: 'creator of companion Siduri',
|
|
691
|
+
});
|
|
692
|
+
behaviorProposals.push({
|
|
693
|
+
directive: 'Address actor:kur_zagin as Kur Zagin',
|
|
694
|
+
category: 'behavioral',
|
|
695
|
+
subject: 'actor:kur_zagin',
|
|
696
|
+
predicate: 'name',
|
|
697
|
+
value: 'Kur Zagin',
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
speech: 'I understand and acknowledge.',
|
|
702
|
+
language: 'en',
|
|
703
|
+
memoryProposals: memoryProposals.length > 0 ? memoryProposals : undefined,
|
|
704
|
+
behaviorProposals: behaviorProposals.length > 0 ? behaviorProposals : undefined,
|
|
705
|
+
_receivedSystemPrompt: brainCtx.systemPrompt,
|
|
706
|
+
};
|
|
707
|
+
}),
|
|
708
|
+
};
|
|
709
|
+
const runtime = new index_1.SiduriRuntime(companionId, { name: 'Siduri' }, {
|
|
710
|
+
brain: mockBrain,
|
|
711
|
+
memory,
|
|
712
|
+
self,
|
|
713
|
+
behavior,
|
|
714
|
+
});
|
|
715
|
+
await runtime.initialize();
|
|
716
|
+
// Session 1 - Turn 1: "your name is Siduri"
|
|
717
|
+
const res1 = await runtime.processPerception({
|
|
718
|
+
source: 'text_chat',
|
|
719
|
+
text: 'your name is Siduri',
|
|
720
|
+
context: createRequestContext(companionId, 'teach', 'owner-user'),
|
|
721
|
+
});
|
|
722
|
+
expect(res1.status).toBe('APPROVED');
|
|
723
|
+
const nameProp = res1.metadata?.proposals?.find((p) => p.predicate === 'name');
|
|
724
|
+
expect(nameProp).toBeDefined();
|
|
725
|
+
await runtime.approveProposal(nameProp.id, { companionId });
|
|
726
|
+
// Verify companion name in self_identity
|
|
727
|
+
const idStep1 = await self.getIdentity(companionId);
|
|
728
|
+
expect(idStep1?.name).toBe('Siduri');
|
|
729
|
+
// Session 1 - Turn 2: "i am Kur Zagin, your creator"
|
|
730
|
+
const res2 = await runtime.processPerception({
|
|
731
|
+
source: 'text_chat',
|
|
732
|
+
text: 'i am Kur Zagin, your creator',
|
|
733
|
+
context: createRequestContext(companionId, 'teach', 'owner-user'),
|
|
734
|
+
});
|
|
735
|
+
expect(res2.status).toBe('APPROVED');
|
|
736
|
+
// Approve both proposals (name and creator of companion Siduri)
|
|
737
|
+
const proposals2 = res2.metadata?.proposals || [];
|
|
738
|
+
const creatorProp = proposals2.find((p) => p.predicate === 'stated_relationship');
|
|
739
|
+
const userProp = proposals2.find((p) => p.predicate === 'name');
|
|
740
|
+
expect(creatorProp).toBeDefined();
|
|
741
|
+
expect(userProp).toBeDefined();
|
|
742
|
+
await runtime.approveProposal(creatorProp.id, { companionId });
|
|
743
|
+
await runtime.approveProposal(userProp.id, { companionId });
|
|
744
|
+
// Also approve the behavioral directives
|
|
745
|
+
const dirProps = res2.metadata?.behavioral_proposals || [];
|
|
746
|
+
for (const d of dirProps) {
|
|
747
|
+
if (d.directive_id) {
|
|
748
|
+
await runtime.approveDirective(d.directive_id, { companionId });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// Verify self_identity.origin has been dual-promoted!
|
|
752
|
+
const idStep2 = await self.getIdentity(companionId);
|
|
753
|
+
expect(idStep2?.name).toBe('Siduri');
|
|
754
|
+
expect(idStep2?.origin).toBe('Kur Zagin');
|
|
755
|
+
// Verify relationship has creator role, loyal stance, and user name
|
|
756
|
+
const relStep2 = await self.getRelationship(companionId, 'actor:kur_zagin');
|
|
757
|
+
expect(relStep2).toBeDefined();
|
|
758
|
+
expect(relStep2?.role).toBe('creator');
|
|
759
|
+
expect(relStep2?.stance).toBe('familiar_loyal');
|
|
760
|
+
expect(relStep2?.trustScore).toBe(1.0);
|
|
761
|
+
expect(relStep2?.name).toBe('Kur Zagin');
|
|
762
|
+
// Verify single-owner fallback: an incoming request with 'owner-user' gets the primary relationship!
|
|
763
|
+
const relOwnerUser = await self.getRelationship(companionId, 'owner-user');
|
|
764
|
+
expect(relOwnerUser).toBeDefined();
|
|
765
|
+
expect(relOwnerUser?.name).toBe('Kur Zagin');
|
|
766
|
+
expect(relOwnerUser?.role).toBe('creator');
|
|
767
|
+
expect(relOwnerUser?.stance).toBe('familiar_loyal');
|
|
768
|
+
// =======================================================================
|
|
769
|
+
// Session 2 ("New Chat"): user opens a fresh session as 'owner-user'
|
|
770
|
+
// =======================================================================
|
|
771
|
+
// Turn 1: "hey, who are you?"
|
|
772
|
+
await runtime.processPerception({
|
|
773
|
+
source: 'text_chat',
|
|
774
|
+
text: 'hey, who are you?',
|
|
775
|
+
context: createRequestContext(companionId, 'hybrid', 'owner-user'),
|
|
776
|
+
});
|
|
777
|
+
const calls = mockBrain.generatePlan.mock.calls;
|
|
778
|
+
const newChatTurn1Ctx = calls[calls.length - 1][0];
|
|
779
|
+
// Identity nucleus has origin
|
|
780
|
+
expect(newChatTurn1Ctx.systemPrompt).toContain('Name: Siduri');
|
|
781
|
+
expect(newChatTurn1Ctx.systemPrompt).toContain('Origin/Created By: Kur Zagin');
|
|
782
|
+
// Relationship stance recognizes Kur Zagin as creator
|
|
783
|
+
expect(newChatTurn1Ctx.systemPrompt).toContain('Kur Zagin');
|
|
784
|
+
expect(newChatTurn1Ctx.systemPrompt).toContain('familiar_loyal');
|
|
785
|
+
// Turn 2: "who is your creator?"
|
|
786
|
+
await runtime.processPerception({
|
|
787
|
+
source: 'text_chat',
|
|
788
|
+
text: 'who is your creator?',
|
|
789
|
+
context: createRequestContext(companionId, 'hybrid', 'owner-user'),
|
|
790
|
+
});
|
|
791
|
+
const newChatTurn2Ctx = calls[calls.length - 1][0];
|
|
792
|
+
expect(newChatTurn2Ctx.systemPrompt).toContain('Origin/Created By: Kur Zagin');
|
|
793
|
+
// Turn 3: From an anonymous guest session:
|
|
794
|
+
// Should NOT get the personal relationship stance, but Identity origin remains!
|
|
795
|
+
await runtime.processPerception({
|
|
796
|
+
source: 'text_chat',
|
|
797
|
+
text: 'who is your creator?',
|
|
798
|
+
context: createRequestContext(companionId, 'hybrid', 'anonymous-session'),
|
|
799
|
+
});
|
|
800
|
+
const guestTurnCtx = calls[calls.length - 1][0];
|
|
801
|
+
expect(guestTurnCtx.systemPrompt).toContain('Origin/Created By: Kur Zagin');
|
|
802
|
+
expect(guestTurnCtx.systemPrompt).not.toContain('Stance toward anonymous-session [Name: Kur Zagin]');
|
|
803
|
+
db.close();
|
|
804
|
+
});
|
|
643
805
|
});
|
package/dist/input-normalizer.js
CHANGED
|
@@ -20,21 +20,37 @@ async function normalizeUserInput(message, roleOrContext = 'OWNER', history = []
|
|
|
20
20
|
? 'VIEWER'
|
|
21
21
|
: 'OWNER')
|
|
22
22
|
: roleOrContext;
|
|
23
|
+
const defaultActor = {
|
|
24
|
+
actorId: role === 'VIEWER' ? 'anonymous-session' : 'owner-user',
|
|
25
|
+
sessionId: `sess-${companionId}`,
|
|
26
|
+
authorizationRole: role === 'VIEWER' ? 'viewer' : 'administrator',
|
|
27
|
+
capabilities: role === 'VIEWER' ? ['chat'] : ['chat', 'memory:approve', 'action:execute'],
|
|
28
|
+
authenticated: role !== 'VIEWER',
|
|
29
|
+
};
|
|
30
|
+
const defaultConversation = {
|
|
31
|
+
channel: 'direct',
|
|
32
|
+
correlationId: `corr-${Date.now()}`,
|
|
33
|
+
};
|
|
23
34
|
const requestContext = isContextObject
|
|
24
|
-
?
|
|
25
|
-
|
|
26
|
-
companionId,
|
|
35
|
+
? {
|
|
36
|
+
companionId: roleOrContext.companionId || companionId,
|
|
27
37
|
actor: {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
authorizationRole: role === 'VIEWER' ? 'viewer' : 'administrator',
|
|
31
|
-
capabilities: role === 'VIEWER' ? ['chat'] : ['chat', 'memory:approve', 'action:execute'],
|
|
32
|
-
authenticated: role !== 'VIEWER',
|
|
38
|
+
...defaultActor,
|
|
39
|
+
...(roleOrContext.actor || {}),
|
|
33
40
|
},
|
|
34
41
|
conversation: {
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
...defaultConversation,
|
|
43
|
+
...(roleOrContext.conversation || {}),
|
|
37
44
|
},
|
|
45
|
+
source: roleOrContext.source || 'local',
|
|
46
|
+
...(roleOrContext.mode ? { mode: roleOrContext.mode } : {}),
|
|
47
|
+
...(roleOrContext.subject ? { subject: roleOrContext.subject } : {}),
|
|
48
|
+
...(roleOrContext.metadata ? { metadata: roleOrContext.metadata } : {}),
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
companionId,
|
|
52
|
+
actor: defaultActor,
|
|
53
|
+
conversation: defaultConversation,
|
|
38
54
|
source: 'local',
|
|
39
55
|
};
|
|
40
56
|
// Universal Perception: Route user input through EarOrgan if available
|
|
@@ -36,4 +36,13 @@ describe('InputNormalizer', () => {
|
|
|
36
36
|
expect(mockEar.listen).toHaveBeenCalledWith('text_chat', 'raw text', expect.anything());
|
|
37
37
|
expect(result.perceivedText).toBe('transcribed text');
|
|
38
38
|
});
|
|
39
|
+
test('safely normalizes partial context object (e.g. { mode: "hybrid" }) with default actor and conversation', async () => {
|
|
40
|
+
const result = await (0, input_normalizer_1.normalizeUserInput)('test query', { mode: 'hybrid' }, [], 'comp-2');
|
|
41
|
+
expect(result.requestContext.conversation).toBeDefined();
|
|
42
|
+
expect(result.requestContext.conversation.channel).toBe('direct');
|
|
43
|
+
expect(result.requestContext.conversation.correlationId).toBeDefined();
|
|
44
|
+
expect(result.requestContext.actor).toBeDefined();
|
|
45
|
+
expect(result.requestContext.actor.actorId).toBe('owner-user');
|
|
46
|
+
expect(result.requestContext.mode).toBe('hybrid');
|
|
47
|
+
});
|
|
39
48
|
});
|
package/dist/memory-settler.js
CHANGED
|
@@ -32,8 +32,8 @@ async function settleMemoryProposals(params) {
|
|
|
32
32
|
message: perceivedText,
|
|
33
33
|
role,
|
|
34
34
|
companionId,
|
|
35
|
-
actorId: requestContext.actor
|
|
36
|
-
channel: requestContext.conversation
|
|
35
|
+
actorId: requestContext.actor?.actorId || 'owner-user',
|
|
36
|
+
channel: requestContext.conversation?.channel || 'direct',
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
39
|
await memory.addSourceEvent(sourceEvent);
|
package/dist/runtime.js
CHANGED
|
@@ -273,19 +273,22 @@ async function promoteApprovedClaimToSelf(claim, self, companionId) {
|
|
|
273
273
|
predicate === 'preferred_address' ||
|
|
274
274
|
predicate === 'affiliation') {
|
|
275
275
|
const rawSubject = (claim.subject || 'actor:user').replace(/^actor:actor:/, 'actor:');
|
|
276
|
-
const isCreator = value.toLowerCase()
|
|
276
|
+
const isCreator = value.toLowerCase().includes('creator');
|
|
277
277
|
const isName = predicate === 'name' || predicate === 'preferred_address';
|
|
278
278
|
const isAffil = predicate === 'affiliation';
|
|
279
279
|
const existingRel = typeof self.getRelationship === 'function'
|
|
280
280
|
? await self.getRelationship(targetCompanionId, rawSubject)
|
|
281
281
|
: null;
|
|
282
|
-
const
|
|
282
|
+
const isPriorCreator = existingRel?.role === 'creator' || (existingRel?.stance === 'familiar_loyal' && existingRel.trustScore === 1.0);
|
|
283
|
+
const role = isCreator
|
|
284
|
+
? 'creator'
|
|
285
|
+
: (isPriorCreator ? 'creator' : (existingRel?.role && existingRel.role !== 'user' ? existingRel.role : (isName || isAffil ? existingRel?.role || 'user' : value)));
|
|
283
286
|
const name = isName ? value : existingRel?.name;
|
|
284
287
|
const affiliation = isAffil ? value : existingRel?.affiliation;
|
|
285
|
-
const stance = isCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
|
|
286
|
-
const trustScore = isCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
|
|
287
|
-
const familiarity = isCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
|
|
288
|
-
const interactionConventions = isCreator
|
|
288
|
+
const stance = isCreator || isPriorCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
|
|
289
|
+
const trustScore = isCreator || isPriorCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
|
|
290
|
+
const familiarity = isCreator || isPriorCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
|
|
291
|
+
const interactionConventions = isCreator || isPriorCreator
|
|
289
292
|
? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
|
|
290
293
|
: (existingRel?.interactionConventions || []);
|
|
291
294
|
await self.updateRelationship(targetCompanionId, {
|
|
@@ -300,6 +303,19 @@ async function promoteApprovedClaimToSelf(claim, self, companionId) {
|
|
|
300
303
|
familiarity,
|
|
301
304
|
interactionConventions,
|
|
302
305
|
});
|
|
306
|
+
// Dual promotion: If this actor is established as creator, also populate companion's origin in self_identity
|
|
307
|
+
if (isCreator || (isName && isPriorCreator)) {
|
|
308
|
+
const existingIdentity = (await self.getIdentity(targetCompanionId)) || {
|
|
309
|
+
companionId: targetCompanionId,
|
|
310
|
+
name: 'Siduri',
|
|
311
|
+
version: '1.0.0',
|
|
312
|
+
updatedAt: new Date().toISOString(),
|
|
313
|
+
};
|
|
314
|
+
const creatorName = name || existingRel?.name || (rawSubject.startsWith('actor:') && rawSubject !== 'actor:user' && rawSubject !== 'actor:primary' ? rawSubject.slice(6) : value);
|
|
315
|
+
existingIdentity.origin = creatorName !== 'user' && creatorName !== 'primary' ? creatorName : value;
|
|
316
|
+
existingIdentity.updatedAt = new Date().toISOString();
|
|
317
|
+
await self.setIdentity(existingIdentity);
|
|
318
|
+
}
|
|
303
319
|
if (isName) {
|
|
304
320
|
await self.commitDirectives(targetCompanionId, [
|
|
305
321
|
{
|
package/dist/siduri-db.js
CHANGED
|
@@ -236,6 +236,34 @@ class SiduriDatabase {
|
|
|
236
236
|
catch {
|
|
237
237
|
// Column already exists
|
|
238
238
|
}
|
|
239
|
+
try {
|
|
240
|
+
// Reconcile creator claims that may have been recorded before origin dual-promotion
|
|
241
|
+
const creatorClaims = this.db.prepare(`
|
|
242
|
+
SELECT * FROM memory_claims
|
|
243
|
+
WHERE predicate = 'stated_relationship'
|
|
244
|
+
AND LOWER(value) LIKE '%creator%'
|
|
245
|
+
AND LOWER(status) = 'approved'
|
|
246
|
+
`).all();
|
|
247
|
+
for (const claim of creatorClaims) {
|
|
248
|
+
const identity = this.db.prepare(`SELECT * FROM self_identity WHERE companion_id = ?`).get(claim.companion_id);
|
|
249
|
+
if (identity && !identity.origin) {
|
|
250
|
+
const nameClaim = this.db.prepare(`
|
|
251
|
+
SELECT value FROM memory_claims
|
|
252
|
+
WHERE companion_id = ? AND subject = ? AND predicate = 'name' AND LOWER(status) = 'approved'
|
|
253
|
+
ORDER BY asserted_at DESC LIMIT 1
|
|
254
|
+
`).get(claim.companion_id, claim.subject);
|
|
255
|
+
const originName = nameClaim?.value || (claim.subject.startsWith('actor:') ? claim.subject.slice(6) : claim.subject);
|
|
256
|
+
this.db.prepare(`UPDATE self_identity SET origin = ? WHERE companion_id = ?`).run(originName, claim.companion_id);
|
|
257
|
+
}
|
|
258
|
+
const rel = this.db.prepare(`SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?`).get(claim.companion_id, claim.subject);
|
|
259
|
+
if (rel && (rel.role !== 'creator' || rel.stance !== 'familiar_loyal')) {
|
|
260
|
+
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);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Best-effort auto-reconciliation
|
|
266
|
+
}
|
|
239
267
|
}
|
|
240
268
|
close() {
|
|
241
269
|
this.db.close();
|
|
@@ -446,7 +474,11 @@ class SiduriDatabase {
|
|
|
446
474
|
const stripped = entityId.startsWith('actor:') ? entityId.slice(6) : entityId;
|
|
447
475
|
const prefixed = entityId.startsWith('actor:') ? entityId : `actor:${entityId}`;
|
|
448
476
|
const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND (entity_id = ? OR entity_id = ? OR entity_id = ?)');
|
|
449
|
-
|
|
477
|
+
let row = stmt.get(companionId, entityId, stripped, prefixed);
|
|
478
|
+
if (!row && (entityId === 'owner-user' || entityId === 'local-user' || entityId === 'owner' || entityId === 'primary' || entityId === 'user')) {
|
|
479
|
+
const fallbackStmt = this.db.prepare("SELECT * FROM self_relationships WHERE companion_id = ? AND entity_type = 'human' ORDER BY updated_at DESC LIMIT 1");
|
|
480
|
+
row = fallbackStmt.get(companionId);
|
|
481
|
+
}
|
|
450
482
|
if (!row)
|
|
451
483
|
return undefined;
|
|
452
484
|
return {
|
|
@@ -789,17 +821,20 @@ class SiduriDatabase {
|
|
|
789
821
|
predicate === 'preferred_address' ||
|
|
790
822
|
predicate === 'affiliation') {
|
|
791
823
|
const rawSubject = (claim.subject || 'actor:user').replace(/^actor:actor:/, 'actor:');
|
|
792
|
-
const isCreator = value.toLowerCase()
|
|
824
|
+
const isCreator = value.toLowerCase().includes('creator');
|
|
793
825
|
const isName = predicate === 'name' || predicate === 'preferred_address';
|
|
794
826
|
const isAffil = predicate === 'affiliation';
|
|
795
827
|
const existingRel = this.getRelationship(companionId, rawSubject);
|
|
796
|
-
const
|
|
828
|
+
const isPriorCreator = existingRel?.role === 'creator' || (existingRel?.stance === 'familiar_loyal' && existingRel.trustScore === 1.0);
|
|
829
|
+
const role = isCreator
|
|
830
|
+
? 'creator'
|
|
831
|
+
: (isPriorCreator ? 'creator' : (existingRel?.role && existingRel.role !== 'user' ? existingRel.role : (isName || isAffil ? existingRel?.role || 'user' : value)));
|
|
797
832
|
const name = isName ? value : existingRel?.name;
|
|
798
833
|
const affiliation = isAffil ? value : existingRel?.affiliation;
|
|
799
|
-
const stance = isCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
|
|
800
|
-
const trustScore = isCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
|
|
801
|
-
const familiarity = isCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
|
|
802
|
-
const interactionConventions = isCreator
|
|
834
|
+
const stance = isCreator || isPriorCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
|
|
835
|
+
const trustScore = isCreator || isPriorCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
|
|
836
|
+
const familiarity = isCreator || isPriorCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
|
|
837
|
+
const interactionConventions = isCreator || isPriorCreator
|
|
803
838
|
? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
|
|
804
839
|
: (existingRel?.interactionConventions || []);
|
|
805
840
|
this.upsertRelationship({
|
|
@@ -814,6 +849,19 @@ class SiduriDatabase {
|
|
|
814
849
|
familiarity,
|
|
815
850
|
interactionConventions,
|
|
816
851
|
});
|
|
852
|
+
// Dual promotion: If this actor is established as creator, also populate companion's origin in self_identity
|
|
853
|
+
if (isCreator || (isName && isPriorCreator)) {
|
|
854
|
+
const existingIdentity = this.getIdentity(companionId) || {
|
|
855
|
+
companionId,
|
|
856
|
+
name: 'Siduri',
|
|
857
|
+
version: '1.0.0',
|
|
858
|
+
updatedAt: new Date().toISOString(),
|
|
859
|
+
};
|
|
860
|
+
const creatorName = name || existingRel?.name || (rawSubject.startsWith('actor:') && rawSubject !== 'actor:user' && rawSubject !== 'actor:primary' ? rawSubject.slice(6) : value);
|
|
861
|
+
existingIdentity.origin = creatorName !== 'user' && creatorName !== 'primary' ? creatorName : value;
|
|
862
|
+
existingIdentity.updatedAt = new Date().toISOString();
|
|
863
|
+
this.setIdentity(existingIdentity);
|
|
864
|
+
}
|
|
817
865
|
if (isName) {
|
|
818
866
|
this.commitDirective({
|
|
819
867
|
id: `dir-name-${claim.id || Date.now()}`,
|
package/package.json
CHANGED