@siduri-x/core 1.0.5 → 1.0.8
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-executor.d.ts +12 -0
- package/dist/action-executor.js +50 -0
- package/dist/action-policy.d.ts +45 -0
- package/dist/action-policy.js +219 -0
- package/dist/action-policy.test.d.ts +1 -0
- package/dist/action-policy.test.js +193 -0
- package/dist/action.d.ts +72 -0
- package/dist/action.js +2 -0
- package/dist/adversarial.test.d.ts +1 -0
- package/dist/adversarial.test.js +489 -0
- package/dist/architecture-boundary.test.d.ts +1 -0
- package/dist/architecture-boundary.test.js +117 -0
- package/dist/capability.d.ts +65 -0
- package/dist/capability.js +157 -0
- package/dist/capability.test.d.ts +1 -0
- package/dist/capability.test.js +268 -0
- package/dist/chat-contract.d.ts +81 -0
- package/dist/chat-contract.js +68 -0
- package/dist/cognition-planner.d.ts +15 -0
- package/dist/cognition-planner.js +23 -0
- package/dist/context-retriever.d.ts +24 -0
- package/dist/context-retriever.js +90 -0
- package/dist/context.d.ts +44 -0
- package/dist/context.js +72 -0
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +76 -0
- package/dist/dispatcher.d.ts +14 -0
- package/dist/dispatcher.js +40 -0
- package/dist/dispatcher.test.d.ts +1 -0
- package/dist/dispatcher.test.js +58 -0
- package/dist/ear-types.d.ts +33 -0
- package/dist/ear-types.js +2 -0
- package/dist/evidence.d.ts +73 -0
- package/dist/evidence.js +27 -0
- package/dist/evidence.test.d.ts +1 -0
- package/dist/evidence.test.js +75 -0
- package/dist/experience-emitter.d.ts +21 -0
- package/dist/experience-emitter.js +47 -0
- package/dist/experience.d.ts +55 -0
- package/dist/experience.js +74 -0
- package/dist/experience.test.d.ts +1 -0
- package/dist/experience.test.js +55 -0
- package/dist/gating.d.ts +45 -0
- package/dist/gating.js +183 -0
- package/dist/gating.test.d.ts +1 -0
- package/dist/gating.test.js +186 -0
- package/dist/index.d.ts +250 -0
- package/dist/index.js +43 -0
- package/dist/input-normalizer.d.ts +14 -0
- package/dist/input-normalizer.js +58 -0
- package/dist/input-normalizer.test.d.ts +1 -0
- package/dist/input-normalizer.test.js +39 -0
- package/dist/intent-classifier.d.ts +24 -0
- package/dist/intent-classifier.js +53 -0
- package/dist/intent-classifier.test.d.ts +1 -0
- package/dist/intent-classifier.test.js +67 -0
- package/dist/memory-settler.d.ts +27 -0
- package/dist/memory-settler.js +92 -0
- package/dist/mouth-types.d.ts +85 -0
- package/dist/mouth-types.js +2 -0
- package/dist/perception-cycle.test.d.ts +1 -0
- package/dist/perception-cycle.test.js +153 -0
- package/dist/prompt-compiler.d.ts +20 -0
- package/dist/prompt-compiler.js +54 -0
- package/dist/prompt-compiler.test.d.ts +1 -0
- package/dist/prompt-compiler.test.js +75 -0
- package/dist/proposals.d.ts +29 -0
- package/dist/proposals.js +2 -0
- package/dist/response-envelope.d.ts +25 -0
- package/dist/response-envelope.js +64 -0
- package/dist/runtime-facades.test.d.ts +1 -0
- package/dist/runtime-facades.test.js +69 -0
- package/dist/runtime.d.ts +114 -0
- package/dist/runtime.js +412 -0
- package/dist/session-history.d.ts +20 -0
- package/dist/session-history.js +55 -0
- package/dist/session-history.test.d.ts +1 -0
- package/dist/session-history.test.js +38 -0
- package/dist/sqlite-action-store.d.ts +20 -0
- package/dist/sqlite-action-store.js +225 -0
- package/dist/sqlite-action-store.test.d.ts +1 -0
- package/dist/sqlite-action-store.test.js +251 -0
- package/dist/teaching.d.ts +15 -0
- package/dist/teaching.js +132 -0
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retrieveRuntimeContext = retrieveRuntimeContext;
|
|
4
|
+
/**
|
|
5
|
+
* Concurrently queries Knowledge and Memory organs with graceful degradation,
|
|
6
|
+
* collecting diagnostics and synthesizing evidence records and citations.
|
|
7
|
+
*/
|
|
8
|
+
async function retrieveRuntimeContext(params) {
|
|
9
|
+
const { companionId, perceivedText, requestContext, role, isContextObject, shouldQueryKnowledge, knowledge, memory, } = params;
|
|
10
|
+
const queryOptions = isContextObject
|
|
11
|
+
? {
|
|
12
|
+
limit: 5,
|
|
13
|
+
}
|
|
14
|
+
: role;
|
|
15
|
+
const subsystemDiagnostics = {};
|
|
16
|
+
const [knowledgeData, memoryData, activeDirectives] = await Promise.all([
|
|
17
|
+
knowledge && shouldQueryKnowledge && typeof knowledge.search === 'function'
|
|
18
|
+
? knowledge.search(perceivedText).catch((e) => {
|
|
19
|
+
console.error('[SiduriRuntime] Knowledge search failed:', e.message);
|
|
20
|
+
subsystemDiagnostics['knowledge'] = `UNAVAILABLE: ${e.message}`;
|
|
21
|
+
return [];
|
|
22
|
+
})
|
|
23
|
+
: Promise.resolve([]),
|
|
24
|
+
memory && typeof memory.searchClaims === 'function'
|
|
25
|
+
? memory.searchClaims(perceivedText, queryOptions, 5).catch((e) => {
|
|
26
|
+
console.error('[SiduriRuntime] Memory search failed:', e.message);
|
|
27
|
+
subsystemDiagnostics['memory_claims'] = `UNAVAILABLE: ${e.message}`;
|
|
28
|
+
return [];
|
|
29
|
+
})
|
|
30
|
+
: Promise.resolve([]),
|
|
31
|
+
memory && typeof memory.getDirectives === 'function'
|
|
32
|
+
? memory.getDirectives().catch((e) => {
|
|
33
|
+
console.error('[SiduriRuntime] Memory directives failed:', e.message);
|
|
34
|
+
subsystemDiagnostics['memory_directives'] = `UNAVAILABLE: ${e.message}`;
|
|
35
|
+
return [];
|
|
36
|
+
})
|
|
37
|
+
: Promise.resolve([]),
|
|
38
|
+
]);
|
|
39
|
+
// Build evidence records from retrieved knowledge context
|
|
40
|
+
const collectedEvidence = [];
|
|
41
|
+
const citations = [];
|
|
42
|
+
if (knowledgeData.length > 0) {
|
|
43
|
+
for (const k of knowledgeData) {
|
|
44
|
+
if (k.evidenceRecord) {
|
|
45
|
+
const nativeRecord = {
|
|
46
|
+
...k.evidenceRecord,
|
|
47
|
+
};
|
|
48
|
+
collectedEvidence.push(nativeRecord);
|
|
49
|
+
citations.push({
|
|
50
|
+
sourceId: nativeRecord.sourceId,
|
|
51
|
+
revision: nativeRecord.revision,
|
|
52
|
+
documentId: nativeRecord.documentId || k.citations?.[0]?.documentId,
|
|
53
|
+
chunkId: nativeRecord.chunkId || k.citations?.[0]?.chunkId,
|
|
54
|
+
locator: nativeRecord.locator || k.citations?.[0]?.locator,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Synthesize fallback evidence record with provenance
|
|
59
|
+
const evId = `ev-know-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
60
|
+
const sourceId = k.provenance || 'configured-knowledge';
|
|
61
|
+
collectedEvidence.push({
|
|
62
|
+
evidenceId: evId,
|
|
63
|
+
sourceId,
|
|
64
|
+
revision: k.revision,
|
|
65
|
+
origin: 'knowledge',
|
|
66
|
+
trust: 'configured',
|
|
67
|
+
sensitivity: 'public',
|
|
68
|
+
companionId,
|
|
69
|
+
correlationId: requestContext.conversation.correlationId,
|
|
70
|
+
createdAt: new Date().toISOString(),
|
|
71
|
+
});
|
|
72
|
+
citations.push({
|
|
73
|
+
sourceId,
|
|
74
|
+
revision: k.revision,
|
|
75
|
+
documentId: k.citations?.[0]?.documentId,
|
|
76
|
+
chunkId: k.citations?.[0]?.chunkId,
|
|
77
|
+
locator: k.citations?.[0]?.locator,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
knowledgeData,
|
|
84
|
+
memoryData,
|
|
85
|
+
activeDirectives,
|
|
86
|
+
subsystemDiagnostics,
|
|
87
|
+
collectedEvidence,
|
|
88
|
+
citations,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ActorContext {
|
|
2
|
+
actorId: string;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
authenticated?: boolean;
|
|
5
|
+
capabilities?: string[];
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface ConversationContext {
|
|
9
|
+
correlationId: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
channel?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
export type SubjectKind = 'actor' | 'companion' | 'configured';
|
|
15
|
+
export interface SubjectRef {
|
|
16
|
+
subjectId: string;
|
|
17
|
+
kind: SubjectKind;
|
|
18
|
+
ownerActorId?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RequestContext {
|
|
21
|
+
companionId: string;
|
|
22
|
+
actor: ActorContext;
|
|
23
|
+
conversation: ConversationContext;
|
|
24
|
+
source?: 'local' | 'external' | string;
|
|
25
|
+
subject?: SubjectRef;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped';
|
|
29
|
+
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'FORBIDDEN_CONTEXT' | 'AMBIGUOUS_CONTEXT' | 'UNAUTHORIZED_CAPABILITY';
|
|
30
|
+
export interface ContextError {
|
|
31
|
+
code: ContextErrorCode;
|
|
32
|
+
message?: string;
|
|
33
|
+
fields?: string[];
|
|
34
|
+
field?: string;
|
|
35
|
+
correlationId?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface RequestContextValidationResult {
|
|
38
|
+
accepted: boolean;
|
|
39
|
+
context?: RequestContext;
|
|
40
|
+
diagnostics?: DiagnosticCode[];
|
|
41
|
+
error?: ContextError;
|
|
42
|
+
}
|
|
43
|
+
export declare function isValidSubjectKind(kind: unknown): kind is SubjectKind;
|
|
44
|
+
export declare function validateRequestContext(context: unknown): RequestContextValidationResult;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Single-owner, single-machine context model
|
|
3
|
+
// Security perimeter is the local machine boundary (external vs internal).
|
|
4
|
+
// No internal audience, viewer, or owner role hierarchies.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isValidSubjectKind = isValidSubjectKind;
|
|
7
|
+
exports.validateRequestContext = validateRequestContext;
|
|
8
|
+
function isValidSubjectKind(kind) {
|
|
9
|
+
return kind === 'actor' || kind === 'companion' || kind === 'configured';
|
|
10
|
+
}
|
|
11
|
+
function validateRequestContext(context) {
|
|
12
|
+
if (!context || typeof context !== 'object') {
|
|
13
|
+
return {
|
|
14
|
+
accepted: false,
|
|
15
|
+
error: {
|
|
16
|
+
code: 'MISSING_CONTEXT',
|
|
17
|
+
fields: ['context'],
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const ctx = context;
|
|
22
|
+
const missingFields = [];
|
|
23
|
+
if (!ctx.companionId || typeof ctx.companionId !== 'string' || ctx.companionId.trim() === '') {
|
|
24
|
+
missingFields.push('companionId');
|
|
25
|
+
}
|
|
26
|
+
if (!ctx.actor || typeof ctx.actor !== 'object') {
|
|
27
|
+
missingFields.push('actor');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (!ctx.actor.actorId || typeof ctx.actor.actorId !== 'string' || ctx.actor.actorId.trim() === '') {
|
|
31
|
+
missingFields.push('actor.actorId');
|
|
32
|
+
}
|
|
33
|
+
if (!ctx.actor.sessionId || typeof ctx.actor.sessionId !== 'string' || ctx.actor.sessionId.trim() === '') {
|
|
34
|
+
missingFields.push('actor.sessionId');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!ctx.conversation || typeof ctx.conversation !== 'object') {
|
|
38
|
+
missingFields.push('conversation');
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
if (!ctx.conversation.correlationId || typeof ctx.conversation.correlationId !== 'string' || ctx.conversation.correlationId.trim() === '') {
|
|
42
|
+
missingFields.push('conversation.correlationId');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (ctx.subject !== undefined) {
|
|
46
|
+
if (!ctx.subject || typeof ctx.subject !== 'object') {
|
|
47
|
+
missingFields.push('subject');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (!ctx.subject.subjectId || typeof ctx.subject.subjectId !== 'string' || ctx.subject.subjectId.trim() === '') {
|
|
51
|
+
missingFields.push('subject.subjectId');
|
|
52
|
+
}
|
|
53
|
+
if (!isValidSubjectKind(ctx.subject.kind)) {
|
|
54
|
+
missingFields.push('subject.kind');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (missingFields.length > 0) {
|
|
59
|
+
return {
|
|
60
|
+
accepted: false,
|
|
61
|
+
error: {
|
|
62
|
+
code: 'MISSING_CONTEXT',
|
|
63
|
+
fields: missingFields,
|
|
64
|
+
correlationId: ctx.conversation?.correlationId,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
accepted: true,
|
|
70
|
+
context: ctx,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const context_1 = require("./context");
|
|
4
|
+
describe('Core Context Contract (Single-Owner Single-Machine)', () => {
|
|
5
|
+
const validContext = {
|
|
6
|
+
companionId: 'companion-a',
|
|
7
|
+
actor: {
|
|
8
|
+
actorId: 'local-user',
|
|
9
|
+
sessionId: 'session-a',
|
|
10
|
+
authenticated: true,
|
|
11
|
+
capabilities: ['chat:interact'],
|
|
12
|
+
},
|
|
13
|
+
conversation: {
|
|
14
|
+
correlationId: 'corr-a',
|
|
15
|
+
},
|
|
16
|
+
subject: {
|
|
17
|
+
subjectId: 'actor:local-user',
|
|
18
|
+
kind: 'actor',
|
|
19
|
+
ownerActorId: 'local-user',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
test('validates a correct RequestContext', () => {
|
|
23
|
+
const result = (0, context_1.validateRequestContext)(validContext);
|
|
24
|
+
expect(result.accepted).toBe(true);
|
|
25
|
+
expect(result.context).toEqual(validContext);
|
|
26
|
+
expect(result.error).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
test('validates a correct RequestContext without subject', () => {
|
|
29
|
+
const { subject, ...contextWithoutSubject } = validContext;
|
|
30
|
+
const result = (0, context_1.validateRequestContext)(contextWithoutSubject);
|
|
31
|
+
expect(result.accepted).toBe(true);
|
|
32
|
+
expect(result.context?.subject).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
test('rejects missing root context or non-object', () => {
|
|
35
|
+
const result = (0, context_1.validateRequestContext)(null);
|
|
36
|
+
expect(result.accepted).toBe(false);
|
|
37
|
+
expect(result.error?.code).toBe('MISSING_CONTEXT');
|
|
38
|
+
expect(result.error?.fields).toContain('context');
|
|
39
|
+
});
|
|
40
|
+
test('rejects missing companionId, actor, or conversation', () => {
|
|
41
|
+
const result = (0, context_1.validateRequestContext)({});
|
|
42
|
+
expect(result.accepted).toBe(false);
|
|
43
|
+
expect(result.error?.code).toBe('MISSING_CONTEXT');
|
|
44
|
+
expect(result.error?.fields).toEqual(expect.arrayContaining(['companionId', 'actor', 'conversation']));
|
|
45
|
+
});
|
|
46
|
+
test('validates subject kinds and constraints', () => {
|
|
47
|
+
expect((0, context_1.isValidSubjectKind)('actor')).toBe(true);
|
|
48
|
+
expect((0, context_1.isValidSubjectKind)('companion')).toBe(true);
|
|
49
|
+
expect((0, context_1.isValidSubjectKind)('configured')).toBe(true);
|
|
50
|
+
expect((0, context_1.isValidSubjectKind)('user')).toBe(false);
|
|
51
|
+
const invalidSubjectCtx = {
|
|
52
|
+
...validContext,
|
|
53
|
+
subject: { subjectId: 'subject-1', kind: 'invalid_kind' },
|
|
54
|
+
};
|
|
55
|
+
const result = (0, context_1.validateRequestContext)(invalidSubjectCtx);
|
|
56
|
+
expect(result.accepted).toBe(false);
|
|
57
|
+
expect(result.error?.fields).toContain('subject.kind');
|
|
58
|
+
});
|
|
59
|
+
test('rejects missing correlationId and preserves correlationId in error if present', () => {
|
|
60
|
+
const missingCorr = {
|
|
61
|
+
...validContext,
|
|
62
|
+
conversation: { ...validContext.conversation, correlationId: '' },
|
|
63
|
+
};
|
|
64
|
+
const result = (0, context_1.validateRequestContext)(missingCorr);
|
|
65
|
+
expect(result.accepted).toBe(false);
|
|
66
|
+
expect(result.error?.fields).toContain('conversation.correlationId');
|
|
67
|
+
const missingActorId = {
|
|
68
|
+
...validContext,
|
|
69
|
+
actor: { ...validContext.actor, actorId: '' },
|
|
70
|
+
};
|
|
71
|
+
const result2 = (0, context_1.validateRequestContext)(missingActorId);
|
|
72
|
+
expect(result2.accepted).toBe(false);
|
|
73
|
+
expect(result2.error?.fields).toContain('actor.actorId');
|
|
74
|
+
expect(result2.error?.correlationId).toBe('corr-a');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ExperienceAdapter, ExperienceEvent, ExperienceAdapterResult } from './experience';
|
|
2
|
+
export interface DispatchSummary {
|
|
3
|
+
dispatched: boolean;
|
|
4
|
+
eventResults: {
|
|
5
|
+
event: ExperienceEvent;
|
|
6
|
+
result: ExperienceAdapterResult;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
export declare class ExperienceDispatcher {
|
|
10
|
+
private readonly adapters;
|
|
11
|
+
private readonly dispatchedEventIds;
|
|
12
|
+
registerAdapter(adapter: ExperienceAdapter): void;
|
|
13
|
+
dispatchEvents(events: ExperienceEvent[]): Promise<DispatchSummary>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExperienceDispatcher = void 0;
|
|
4
|
+
class ExperienceDispatcher {
|
|
5
|
+
adapters = [];
|
|
6
|
+
dispatchedEventIds = new Set();
|
|
7
|
+
registerAdapter(adapter) {
|
|
8
|
+
this.adapters.push(adapter);
|
|
9
|
+
}
|
|
10
|
+
async dispatchEvents(events) {
|
|
11
|
+
const eventResults = [];
|
|
12
|
+
for (const event of events) {
|
|
13
|
+
// Replay / duplicate dispatch protection: each eventId is dispatched only once
|
|
14
|
+
if (this.dispatchedEventIds.has(event.eventId)) {
|
|
15
|
+
eventResults.push({
|
|
16
|
+
event,
|
|
17
|
+
result: {
|
|
18
|
+
accepted: false,
|
|
19
|
+
eventId: event.eventId,
|
|
20
|
+
lifecycle: 'FAILED',
|
|
21
|
+
error: 'Duplicate event ID already dispatched',
|
|
22
|
+
reason: 'DUPLICATE_EVENT_DISPATCH',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
this.dispatchedEventIds.add(event.eventId);
|
|
28
|
+
const matchingAdapters = this.adapters.filter((a) => a.kind === event.kind);
|
|
29
|
+
for (const adapter of matchingAdapters) {
|
|
30
|
+
const result = await adapter.handleEvent(event);
|
|
31
|
+
eventResults.push({ event, result });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
dispatched: eventResults.some((r) => r.result.accepted),
|
|
36
|
+
eventResults,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.ExperienceDispatcher = ExperienceDispatcher;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const dispatcher_1 = require("./dispatcher");
|
|
4
|
+
const experience_1 = require("./experience");
|
|
5
|
+
describe('T5 ExperienceDispatcher Contract Suite', () => {
|
|
6
|
+
let dispatcher;
|
|
7
|
+
let mockVoiceAdapter;
|
|
8
|
+
let mockAvatarAdapter;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
dispatcher = new dispatcher_1.ExperienceDispatcher();
|
|
11
|
+
mockVoiceAdapter = {
|
|
12
|
+
kind: 'voice',
|
|
13
|
+
handleEvent: jest.fn().mockImplementation(async (event) => ({
|
|
14
|
+
accepted: true,
|
|
15
|
+
eventId: event.eventId,
|
|
16
|
+
lifecycle: 'STARTED',
|
|
17
|
+
})),
|
|
18
|
+
};
|
|
19
|
+
mockAvatarAdapter = {
|
|
20
|
+
kind: 'avatar',
|
|
21
|
+
handleEvent: jest.fn().mockImplementation(async (event) => ({
|
|
22
|
+
accepted: true,
|
|
23
|
+
eventId: event.eventId,
|
|
24
|
+
lifecycle: 'STARTED',
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
dispatcher.registerAdapter(mockVoiceAdapter);
|
|
28
|
+
dispatcher.registerAdapter(mockAvatarAdapter);
|
|
29
|
+
});
|
|
30
|
+
test('dispatches experience events to matching adapters', async () => {
|
|
31
|
+
const events = (0, experience_1.createExperienceEvents)({
|
|
32
|
+
responseId: 'resp-1',
|
|
33
|
+
companionId: 'companion-a',
|
|
34
|
+
correlationId: 'corr-1',
|
|
35
|
+
channel: 'public',
|
|
36
|
+
speech: 'Hello dispatch',
|
|
37
|
+
language: 'en',
|
|
38
|
+
});
|
|
39
|
+
const summary = await dispatcher.dispatchEvents(events);
|
|
40
|
+
expect(summary.dispatched).toBe(true);
|
|
41
|
+
expect(summary.eventResults.length).toBe(2);
|
|
42
|
+
expect(mockVoiceAdapter.handleEvent).toHaveBeenCalledTimes(1);
|
|
43
|
+
expect(mockAvatarAdapter.handleEvent).toHaveBeenCalledTimes(1);
|
|
44
|
+
});
|
|
45
|
+
test('does not dispatch when no matching adapters registered', async () => {
|
|
46
|
+
const emptyDispatcher = new dispatcher_1.ExperienceDispatcher();
|
|
47
|
+
const events = (0, experience_1.createExperienceEvents)({
|
|
48
|
+
responseId: 'resp-1',
|
|
49
|
+
companionId: 'companion-a',
|
|
50
|
+
correlationId: 'corr-1',
|
|
51
|
+
channel: 'public',
|
|
52
|
+
speech: 'Hello empty',
|
|
53
|
+
});
|
|
54
|
+
const summary = await emptyDispatcher.dispatchEvents(events);
|
|
55
|
+
expect(summary.dispatched).toBe(false);
|
|
56
|
+
expect(summary.eventResults.length).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RequestContext } from './context';
|
|
2
|
+
import { EarPerception } from './index';
|
|
3
|
+
export interface EarLimitsConfig {
|
|
4
|
+
maxTextLength?: number;
|
|
5
|
+
maxAudioBytes?: number;
|
|
6
|
+
allowedAudioMimeTypes?: string[];
|
|
7
|
+
maxDurationSeconds?: number;
|
|
8
|
+
transcriptionTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface EarPerceptionMetadata extends Record<string, unknown> {
|
|
11
|
+
source?: string;
|
|
12
|
+
channel?: string;
|
|
13
|
+
modality?: 'text' | 'audio' | 'object' | 'system';
|
|
14
|
+
confidence?: number;
|
|
15
|
+
provenance?: string;
|
|
16
|
+
actorId?: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
correlationId?: string;
|
|
19
|
+
byteSize?: number;
|
|
20
|
+
durationSeconds?: number;
|
|
21
|
+
mimeType?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface HardenedEarPerception extends EarPerception {
|
|
24
|
+
modality: 'text' | 'audio' | 'object' | 'system';
|
|
25
|
+
metadata?: EarPerceptionMetadata;
|
|
26
|
+
rawConfidence?: number;
|
|
27
|
+
}
|
|
28
|
+
export interface EarIngestOptions {
|
|
29
|
+
source?: string;
|
|
30
|
+
context?: RequestContext;
|
|
31
|
+
mimeType?: string;
|
|
32
|
+
durationSeconds?: number;
|
|
33
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { MemoryProposal, BehaviorProposal } from './proposals';
|
|
2
|
+
export type EvidenceOrigin = 'knowledge' | 'observation' | 'ocr' | 'platform' | 'conversation';
|
|
3
|
+
export type EvidenceTrust = 'configured' | 'provider' | 'untrusted';
|
|
4
|
+
export type EvidenceSensitivity = 'public' | 'private' | 'restricted';
|
|
5
|
+
export interface EvidenceRecord {
|
|
6
|
+
evidenceId: string;
|
|
7
|
+
sourceId: string;
|
|
8
|
+
documentId?: string;
|
|
9
|
+
chunkId?: string;
|
|
10
|
+
locator?: string;
|
|
11
|
+
revision?: string;
|
|
12
|
+
origin: EvidenceOrigin;
|
|
13
|
+
confidence?: number;
|
|
14
|
+
uncertainty?: string;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
expiresAt?: string;
|
|
17
|
+
trust: EvidenceTrust;
|
|
18
|
+
sensitivity?: EvidenceSensitivity;
|
|
19
|
+
companionId: string;
|
|
20
|
+
correlationId: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export type ResponseApprovalStatus = 'STAGED' | 'APPROVED' | 'REJECTED' | 'EXPIRED' | 'EMITTED';
|
|
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';
|
|
25
|
+
export interface ResponseCitation {
|
|
26
|
+
sourceId: string;
|
|
27
|
+
documentId?: string;
|
|
28
|
+
chunkId?: string;
|
|
29
|
+
locator?: string;
|
|
30
|
+
revision?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface StagedResponsePlan {
|
|
33
|
+
responseId: string;
|
|
34
|
+
companionId: string;
|
|
35
|
+
correlationId: string;
|
|
36
|
+
channel?: string;
|
|
37
|
+
speech: string;
|
|
38
|
+
language: string;
|
|
39
|
+
evidenceIds: string[];
|
|
40
|
+
citations: ResponseCitation[];
|
|
41
|
+
confidenceSummary: number;
|
|
42
|
+
uncertaintySummary?: string;
|
|
43
|
+
requiresApproval: boolean;
|
|
44
|
+
status: ResponseApprovalStatus;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
expiresAt?: string;
|
|
47
|
+
memoryProposals?: MemoryProposal[];
|
|
48
|
+
behaviorProposals?: BehaviorProposal[];
|
|
49
|
+
internalMonologue?: string;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
export interface ResponseGateEvaluation {
|
|
53
|
+
admissible: boolean;
|
|
54
|
+
disposition: ResponseApprovalStatus;
|
|
55
|
+
reasonCode: ResponseGateReasonCode;
|
|
56
|
+
stagedPlan: StagedResponsePlan;
|
|
57
|
+
filteredEvidenceIds: string[];
|
|
58
|
+
filteredCitations: ResponseCitation[];
|
|
59
|
+
diagnostics?: Record<string, string>;
|
|
60
|
+
}
|
|
61
|
+
export interface EvidenceFilterOptions {
|
|
62
|
+
companionId: string;
|
|
63
|
+
channel?: string;
|
|
64
|
+
now?: string | Date;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
export declare function filterEvidenceRecords(records: EvidenceRecord[], options: EvidenceFilterOptions): {
|
|
68
|
+
admitted: EvidenceRecord[];
|
|
69
|
+
excluded: {
|
|
70
|
+
record: EvidenceRecord;
|
|
71
|
+
reason: string;
|
|
72
|
+
}[];
|
|
73
|
+
};
|
package/dist/evidence.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterEvidenceRecords = filterEvidenceRecords;
|
|
4
|
+
function filterEvidenceRecords(records, options) {
|
|
5
|
+
const admitted = [];
|
|
6
|
+
const excluded = [];
|
|
7
|
+
const nowTime = options.now ? new Date(options.now).getTime() : Date.now();
|
|
8
|
+
for (const record of records) {
|
|
9
|
+
// 1. Companion isolation
|
|
10
|
+
if (record.companionId !== options.companionId) {
|
|
11
|
+
excluded.push({ record, reason: 'companion_isolation_mismatch' });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
// 2. Expiry
|
|
15
|
+
if (record.expiresAt) {
|
|
16
|
+
const expTime = new Date(record.expiresAt).getTime();
|
|
17
|
+
if (expTime <= nowTime) {
|
|
18
|
+
excluded.push({ record, reason: 'evidence_expired' });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// In single-owner architecture, memory and evidence are partitioned strictly
|
|
23
|
+
// by companion boundary (companionId) and temporal expiration.
|
|
24
|
+
admitted.push(record);
|
|
25
|
+
}
|
|
26
|
+
return { admitted, excluded };
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const evidence_1 = require("./evidence");
|
|
4
|
+
describe('T4 Evidence & Disclosure Core Contract', () => {
|
|
5
|
+
const baseRecord = {
|
|
6
|
+
evidenceId: 'ev-1',
|
|
7
|
+
sourceId: 'src-1',
|
|
8
|
+
origin: 'knowledge',
|
|
9
|
+
trust: 'configured',
|
|
10
|
+
sensitivity: 'public',
|
|
11
|
+
companionId: 'companion-a',
|
|
12
|
+
correlationId: 'corr-1',
|
|
13
|
+
createdAt: new Date(Date.now() - 5000).toISOString(),
|
|
14
|
+
};
|
|
15
|
+
test('companion isolation excludes foreign companion evidence', () => {
|
|
16
|
+
const records = [
|
|
17
|
+
{ ...baseRecord, evidenceId: 'ev-mine', companionId: 'companion-a' },
|
|
18
|
+
{ ...baseRecord, evidenceId: 'ev-foreign', companionId: 'companion-b' },
|
|
19
|
+
];
|
|
20
|
+
const options = {
|
|
21
|
+
companionId: 'companion-a',
|
|
22
|
+
channel: 'public',
|
|
23
|
+
};
|
|
24
|
+
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
25
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-mine']);
|
|
26
|
+
expect(excluded).toEqual([
|
|
27
|
+
expect.objectContaining({
|
|
28
|
+
record: expect.objectContaining({ evidenceId: 'ev-foreign' }),
|
|
29
|
+
reason: 'companion_isolation_mismatch',
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
test('expired evidence is excluded', () => {
|
|
34
|
+
const records = [
|
|
35
|
+
{ ...baseRecord, evidenceId: 'ev-valid', expiresAt: new Date(Date.now() + 60000).toISOString() },
|
|
36
|
+
{ ...baseRecord, evidenceId: 'ev-expired', expiresAt: new Date(Date.now() - 1000).toISOString() },
|
|
37
|
+
];
|
|
38
|
+
const options = {
|
|
39
|
+
companionId: 'companion-a',
|
|
40
|
+
channel: 'public',
|
|
41
|
+
};
|
|
42
|
+
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
43
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-valid']);
|
|
44
|
+
expect(excluded).toEqual([
|
|
45
|
+
expect.objectContaining({
|
|
46
|
+
record: expect.objectContaining({ evidenceId: 'ev-expired' }),
|
|
47
|
+
reason: 'evidence_expired',
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
test('single-owner companion admits valid evidence without multi-audience filtering', () => {
|
|
52
|
+
const records = [
|
|
53
|
+
{ ...baseRecord, evidenceId: 'ev-public' },
|
|
54
|
+
{ ...baseRecord, evidenceId: 'ev-direct-only' },
|
|
55
|
+
];
|
|
56
|
+
const options = {
|
|
57
|
+
companionId: 'companion-a',
|
|
58
|
+
};
|
|
59
|
+
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
|
|
60
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-public', 'ev-direct-only']);
|
|
61
|
+
expect(excluded).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
test('single-owner companion admits private and restricted sensitivity for companion owner', () => {
|
|
64
|
+
const records = [
|
|
65
|
+
{ ...baseRecord, evidenceId: 'ev-pub', sensitivity: 'public' },
|
|
66
|
+
{ ...baseRecord, evidenceId: 'ev-priv', sensitivity: 'private' },
|
|
67
|
+
{ ...baseRecord, evidenceId: 'ev-rest', sensitivity: 'restricted' },
|
|
68
|
+
];
|
|
69
|
+
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, {
|
|
70
|
+
companionId: 'companion-a',
|
|
71
|
+
});
|
|
72
|
+
expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-pub', 'ev-priv', 'ev-rest']);
|
|
73
|
+
expect(excluded).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RequestContext, StagedResponsePlan, ResponseGateEvaluation, ExperienceDispatcher, ExperienceEvent, ExperienceAdapter, VoiceOrgan, BodyOrgan } from './index';
|
|
2
|
+
export interface ExperienceEmissionParams {
|
|
3
|
+
companionId: string;
|
|
4
|
+
requestContext: RequestContext;
|
|
5
|
+
stagedPlan: StagedResponsePlan;
|
|
6
|
+
gateEval: ResponseGateEvaluation;
|
|
7
|
+
speech: string;
|
|
8
|
+
language: string;
|
|
9
|
+
dispatcher: ExperienceDispatcher;
|
|
10
|
+
voice?: VoiceOrgan | ExperienceAdapter;
|
|
11
|
+
body?: BodyOrgan | ExperienceAdapter;
|
|
12
|
+
}
|
|
13
|
+
export interface ExperienceEmissionResult {
|
|
14
|
+
experienceEvents: ExperienceEvent[];
|
|
15
|
+
speechId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates ExperienceEvents, dispatches them through the ExperienceDispatcher,
|
|
19
|
+
* and coordinates backward-compatible fallbacks for legacy Voice/Body adapters.
|
|
20
|
+
*/
|
|
21
|
+
export declare function emitExperienceEvents(params: ExperienceEmissionParams): Promise<ExperienceEmissionResult>;
|