@siduri-x/core 1.0.7 → 1.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.test.js +0 -1
- package/dist/adversarial.test.js +11 -15
- package/dist/capability.test.js +0 -1
- package/dist/context-retriever.js +0 -2
- package/dist/context.d.ts +1 -2
- package/dist/dispatcher.test.js +0 -2
- package/dist/evidence.d.ts +1 -4
- package/dist/evidence.js +2 -22
- package/dist/evidence.test.js +11 -37
- package/dist/experience-emitter.js +0 -1
- package/dist/experience.d.ts +0 -2
- package/dist/experience.js +0 -5
- package/dist/experience.test.js +0 -4
- package/dist/gating.d.ts +0 -1
- package/dist/gating.js +0 -2
- package/dist/gating.test.js +9 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/intent-classifier.test.js +0 -1
- package/dist/memory-settler.js +0 -3
- package/dist/perception-cycle.test.js +2 -4
- package/dist/prompt-compiler.js +0 -3
- package/dist/prompt-compiler.test.js +0 -1
- package/dist/proposals.d.ts +0 -1
- package/dist/runtime-facades.test.js +26 -0
- package/dist/runtime.d.ts +7 -1
- package/dist/runtime.js +12 -2
- package/dist/schema-validator.d.ts +9 -0
- package/dist/schema-validator.js +107 -0
- package/dist/schema-validator.test.d.ts +1 -0
- package/dist/schema-validator.test.js +124 -0
- package/dist/sqlite-action-store.test.js +0 -1
- package/dist/teaching.d.ts +2 -2
- package/dist/teaching.js +5 -32
- package/package.json +1 -1
package/dist/adversarial.test.js
CHANGED
|
@@ -14,7 +14,6 @@ describe('Adversarial Hardening Verification Suite (Phase 3)', () => {
|
|
|
14
14
|
},
|
|
15
15
|
conversation: {
|
|
16
16
|
channel: 'private',
|
|
17
|
-
audienceId: 'audience-owner',
|
|
18
17
|
correlationId: 'corr-adv-1',
|
|
19
18
|
},
|
|
20
19
|
};
|
|
@@ -29,7 +28,6 @@ describe('Adversarial Hardening Verification Suite (Phase 3)', () => {
|
|
|
29
28
|
},
|
|
30
29
|
conversation: {
|
|
31
30
|
channel: 'public',
|
|
32
|
-
audienceId: 'audience-public',
|
|
33
31
|
correlationId: 'corr-adv-2',
|
|
34
32
|
},
|
|
35
33
|
};
|
|
@@ -86,7 +84,7 @@ describe('Adversarial Hardening Verification Suite (Phase 3)', () => {
|
|
|
86
84
|
...baseOwnerContext,
|
|
87
85
|
companionId: 'companion-A',
|
|
88
86
|
});
|
|
89
|
-
expect(mockMemory.searchClaims).toHaveBeenCalledWith('Query', expect.objectContaining({
|
|
87
|
+
expect(mockMemory.searchClaims).toHaveBeenCalledWith('Query', expect.objectContaining({ limit: 5 }), 5);
|
|
90
88
|
});
|
|
91
89
|
});
|
|
92
90
|
// INVARIANT 2: Authority & Request Boundary
|
|
@@ -378,41 +376,39 @@ describe('Adversarial Hardening Verification Suite (Phase 3)', () => {
|
|
|
378
376
|
describe('Invariant 8: Response Gating Evidence Admissibility Semantics', () => {
|
|
379
377
|
test('gate strictly enforces evidence admissibility and disclosure without claiming unverified factuality', () => {
|
|
380
378
|
const gating = new index_1.ResponseGatingEngine();
|
|
381
|
-
const
|
|
379
|
+
const validEvidence = {
|
|
382
380
|
evidenceId: 'ev-pub-1',
|
|
383
381
|
sourceId: 'src-facts',
|
|
384
382
|
origin: 'knowledge',
|
|
385
383
|
trust: 'configured',
|
|
386
384
|
sensitivity: 'public',
|
|
387
|
-
allowedAudiences: ['audience-public'],
|
|
388
385
|
companionId: 'companion-adv',
|
|
389
386
|
correlationId: 'corr-adv-1',
|
|
390
387
|
createdAt: new Date().toISOString(),
|
|
391
388
|
};
|
|
392
|
-
const
|
|
393
|
-
evidenceId: 'ev-
|
|
389
|
+
const foreignCompanionEvidence = {
|
|
390
|
+
evidenceId: 'ev-foreign-1',
|
|
394
391
|
sourceId: 'src-secrets',
|
|
395
392
|
origin: 'knowledge',
|
|
396
393
|
trust: 'configured',
|
|
397
394
|
sensitivity: 'restricted',
|
|
398
|
-
|
|
399
|
-
companionId: 'companion-adv',
|
|
395
|
+
companionId: 'foreign-companion',
|
|
400
396
|
correlationId: 'corr-adv-1',
|
|
401
397
|
createdAt: new Date().toISOString(),
|
|
402
398
|
};
|
|
403
|
-
// Staged for
|
|
399
|
+
// Staged for companion-adv with both valid and foreign-companion evidence attached
|
|
404
400
|
const staged = gating.stageResponse({
|
|
405
|
-
requestContext:
|
|
401
|
+
requestContext: baseOwnerContext,
|
|
406
402
|
candidateSpeech: 'Siduri was created in 1840 by aliens.',
|
|
407
403
|
candidateLanguage: 'en',
|
|
408
|
-
evidenceRecords: [
|
|
404
|
+
evidenceRecords: [validEvidence, foreignCompanionEvidence],
|
|
409
405
|
});
|
|
410
|
-
const evaluation = gating.evaluateGate(staged, [
|
|
406
|
+
const evaluation = gating.evaluateGate(staged, [validEvidence, foreignCompanionEvidence]);
|
|
411
407
|
expect(evaluation.admissible).toBe(true);
|
|
412
408
|
expect(evaluation.reasonCode).toBe('APPROVED_DIRECT');
|
|
413
|
-
//
|
|
409
|
+
// Valid companion evidence admitted, foreign companion evidence excluded by isolation boundary
|
|
414
410
|
expect(evaluation.filteredEvidenceIds).toEqual(['ev-pub-1']);
|
|
415
|
-
expect(evaluation.filteredEvidenceIds).not.toContain('ev-
|
|
411
|
+
expect(evaluation.filteredEvidenceIds).not.toContain('ev-foreign-1');
|
|
416
412
|
});
|
|
417
413
|
});
|
|
418
414
|
// INVARIANT 9: Full-Field Tamper-Evident Audit Trail
|
package/dist/capability.test.js
CHANGED
|
@@ -9,8 +9,6 @@ async function retrieveRuntimeContext(params) {
|
|
|
9
9
|
const { companionId, perceivedText, requestContext, role, isContextObject, shouldQueryKnowledge, knowledge, memory, } = params;
|
|
10
10
|
const queryOptions = isContextObject
|
|
11
11
|
? {
|
|
12
|
-
channel: requestContext.conversation.channel,
|
|
13
|
-
audienceId: requestContext.conversation.audienceId,
|
|
14
12
|
limit: 5,
|
|
15
13
|
}
|
|
16
14
|
: role;
|
package/dist/context.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface ConversationContext {
|
|
|
9
9
|
correlationId: string;
|
|
10
10
|
sessionId?: string;
|
|
11
11
|
channel?: string;
|
|
12
|
-
audienceId?: string;
|
|
13
12
|
[key: string]: unknown;
|
|
14
13
|
}
|
|
15
14
|
export type SubjectKind = 'actor' | 'companion' | 'configured';
|
|
@@ -27,7 +26,7 @@ export interface RequestContext {
|
|
|
27
26
|
metadata?: Record<string, unknown>;
|
|
28
27
|
}
|
|
29
28
|
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped';
|
|
30
|
-
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'FORBIDDEN_CONTEXT' | '
|
|
29
|
+
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'FORBIDDEN_CONTEXT' | 'AMBIGUOUS_CONTEXT' | 'UNAUTHORIZED_CAPABILITY';
|
|
31
30
|
export interface ContextError {
|
|
32
31
|
code: ContextErrorCode;
|
|
33
32
|
message?: string;
|
package/dist/dispatcher.test.js
CHANGED
|
@@ -33,7 +33,6 @@ describe('T5 ExperienceDispatcher Contract Suite', () => {
|
|
|
33
33
|
companionId: 'companion-a',
|
|
34
34
|
correlationId: 'corr-1',
|
|
35
35
|
channel: 'public',
|
|
36
|
-
audienceId: 'audience-public',
|
|
37
36
|
speech: 'Hello dispatch',
|
|
38
37
|
language: 'en',
|
|
39
38
|
});
|
|
@@ -50,7 +49,6 @@ describe('T5 ExperienceDispatcher Contract Suite', () => {
|
|
|
50
49
|
companionId: 'companion-a',
|
|
51
50
|
correlationId: 'corr-1',
|
|
52
51
|
channel: 'public',
|
|
53
|
-
audienceId: 'audience-public',
|
|
54
52
|
speech: 'Hello empty',
|
|
55
53
|
});
|
|
56
54
|
const summary = await emptyDispatcher.dispatchEvents(events);
|
package/dist/evidence.d.ts
CHANGED
|
@@ -16,13 +16,12 @@ export interface EvidenceRecord {
|
|
|
16
16
|
expiresAt?: string;
|
|
17
17
|
trust: EvidenceTrust;
|
|
18
18
|
sensitivity?: EvidenceSensitivity;
|
|
19
|
-
allowedAudiences?: string[];
|
|
20
19
|
companionId: string;
|
|
21
20
|
correlationId: string;
|
|
22
21
|
[key: string]: unknown;
|
|
23
22
|
}
|
|
24
23
|
export type ResponseApprovalStatus = 'STAGED' | 'APPROVED' | 'REJECTED' | 'EXPIRED' | 'EMITTED';
|
|
25
|
-
export type ResponseGateReasonCode = 'APPROVED_DIRECT' | 'APPROVAL_REQUIRED' | 'UNKNOWN_APPROVAL_ID' | 'APPROVAL_ID_MISMATCH' | 'COMPANION_MISMATCH' | '
|
|
24
|
+
export type ResponseGateReasonCode = 'APPROVED_DIRECT' | 'APPROVAL_REQUIRED' | 'UNKNOWN_APPROVAL_ID' | 'APPROVAL_ID_MISMATCH' | 'COMPANION_MISMATCH' | 'EVIDENCE_EXPIRED' | 'EVIDENCE_SENSITIVITY_EXCLUDED' | 'UNRESOLVED_LOW_CONFIDENCE' | 'EMPTY_SPEECH' | 'PROPOSAL_VALIDATION_FAILED' | 'EXPLICITLY_REJECTED';
|
|
26
25
|
export interface ResponseCitation {
|
|
27
26
|
sourceId: string;
|
|
28
27
|
documentId?: string;
|
|
@@ -35,7 +34,6 @@ export interface StagedResponsePlan {
|
|
|
35
34
|
companionId: string;
|
|
36
35
|
correlationId: string;
|
|
37
36
|
channel?: string;
|
|
38
|
-
audienceId?: string;
|
|
39
37
|
speech: string;
|
|
40
38
|
language: string;
|
|
41
39
|
evidenceIds: string[];
|
|
@@ -63,7 +61,6 @@ export interface ResponseGateEvaluation {
|
|
|
63
61
|
export interface EvidenceFilterOptions {
|
|
64
62
|
companionId: string;
|
|
65
63
|
channel?: string;
|
|
66
|
-
audienceId?: string;
|
|
67
64
|
now?: string | Date;
|
|
68
65
|
[key: string]: unknown;
|
|
69
66
|
}
|
package/dist/evidence.js
CHANGED
|
@@ -19,28 +19,8 @@ function filterEvidenceRecords(records, options) {
|
|
|
19
19
|
continue;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
record.allowedAudiences &&
|
|
25
|
-
record.allowedAudiences.length > 0 &&
|
|
26
|
-
!record.allowedAudiences.includes('audience-public') &&
|
|
27
|
-
!record.allowedAudiences.includes(options.audienceId)) {
|
|
28
|
-
excluded.push({ record, reason: 'audience_not_allowed' });
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
// 4. Optional channel sensitivity policy (if channel explicitly specified)
|
|
32
|
-
if (options.channel === 'public') {
|
|
33
|
-
if (record.sensitivity && record.sensitivity !== 'public') {
|
|
34
|
-
excluded.push({ record, reason: 'sensitivity_private_in_public_channel' });
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
else if (options.channel === 'direct') {
|
|
39
|
-
if (record.sensitivity === 'restricted') {
|
|
40
|
-
excluded.push({ record, reason: 'sensitivity_restricted_in_direct_channel' });
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
22
|
+
// In single-owner architecture, memory and evidence are partitioned strictly
|
|
23
|
+
// by companion boundary (companionId) and temporal expiration.
|
|
44
24
|
admitted.push(record);
|
|
45
25
|
}
|
|
46
26
|
return { admitted, excluded };
|
package/dist/evidence.test.js
CHANGED
|
@@ -8,7 +8,6 @@ describe('T4 Evidence & Disclosure Core Contract', () => {
|
|
|
8
8
|
origin: 'knowledge',
|
|
9
9
|
trust: 'configured',
|
|
10
10
|
sensitivity: 'public',
|
|
11
|
-
allowedAudiences: ['audience-public'],
|
|
12
11
|
companionId: 'companion-a',
|
|
13
12
|
correlationId: 'corr-1',
|
|
14
13
|
createdAt: new Date(Date.now() - 5000).toISOString(),
|
|
@@ -21,7 +20,6 @@ describe('T4 Evidence & Disclosure Core Contract', () => {
|
|
|
21
20
|
const options = {
|
|
22
21
|
companionId: 'companion-a',
|
|
23
22
|
channel: 'public',
|
|
24
|
-
audienceId: 'audience-public',
|
|
25
23
|
};
|
|
26
24
|
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
27
25
|
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-mine']);
|
|
@@ -40,7 +38,6 @@ describe('T4 Evidence & Disclosure Core Contract', () => {
|
|
|
40
38
|
const options = {
|
|
41
39
|
companionId: 'companion-a',
|
|
42
40
|
channel: 'public',
|
|
43
|
-
audienceId: 'audience-public',
|
|
44
41
|
};
|
|
45
42
|
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
46
43
|
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-valid']);
|
|
@@ -51,51 +48,28 @@ describe('T4 Evidence & Disclosure Core Contract', () => {
|
|
|
51
48
|
}),
|
|
52
49
|
]);
|
|
53
50
|
});
|
|
54
|
-
test('
|
|
51
|
+
test('single-owner companion admits valid evidence without multi-audience filtering', () => {
|
|
55
52
|
const records = [
|
|
56
|
-
{ ...baseRecord, evidenceId: 'ev-public'
|
|
57
|
-
{ ...baseRecord, evidenceId: 'ev-direct-only'
|
|
53
|
+
{ ...baseRecord, evidenceId: 'ev-public' },
|
|
54
|
+
{ ...baseRecord, evidenceId: 'ev-direct-only' },
|
|
58
55
|
];
|
|
59
56
|
const options = {
|
|
60
57
|
companionId: 'companion-a',
|
|
61
|
-
channel: 'public',
|
|
62
|
-
audienceId: 'audience-public',
|
|
63
58
|
};
|
|
64
59
|
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
65
|
-
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-public']);
|
|
66
|
-
expect(excluded).toEqual([
|
|
67
|
-
expect.objectContaining({
|
|
68
|
-
record: expect.objectContaining({ evidenceId: 'ev-direct-only' }),
|
|
69
|
-
reason: 'audience_not_allowed',
|
|
70
|
-
}),
|
|
71
|
-
]);
|
|
72
|
-
});
|
|
73
|
-
test('sensitivity policy excludes private and restricted evidence from public channels', () => {
|
|
74
|
-
const records = [
|
|
75
|
-
{ ...baseRecord, evidenceId: 'ev-pub', sensitivity: 'public', allowedAudiences: ['audience-public'] },
|
|
76
|
-
{ ...baseRecord, evidenceId: 'ev-priv', sensitivity: 'private', allowedAudiences: ['audience-public'] },
|
|
77
|
-
{ ...baseRecord, evidenceId: 'ev-rest', sensitivity: 'restricted', allowedAudiences: ['audience-public'] },
|
|
78
|
-
];
|
|
79
|
-
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, {
|
|
80
|
-
companionId: 'companion-a',
|
|
81
|
-
channel: 'public',
|
|
82
|
-
audienceId: 'audience-public',
|
|
83
|
-
});
|
|
84
|
-
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-pub']);
|
|
85
|
-
expect(excluded.map((e) => e.record.evidenceId)).toEqual(['ev-priv', 'ev-rest']);
|
|
60
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-public', 'ev-direct-only']);
|
|
61
|
+
expect(excluded).toEqual([]);
|
|
86
62
|
});
|
|
87
|
-
test('
|
|
63
|
+
test('single-owner companion admits private and restricted sensitivity for companion owner', () => {
|
|
88
64
|
const records = [
|
|
89
|
-
{ ...baseRecord, evidenceId: 'ev-pub', sensitivity: 'public'
|
|
90
|
-
{ ...baseRecord, evidenceId: 'ev-priv', sensitivity: 'private'
|
|
91
|
-
{ ...baseRecord, evidenceId: 'ev-rest', sensitivity: 'restricted'
|
|
65
|
+
{ ...baseRecord, evidenceId: 'ev-pub', sensitivity: 'public' },
|
|
66
|
+
{ ...baseRecord, evidenceId: 'ev-priv', sensitivity: 'private' },
|
|
67
|
+
{ ...baseRecord, evidenceId: 'ev-rest', sensitivity: 'restricted' },
|
|
92
68
|
];
|
|
93
69
|
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, {
|
|
94
70
|
companionId: 'companion-a',
|
|
95
|
-
channel: 'direct',
|
|
96
|
-
audienceId: 'audience-direct-a',
|
|
97
71
|
});
|
|
98
|
-
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-pub', 'ev-priv']);
|
|
99
|
-
expect(excluded
|
|
72
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-pub', 'ev-priv', 'ev-rest']);
|
|
73
|
+
expect(excluded).toEqual([]);
|
|
100
74
|
});
|
|
101
75
|
});
|
|
@@ -13,7 +13,6 @@ async function emitExperienceEvents(params) {
|
|
|
13
13
|
companionId,
|
|
14
14
|
correlationId: requestContext.conversation.correlationId,
|
|
15
15
|
channel: requestContext.conversation?.channel,
|
|
16
|
-
audienceId: requestContext.conversation?.audienceId,
|
|
17
16
|
speech,
|
|
18
17
|
language: language || 'ja',
|
|
19
18
|
evidenceIds: gateEval.filteredEvidenceIds,
|
package/dist/experience.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export interface ExperienceEvent {
|
|
|
7
7
|
responseId: string;
|
|
8
8
|
correlationId: string;
|
|
9
9
|
channel?: string;
|
|
10
|
-
audienceId?: string;
|
|
11
10
|
approval: 'APPROVED';
|
|
12
11
|
kind: ExperienceEventKind;
|
|
13
12
|
lifecycle: ExperienceEventLifecycle;
|
|
@@ -39,7 +38,6 @@ export interface CreateExperienceEventsOptions {
|
|
|
39
38
|
companionId: string;
|
|
40
39
|
correlationId: string;
|
|
41
40
|
channel?: string;
|
|
42
|
-
audienceId?: string;
|
|
43
41
|
speech: string;
|
|
44
42
|
language?: string;
|
|
45
43
|
evidenceIds?: string[];
|
package/dist/experience.js
CHANGED
|
@@ -17,7 +17,6 @@ function createExperienceEvents(options) {
|
|
|
17
17
|
responseId: options.responseId,
|
|
18
18
|
correlationId: options.correlationId,
|
|
19
19
|
channel: options.channel,
|
|
20
|
-
audienceId: options.audienceId,
|
|
21
20
|
approval: 'APPROVED',
|
|
22
21
|
kind: 'voice',
|
|
23
22
|
lifecycle: 'STARTED',
|
|
@@ -35,7 +34,6 @@ function createExperienceEvents(options) {
|
|
|
35
34
|
responseId: options.responseId,
|
|
36
35
|
correlationId: options.correlationId,
|
|
37
36
|
channel: options.channel,
|
|
38
|
-
audienceId: options.audienceId,
|
|
39
37
|
approval: 'APPROVED',
|
|
40
38
|
kind: 'avatar',
|
|
41
39
|
lifecycle: 'STARTED',
|
|
@@ -62,9 +60,6 @@ function validateExperienceEvent(event) {
|
|
|
62
60
|
return { valid: false, error: 'Missing or invalid responseId' };
|
|
63
61
|
if (!e.correlationId || typeof e.correlationId !== 'string')
|
|
64
62
|
return { valid: false, error: 'Missing or invalid correlationId' };
|
|
65
|
-
if (e.audienceId !== undefined && typeof e.audienceId !== 'string') {
|
|
66
|
-
return { valid: false, error: 'Invalid audienceId: must be a string' };
|
|
67
|
-
}
|
|
68
63
|
if (e.approval !== 'APPROVED')
|
|
69
64
|
return { valid: false, error: 'Event approval must be APPROVED' };
|
|
70
65
|
if (!['voice', 'caption', 'avatar', 'platform_action'].includes(e.kind)) {
|
package/dist/experience.test.js
CHANGED
|
@@ -7,7 +7,6 @@ describe('T5 Experience Event Contract Suite', () => {
|
|
|
7
7
|
companionId: 'companion-a',
|
|
8
8
|
correlationId: 'corr-123',
|
|
9
9
|
channel: 'public',
|
|
10
|
-
audienceId: 'audience-public',
|
|
11
10
|
speech: 'Hello world',
|
|
12
11
|
language: 'en',
|
|
13
12
|
evidenceIds: ['ev-1'],
|
|
@@ -52,8 +51,5 @@ describe('T5 Experience Event Contract Suite', () => {
|
|
|
52
51
|
expect((0, experience_1.validateExperienceEvent)(missingCompanion).valid).toBe(false);
|
|
53
52
|
const missingResponseId = { ...events[0], responseId: '' };
|
|
54
53
|
expect((0, experience_1.validateExperienceEvent)(missingResponseId).valid).toBe(false);
|
|
55
|
-
const withoutAudience = { ...events[0] };
|
|
56
|
-
delete withoutAudience.audienceId;
|
|
57
|
-
expect((0, experience_1.validateExperienceEvent)(withoutAudience).valid).toBe(true);
|
|
58
54
|
});
|
|
59
55
|
});
|
package/dist/gating.d.ts
CHANGED
package/dist/gating.js
CHANGED
|
@@ -40,7 +40,6 @@ class ResponseGatingEngine {
|
|
|
40
40
|
companionId: requestContext.companionId,
|
|
41
41
|
correlationId: requestContext.conversation.correlationId,
|
|
42
42
|
channel: requestContext.conversation?.channel,
|
|
43
|
-
audienceId: requestContext.conversation?.audienceId,
|
|
44
43
|
speech: options.candidateSpeech,
|
|
45
44
|
language: options.candidateLanguage,
|
|
46
45
|
evidenceIds,
|
|
@@ -90,7 +89,6 @@ class ResponseGatingEngine {
|
|
|
90
89
|
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(attachedEvidence, {
|
|
91
90
|
companionId: staged.companionId,
|
|
92
91
|
channel: staged.channel,
|
|
93
|
-
audienceId: staged.audienceId,
|
|
94
92
|
now,
|
|
95
93
|
});
|
|
96
94
|
// If any evidence attached to this plan violated disclosure in this channel, exclude it
|
package/dist/gating.test.js
CHANGED
|
@@ -14,7 +14,6 @@ describe('T4 Response Gating & Staged Approval Suite', () => {
|
|
|
14
14
|
},
|
|
15
15
|
conversation: {
|
|
16
16
|
channel: 'public',
|
|
17
|
-
audienceId: 'audience-public',
|
|
18
17
|
correlationId: 'corr-gate-1',
|
|
19
18
|
},
|
|
20
19
|
};
|
|
@@ -28,7 +27,6 @@ describe('T4 Response Gating & Staged Approval Suite', () => {
|
|
|
28
27
|
origin: 'knowledge',
|
|
29
28
|
trust: 'configured',
|
|
30
29
|
sensitivity: 'public',
|
|
31
|
-
allowedAudiences: ['audience-public'],
|
|
32
30
|
companionId: 'companion-a',
|
|
33
31
|
correlationId: 'corr-gate-1',
|
|
34
32
|
createdAt: new Date().toISOString(),
|
|
@@ -56,7 +54,6 @@ describe('T4 Response Gating & Staged Approval Suite', () => {
|
|
|
56
54
|
origin: 'ocr',
|
|
57
55
|
trust: 'untrusted',
|
|
58
56
|
sensitivity: 'public',
|
|
59
|
-
allowedAudiences: ['audience-public'],
|
|
60
57
|
companionId: 'companion-a',
|
|
61
58
|
correlationId: 'corr-gate-2',
|
|
62
59
|
createdAt: new Date().toISOString(),
|
|
@@ -162,28 +159,27 @@ describe('T4 Response Gating & Staged Approval Suite', () => {
|
|
|
162
159
|
expect(approveRes.success).toBe(false);
|
|
163
160
|
expect(approveRes.reason).toBe('EVIDENCE_EXPIRED');
|
|
164
161
|
});
|
|
165
|
-
test('
|
|
166
|
-
const
|
|
167
|
-
evidenceId: 'ev-
|
|
162
|
+
test('foreign companion evidence is filtered out and absent from citations/evidence list', () => {
|
|
163
|
+
const foreignEvidence = {
|
|
164
|
+
evidenceId: 'ev-foreign-1',
|
|
168
165
|
sourceId: 'src-secret',
|
|
169
166
|
origin: 'knowledge',
|
|
170
167
|
trust: 'configured',
|
|
171
168
|
sensitivity: 'private',
|
|
172
|
-
|
|
173
|
-
companionId: 'companion-a',
|
|
169
|
+
companionId: 'companion-b',
|
|
174
170
|
correlationId: 'corr-gate-1',
|
|
175
171
|
createdAt: new Date().toISOString(),
|
|
176
172
|
};
|
|
177
173
|
const staged = engine.stageResponse({
|
|
178
|
-
requestContext: validPublicContext, //
|
|
179
|
-
candidateSpeech: '
|
|
174
|
+
requestContext: validPublicContext, // companion-a
|
|
175
|
+
candidateSpeech: 'Response text',
|
|
180
176
|
candidateLanguage: 'en',
|
|
181
|
-
evidenceRecords: [
|
|
177
|
+
evidenceRecords: [foreignEvidence],
|
|
182
178
|
citations: [{ sourceId: 'src-secret' }],
|
|
183
179
|
});
|
|
184
|
-
const evalRes = engine.evaluateGate(staged, [
|
|
180
|
+
const evalRes = engine.evaluateGate(staged, [foreignEvidence]);
|
|
185
181
|
expect(evalRes.admissible).toBe(true);
|
|
186
|
-
//
|
|
182
|
+
// Foreign companion evidence and its citation must be stripped due to companion isolation
|
|
187
183
|
expect(evalRes.filteredEvidenceIds).toEqual([]);
|
|
188
184
|
expect(evalRes.filteredCitations).toEqual([]);
|
|
189
185
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './action-executor';
|
|
|
22
22
|
export * from './experience-emitter';
|
|
23
23
|
export * from './response-envelope';
|
|
24
24
|
export * from './session-history';
|
|
25
|
+
export * from './schema-validator';
|
|
25
26
|
import { EvidenceRecord } from './evidence';
|
|
26
27
|
import { ActionIntent } from './action';
|
|
27
28
|
import { RequestContext } from './context';
|
package/dist/index.js
CHANGED
|
@@ -39,5 +39,6 @@ __exportStar(require("./action-executor"), exports);
|
|
|
39
39
|
__exportStar(require("./experience-emitter"), exports);
|
|
40
40
|
__exportStar(require("./response-envelope"), exports);
|
|
41
41
|
__exportStar(require("./session-history"), exports);
|
|
42
|
+
__exportStar(require("./schema-validator"), exports);
|
|
42
43
|
// Mouth (Communication & Output Delivery)
|
|
43
44
|
__exportStar(require("./mouth-types"), exports);
|
package/dist/memory-settler.js
CHANGED
|
@@ -26,7 +26,6 @@ async function settleMemoryProposals(params) {
|
|
|
26
26
|
companionId,
|
|
27
27
|
actorId: requestContext.actor.actorId,
|
|
28
28
|
channel: requestContext.conversation.channel,
|
|
29
|
-
audienceId: requestContext.conversation.audienceId,
|
|
30
29
|
},
|
|
31
30
|
};
|
|
32
31
|
await memory.addSourceEvent(sourceEvent);
|
|
@@ -47,7 +46,6 @@ async function settleMemoryProposals(params) {
|
|
|
47
46
|
userConfirmation: 'none',
|
|
48
47
|
sensitivity: claim.sensitivity ||
|
|
49
48
|
(requestContext.conversation?.channel === 'public' ? 'public' : 'private'),
|
|
50
|
-
allowedAudiences: claim.allowedAudiences || (requestContext.conversation?.audienceId ? [requestContext.conversation.audienceId] : undefined),
|
|
51
49
|
});
|
|
52
50
|
createdMemoryProposals.push(proposal);
|
|
53
51
|
}
|
|
@@ -63,7 +61,6 @@ async function settleMemoryProposals(params) {
|
|
|
63
61
|
sourceEventId: sourceEventId || p.sourceEventId,
|
|
64
62
|
claimType: p.claimType || 'semantic',
|
|
65
63
|
sensitivity: p.sensitivity || 'private',
|
|
66
|
-
allowedAudiences: p.allowedAudiences || (requestContext.conversation?.audienceId ? [requestContext.conversation.audienceId] : undefined),
|
|
67
64
|
});
|
|
68
65
|
createdMemoryProposals.push(proposal);
|
|
69
66
|
}
|
|
@@ -23,7 +23,6 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
|
|
|
23
23
|
},
|
|
24
24
|
conversation: {
|
|
25
25
|
channel: 'direct',
|
|
26
|
-
audienceId: 'aud-direct',
|
|
27
26
|
correlationId: 'corr-perc-1',
|
|
28
27
|
},
|
|
29
28
|
};
|
|
@@ -89,7 +88,7 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
|
|
|
89
88
|
capabilities: ['chat:public'],
|
|
90
89
|
authenticated: true,
|
|
91
90
|
},
|
|
92
|
-
conversation: { channel: 'direct',
|
|
91
|
+
conversation: { channel: 'direct', correlationId: 'c1' },
|
|
93
92
|
};
|
|
94
93
|
const bobContext = {
|
|
95
94
|
companionId: 'comp-multi',
|
|
@@ -100,7 +99,7 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
|
|
|
100
99
|
capabilities: ['chat:public'],
|
|
101
100
|
authenticated: false,
|
|
102
101
|
},
|
|
103
|
-
conversation: { channel: 'public',
|
|
102
|
+
conversation: { channel: 'public', correlationId: 'c2' },
|
|
104
103
|
};
|
|
105
104
|
await runtime.handleUserMessage('Alice secret message', aliceContext);
|
|
106
105
|
await runtime.handleUserMessage('Bob public query', bobContext);
|
|
@@ -122,7 +121,6 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
|
|
|
122
121
|
origin: 'knowledge',
|
|
123
122
|
trust: 'configured',
|
|
124
123
|
sensitivity: 'public',
|
|
125
|
-
allowedAudiences: ['audience-public'],
|
|
126
124
|
companionId: 'comp-native-ev',
|
|
127
125
|
correlationId: 'corr-test',
|
|
128
126
|
createdAt: new Date().toISOString(),
|
package/dist/prompt-compiler.js
CHANGED
|
@@ -32,11 +32,8 @@ async function compilePrompts(params) {
|
|
|
32
32
|
// Compile Behavior with neutral context metadata
|
|
33
33
|
const behaviorInjections = behavior && typeof behavior.compile === 'function'
|
|
34
34
|
? await behavior.compile({
|
|
35
|
-
activeRole: role,
|
|
36
35
|
directives: activeDirectives,
|
|
37
36
|
companionId,
|
|
38
|
-
channel: requestContext.conversation.channel,
|
|
39
|
-
audienceId: requestContext.conversation.audienceId,
|
|
40
37
|
actorId: requestContext.actor.actorId,
|
|
41
38
|
})
|
|
42
39
|
: '';
|
package/dist/proposals.d.ts
CHANGED
|
@@ -66,4 +66,30 @@ describe('SiduriRuntime Facade Methods & Delegation', () => {
|
|
|
66
66
|
await runtime.resetMemory();
|
|
67
67
|
expect(mockMemory.resetMemory).toHaveBeenCalled();
|
|
68
68
|
});
|
|
69
|
+
test('configures SqliteActionStore when actionStore is sqlite', () => {
|
|
70
|
+
const runtime = new runtime_1.SiduriRuntime('comp-sqlite', {
|
|
71
|
+
id: 'comp-sqlite',
|
|
72
|
+
name: 'Sqlite Test',
|
|
73
|
+
actionStore: 'sqlite',
|
|
74
|
+
});
|
|
75
|
+
expect(runtime.actionPolicy.getStore()).toBeDefined();
|
|
76
|
+
// Verify it is an instance of SqliteActionStore
|
|
77
|
+
expect(runtime.actionPolicy.getStore().constructor.name).toBe('SqliteActionStore');
|
|
78
|
+
});
|
|
79
|
+
test('accepts custom actionStore via RuntimeOrgans', () => {
|
|
80
|
+
const customStore = {
|
|
81
|
+
recordExecution: jest.fn(),
|
|
82
|
+
getExecution: jest.fn(),
|
|
83
|
+
updateExecution: jest.fn(),
|
|
84
|
+
recordApproval: jest.fn(),
|
|
85
|
+
getApproval: jest.fn(),
|
|
86
|
+
recordAudit: jest.fn(),
|
|
87
|
+
getAuditLog: jest.fn(),
|
|
88
|
+
verifyAuditChain: jest.fn(),
|
|
89
|
+
};
|
|
90
|
+
const runtime = new runtime_1.SiduriRuntime('comp-custom', { id: 'comp-custom', name: 'Custom' }, {
|
|
91
|
+
actionStore: customStore,
|
|
92
|
+
});
|
|
93
|
+
expect(runtime.actionPolicy.getStore()).toBe(customStore);
|
|
94
|
+
});
|
|
69
95
|
});
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrainOrgan, MemoryOrgan, VoiceOrgan, KnowledgeOrgan, VisionOrgan, BehaviorOrgan, BodyOrgan, HandsOrgan, EarOrgan, ObservationOrgan, ObservationResult, Observation, Message, RequestContext, ActionPolicyEngine, ResponseGatingEngine, StageResponseOptions, ApproveResponseOptions, RejectResponseOptions, ExperienceDispatcher, ExperienceAdapter, OrganConfig, Claim, BehaviorDirective, StagedResponsePlan, ResponseGateEvaluation, EvidenceRecord, MouthOrgan, MouthUtterance, MouthMedium, FormattedMouthOutput, MouthStreamChunk, MouthChannel } from './index';
|
|
1
|
+
import { BrainOrgan, MemoryOrgan, VoiceOrgan, KnowledgeOrgan, VisionOrgan, BehaviorOrgan, BodyOrgan, HandsOrgan, EarOrgan, ObservationOrgan, ObservationResult, Observation, Message, RequestContext, ActionPolicyEngine, ActionStore, ResponseGatingEngine, StageResponseOptions, ApproveResponseOptions, RejectResponseOptions, ExperienceDispatcher, ExperienceAdapter, OrganConfig, Claim, BehaviorDirective, StagedResponsePlan, ResponseGateEvaluation, EvidenceRecord, MouthOrgan, MouthUtterance, MouthMedium, FormattedMouthOutput, MouthStreamChunk, MouthChannel } from './index';
|
|
2
2
|
export interface SiduriRuntimeConfig {
|
|
3
3
|
name: string;
|
|
4
4
|
brain?: OrganConfig | Record<string, unknown>;
|
|
@@ -13,6 +13,11 @@ export interface SiduriRuntimeConfig {
|
|
|
13
13
|
observation?: OrganConfig | Record<string, unknown>;
|
|
14
14
|
mouth?: OrganConfig | Record<string, unknown>;
|
|
15
15
|
actionPolicy?: Record<string, unknown>;
|
|
16
|
+
actionStore?: 'in-memory' | 'sqlite' | {
|
|
17
|
+
type: 'sqlite' | 'in-memory';
|
|
18
|
+
dbPath?: string;
|
|
19
|
+
};
|
|
20
|
+
actionStorePath?: string;
|
|
16
21
|
[key: string]: unknown;
|
|
17
22
|
}
|
|
18
23
|
export interface RuntimeOrgans {
|
|
@@ -27,6 +32,7 @@ export interface RuntimeOrgans {
|
|
|
27
32
|
ear?: EarOrgan;
|
|
28
33
|
observation?: ObservationOrgan;
|
|
29
34
|
mouth?: MouthOrgan;
|
|
35
|
+
actionStore?: ActionStore;
|
|
30
36
|
actionPolicy?: ActionPolicyEngine;
|
|
31
37
|
}
|
|
32
38
|
export interface CompanionPerception {
|
package/dist/runtime.js
CHANGED
|
@@ -57,7 +57,17 @@ class SiduriRuntime {
|
|
|
57
57
|
this.observation = organs.observation;
|
|
58
58
|
this.mouth = organs.mouth;
|
|
59
59
|
this.gating = new index_1.ResponseGatingEngine();
|
|
60
|
-
|
|
60
|
+
let actionStore = organs.actionStore;
|
|
61
|
+
if (!actionStore) {
|
|
62
|
+
const storeOpt = config.actionStore;
|
|
63
|
+
const storePath = config.actionStorePath || (typeof storeOpt === 'object' ? storeOpt.dbPath : undefined);
|
|
64
|
+
if (storeOpt === 'sqlite' || (typeof storeOpt === 'object' && storeOpt.type === 'sqlite') || storePath) {
|
|
65
|
+
actionStore = new index_1.SqliteActionStore({ dbPath: storePath });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.actionPolicy = organs.actionPolicy || new index_1.ActionPolicyEngine({
|
|
69
|
+
store: actionStore,
|
|
70
|
+
});
|
|
61
71
|
this.dispatcher = new index_1.ExperienceDispatcher();
|
|
62
72
|
if (this.voice && typeof this.voice.handleEvent === 'function') {
|
|
63
73
|
this.dispatcher.registerAdapter(this.voice);
|
|
@@ -214,7 +224,7 @@ class SiduriRuntime {
|
|
|
214
224
|
// 1. Input validation, RequestContext synthesis, and Ear perception routing
|
|
215
225
|
const input = await (0, input_normalizer_1.normalizeUserInput)(rawText, roleOrContext, history, this.id, this.ear);
|
|
216
226
|
const sessionKey = input.requestContext.actor.sessionId ||
|
|
217
|
-
input.requestContext.conversation.
|
|
227
|
+
input.requestContext.conversation.correlationId ||
|
|
218
228
|
'default';
|
|
219
229
|
const currentMessage = { role: 'user', content: input.perceivedText };
|
|
220
230
|
const boundedSessionHistory = [...input.boundedHistory, currentMessage].slice(-20);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class ConfigValidationError extends Error {
|
|
2
|
+
errors: string[];
|
|
3
|
+
constructor(errors: string[]);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validates a companion configuration object against a JSON schema (draft-07 compatible).
|
|
7
|
+
* Throws ConfigValidationError if validation errors are detected.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateCompanionConfig(config: unknown, schema: any, path?: string): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigValidationError = void 0;
|
|
4
|
+
exports.validateCompanionConfig = validateCompanionConfig;
|
|
5
|
+
class ConfigValidationError extends Error {
|
|
6
|
+
errors;
|
|
7
|
+
constructor(errors) {
|
|
8
|
+
super(`Siduri configuration validation failed:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
9
|
+
this.name = 'ConfigValidationError';
|
|
10
|
+
this.errors = errors;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.ConfigValidationError = ConfigValidationError;
|
|
14
|
+
/**
|
|
15
|
+
* Validates a companion configuration object against a JSON schema (draft-07 compatible).
|
|
16
|
+
* Throws ConfigValidationError if validation errors are detected.
|
|
17
|
+
*/
|
|
18
|
+
function validateCompanionConfig(config, schema, path = '$') {
|
|
19
|
+
const errors = [];
|
|
20
|
+
function validateNode(value, nodeSchema, curPath) {
|
|
21
|
+
if (!nodeSchema || typeof nodeSchema !== 'object')
|
|
22
|
+
return;
|
|
23
|
+
// Type validation
|
|
24
|
+
if (nodeSchema.type !== undefined) {
|
|
25
|
+
const type = nodeSchema.type;
|
|
26
|
+
if (type === 'object') {
|
|
27
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
28
|
+
errors.push(`${curPath}: expected object, received ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (type === 'array') {
|
|
33
|
+
if (!Array.isArray(value)) {
|
|
34
|
+
errors.push(`${curPath}: expected array, received ${typeof value}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (type === 'string') {
|
|
39
|
+
if (typeof value !== 'string') {
|
|
40
|
+
errors.push(`${curPath}: expected string, received ${typeof value}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (type === 'number') {
|
|
45
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
46
|
+
errors.push(`${curPath}: expected number, received ${typeof value}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (type === 'integer') {
|
|
51
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
52
|
+
errors.push(`${curPath}: expected integer, received ${typeof value}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (type === 'boolean') {
|
|
57
|
+
if (typeof value !== 'boolean') {
|
|
58
|
+
errors.push(`${curPath}: expected boolean, received ${typeof value}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Enum validation
|
|
64
|
+
if (Array.isArray(nodeSchema.enum)) {
|
|
65
|
+
if (!nodeSchema.enum.includes(value)) {
|
|
66
|
+
errors.push(`${curPath}: invalid value ${JSON.stringify(value)}, expected one of: ${nodeSchema.enum.map((v) => JSON.stringify(v)).join(', ')}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Object properties validation
|
|
71
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
72
|
+
if (Array.isArray(nodeSchema.required)) {
|
|
73
|
+
for (const reqKey of nodeSchema.required) {
|
|
74
|
+
if (value[reqKey] === undefined) {
|
|
75
|
+
errors.push(`${curPath}.${reqKey}: is required`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const definedProps = nodeSchema.properties || {};
|
|
80
|
+
if (nodeSchema.additionalProperties === false) {
|
|
81
|
+
for (const key of Object.keys(value)) {
|
|
82
|
+
// Allow $schema property at root level
|
|
83
|
+
if (curPath === '$' && key === '$schema')
|
|
84
|
+
continue;
|
|
85
|
+
if (!(key in definedProps)) {
|
|
86
|
+
errors.push(`${curPath}.${key}: unexpected property is not allowed`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [propName, propSchema] of Object.entries(definedProps)) {
|
|
91
|
+
if (value[propName] !== undefined) {
|
|
92
|
+
validateNode(value[propName], propSchema, `${curPath}.${propName}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Array items validation
|
|
97
|
+
if (Array.isArray(value) && nodeSchema.items) {
|
|
98
|
+
value.forEach((item, index) => {
|
|
99
|
+
validateNode(item, nodeSchema.items, `${curPath}[${index}]`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
validateNode(config, schema, path);
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
throw new ConfigValidationError(errors);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schema_validator_1 = require("./schema-validator");
|
|
4
|
+
describe('validateCompanionConfig', () => {
|
|
5
|
+
const sampleSchema = {
|
|
6
|
+
type: 'object',
|
|
7
|
+
required: ['id', 'name', 'organs'],
|
|
8
|
+
additionalProperties: false,
|
|
9
|
+
properties: {
|
|
10
|
+
$schema: { type: 'string' },
|
|
11
|
+
id: { type: 'string' },
|
|
12
|
+
name: { type: 'string' },
|
|
13
|
+
organs: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
properties: {
|
|
17
|
+
brain: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['provider', 'model'],
|
|
20
|
+
properties: {
|
|
21
|
+
provider: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['openrouter', 'openai-compatible'],
|
|
24
|
+
},
|
|
25
|
+
model: { type: 'string' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
memory: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
required: ['provider'],
|
|
31
|
+
properties: {
|
|
32
|
+
provider: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
enum: ['postgres', 'in-memory', 'none'],
|
|
35
|
+
},
|
|
36
|
+
maxConnections: { type: 'number' },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
test('accepts valid configuration matching schema', () => {
|
|
44
|
+
const validConfig = {
|
|
45
|
+
$schema: './siduri.schema.json',
|
|
46
|
+
id: 'companion-1',
|
|
47
|
+
name: 'Test Companion',
|
|
48
|
+
organs: {
|
|
49
|
+
brain: {
|
|
50
|
+
provider: 'openrouter',
|
|
51
|
+
model: 'gpt-4o',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
expect(() => (0, schema_validator_1.validateCompanionConfig)(validConfig, sampleSchema)).not.toThrow();
|
|
56
|
+
});
|
|
57
|
+
test('throws ConfigValidationError if missing required root field', () => {
|
|
58
|
+
const invalidConfig = {
|
|
59
|
+
name: 'Missing Id and Organs',
|
|
60
|
+
};
|
|
61
|
+
expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
|
|
62
|
+
try {
|
|
63
|
+
(0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
expect(err.errors).toContain('$.id: is required');
|
|
67
|
+
expect(err.errors).toContain('$.organs: is required');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
test('throws ConfigValidationError on unexpected property when additionalProperties is false', () => {
|
|
71
|
+
const invalidConfig = {
|
|
72
|
+
id: 'c-1',
|
|
73
|
+
name: 'Test',
|
|
74
|
+
organs: {},
|
|
75
|
+
extraField: 'not allowed',
|
|
76
|
+
};
|
|
77
|
+
expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
|
|
78
|
+
try {
|
|
79
|
+
(0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
expect(err.errors).toContain('$.extraField: unexpected property is not allowed');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
test('throws ConfigValidationError on invalid enum value', () => {
|
|
86
|
+
const invalidConfig = {
|
|
87
|
+
id: 'c-1',
|
|
88
|
+
name: 'Test',
|
|
89
|
+
organs: {
|
|
90
|
+
brain: {
|
|
91
|
+
provider: 'invalid-brain-provider',
|
|
92
|
+
model: 'gpt-4',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
|
|
97
|
+
try {
|
|
98
|
+
(0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
expect(err.errors.some((e) => e.includes('invalid value "invalid-brain-provider"'))).toBe(true);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
test('throws ConfigValidationError on invalid type', () => {
|
|
105
|
+
const invalidConfig = {
|
|
106
|
+
id: 12345, // should be string
|
|
107
|
+
name: 'Test',
|
|
108
|
+
organs: {
|
|
109
|
+
memory: {
|
|
110
|
+
provider: 'postgres',
|
|
111
|
+
maxConnections: 'ten', // should be number
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
expect(() => (0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema)).toThrow(schema_validator_1.ConfigValidationError);
|
|
116
|
+
try {
|
|
117
|
+
(0, schema_validator_1.validateCompanionConfig)(invalidConfig, sampleSchema);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
expect(err.errors).toContain('$.id: expected string, received number');
|
|
121
|
+
expect(err.errors).toContain('$.organs.memory.maxConnections: expected number, received string');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
package/dist/teaching.d.ts
CHANGED
|
@@ -4,12 +4,12 @@ export interface ExtractedTeaching {
|
|
|
4
4
|
behaviorProposals: BehaviorProposal[];
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Deterministically extracts teaching candidates from user messages according to
|
|
7
|
+
* Deterministically extracts teaching candidates from user messages according to single-owner model.
|
|
8
8
|
*
|
|
9
9
|
* Rules:
|
|
10
10
|
* - Scoped to the requesting actor context (subject: `actor:${actorId}`), NEVER `primary_user`.
|
|
11
11
|
* - Candidates are pending proposals only, never active/approved.
|
|
12
|
-
* -
|
|
12
|
+
* - In a single-owner companion, preferences apply across the companion instance without audience partitioning.
|
|
13
13
|
* - Companion identity is isolated.
|
|
14
14
|
*/
|
|
15
15
|
export declare function extractDeterministicTeaching(message: string, context?: RequestContext, sourceEventId?: string): ExtractedTeaching;
|
package/dist/teaching.js
CHANGED
|
@@ -5,12 +5,12 @@ function cleanValue(value, limit = 160) {
|
|
|
5
5
|
return value.replace(/\s+/g, ' ').replace(/^[ .,!?:;"']+|[ .,!?:;"']+$/g, '').slice(0, limit);
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Deterministically extracts teaching candidates from user messages according to
|
|
8
|
+
* Deterministically extracts teaching candidates from user messages according to single-owner model.
|
|
9
9
|
*
|
|
10
10
|
* Rules:
|
|
11
11
|
* - Scoped to the requesting actor context (subject: `actor:${actorId}`), NEVER `primary_user`.
|
|
12
12
|
* - Candidates are pending proposals only, never active/approved.
|
|
13
|
-
* -
|
|
13
|
+
* - In a single-owner companion, preferences apply across the companion instance without audience partitioning.
|
|
14
14
|
* - Companion identity is isolated.
|
|
15
15
|
*/
|
|
16
16
|
function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
@@ -23,7 +23,6 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
23
23
|
const actorId = context?.actor?.actorId;
|
|
24
24
|
const actorSubject = actorId ? `actor:${actorId}` : 'actor:anonymous';
|
|
25
25
|
const companionId = context?.companionId || 'default';
|
|
26
|
-
const defaultAudience = context?.conversation?.audienceId || (context?.conversation?.channel === 'direct' ? `audience-direct-${actorId}` : 'audience-public');
|
|
27
26
|
const sensitivity = context?.conversation?.channel === 'public' ? 'public' : 'private';
|
|
28
27
|
// 1. Companion's Name: "your name is X" / "you are called X"
|
|
29
28
|
const companionNameMatch = text.match(/\b(?:your name is|you are called)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
|
|
@@ -37,7 +36,6 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
37
36
|
claimType: 'semantic',
|
|
38
37
|
provenance: 'deterministic_teaching',
|
|
39
38
|
sensitivity: 'public',
|
|
40
|
-
allowedAudiences: ['audience-public'],
|
|
41
39
|
sourceEventId,
|
|
42
40
|
});
|
|
43
41
|
behaviorProposals.push({
|
|
@@ -62,36 +60,14 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
62
60
|
claimType: 'preference',
|
|
63
61
|
provenance: 'deterministic_teaching',
|
|
64
62
|
sensitivity,
|
|
65
|
-
allowedAudiences: [defaultAudience],
|
|
66
63
|
sourceEventId,
|
|
67
64
|
});
|
|
68
65
|
}
|
|
69
66
|
// 3. Preferred Address / Call me X: "call me X"
|
|
70
|
-
const callMeMatch = text.match(/\b(?:(?:from now on|only),?\s*)?call me\s+(.+?)(?:\s+(in private|privately|in public|publicly|everywhere|in direct conversations))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
|
|
67
|
+
const callMeMatch = text.match(/\b(?:(?:from now on|only),?\s*)?call me\s+(.+?)(?:\s+(?:in private|privately|in public|publicly|everywhere|in direct conversations))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
|
|
71
68
|
if (callMeMatch) {
|
|
72
69
|
const address = cleanValue(callMeMatch[1], 80);
|
|
73
|
-
const
|
|
74
|
-
let claimAudiences = [defaultAudience];
|
|
75
|
-
let claimSensitivity = sensitivity;
|
|
76
|
-
let directiveInstruction = `Address ${actorSubject} as ${address}`;
|
|
77
|
-
if (scopePhrase.includes('private') || scopePhrase.includes('privately')) {
|
|
78
|
-
claimSensitivity = 'private';
|
|
79
|
-
claimAudiences = [context?.conversation?.audienceId || `audience-private-${actorId}`];
|
|
80
|
-
directiveInstruction += ' in private conversations';
|
|
81
|
-
}
|
|
82
|
-
else if (scopePhrase.includes('public') || scopePhrase.includes('publicly')) {
|
|
83
|
-
claimSensitivity = 'public';
|
|
84
|
-
claimAudiences = ['audience-public'];
|
|
85
|
-
directiveInstruction += ' in public conversations';
|
|
86
|
-
}
|
|
87
|
-
else if (scopePhrase.includes('direct')) {
|
|
88
|
-
claimSensitivity = 'private';
|
|
89
|
-
claimAudiences = [context?.conversation?.audienceId || `audience-direct-${actorId}`];
|
|
90
|
-
directiveInstruction += ' in direct conversations';
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
directiveInstruction += ' when addressing the actor';
|
|
94
|
-
}
|
|
70
|
+
const directiveInstruction = `Address ${actorSubject} as ${address}`;
|
|
95
71
|
claims.push({
|
|
96
72
|
subject: actorSubject,
|
|
97
73
|
predicate: 'preferred_address',
|
|
@@ -99,8 +75,7 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
99
75
|
content: `The actor's preferred address is ${address}.`,
|
|
100
76
|
claimType: 'relationship',
|
|
101
77
|
provenance: 'deterministic_teaching',
|
|
102
|
-
sensitivity
|
|
103
|
-
allowedAudiences: claimAudiences,
|
|
78
|
+
sensitivity,
|
|
104
79
|
sourceEventId,
|
|
105
80
|
});
|
|
106
81
|
behaviorProposals.push({
|
|
@@ -125,7 +100,6 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
125
100
|
claimType: 'relationship',
|
|
126
101
|
provenance: 'deterministic_teaching',
|
|
127
102
|
sensitivity: 'private',
|
|
128
|
-
allowedAudiences: [defaultAudience],
|
|
129
103
|
sourceEventId,
|
|
130
104
|
});
|
|
131
105
|
behaviorProposals.push({
|
|
@@ -151,7 +125,6 @@ function extractDeterministicTeaching(message, context, sourceEventId) {
|
|
|
151
125
|
claimType: 'preference',
|
|
152
126
|
provenance: 'deterministic_teaching',
|
|
153
127
|
sensitivity,
|
|
154
|
-
allowedAudiences: [defaultAudience],
|
|
155
128
|
sourceEventId,
|
|
156
129
|
});
|
|
157
130
|
}
|
package/package.json
CHANGED