@siduri-x/core 1.0.3 → 1.0.5

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/dist/action-policy.d.ts +0 -45
  3. package/dist/action-policy.js +0 -217
  4. package/dist/action-policy.test.d.ts +0 -1
  5. package/dist/action-policy.test.js +0 -157
  6. package/dist/action.d.ts +0 -72
  7. package/dist/action.js +0 -2
  8. package/dist/adversarial.test.d.ts +0 -1
  9. package/dist/adversarial.test.js +0 -493
  10. package/dist/architecture-boundary.test.d.ts +0 -1
  11. package/dist/architecture-boundary.test.js +0 -116
  12. package/dist/capability.d.ts +0 -57
  13. package/dist/capability.js +0 -130
  14. package/dist/capability.test.d.ts +0 -1
  15. package/dist/capability.test.js +0 -269
  16. package/dist/chat-contract.d.ts +0 -78
  17. package/dist/chat-contract.js +0 -63
  18. package/dist/context.d.ts +0 -47
  19. package/dist/context.js +0 -92
  20. package/dist/context.test.d.ts +0 -1
  21. package/dist/context.test.js +0 -109
  22. package/dist/dispatcher.d.ts +0 -14
  23. package/dist/dispatcher.js +0 -40
  24. package/dist/dispatcher.test.d.ts +0 -1
  25. package/dist/dispatcher.test.js +0 -60
  26. package/dist/ear-types.d.ts +0 -33
  27. package/dist/ear-types.js +0 -2
  28. package/dist/evidence.d.ts +0 -72
  29. package/dist/evidence.js +0 -45
  30. package/dist/evidence.test.d.ts +0 -1
  31. package/dist/evidence.test.js +0 -101
  32. package/dist/experience.d.ts +0 -56
  33. package/dist/experience.js +0 -78
  34. package/dist/experience.test.d.ts +0 -1
  35. package/dist/experience.test.js +0 -58
  36. package/dist/gating.d.ts +0 -45
  37. package/dist/gating.js +0 -189
  38. package/dist/gating.test.d.ts +0 -1
  39. package/dist/gating.test.js +0 -190
  40. package/dist/index.d.ts +0 -266
  41. package/dist/index.js +0 -29
  42. package/dist/runtime.d.ts +0 -50
  43. package/dist/runtime.js +0 -412
  44. package/dist/teaching.d.ts +0 -15
  45. package/dist/teaching.js +0 -159
package/dist/context.js DELETED
@@ -1,92 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isValidAuthorizationRole = isValidAuthorizationRole;
4
- exports.isValidChannel = isValidChannel;
5
- exports.isValidSubjectKind = isValidSubjectKind;
6
- exports.validateRequestContext = validateRequestContext;
7
- function isValidAuthorizationRole(role) {
8
- return role === 'viewer' || role === 'operator' || role === 'administrator';
9
- }
10
- function isValidChannel(channel) {
11
- return channel === 'public' || channel === 'direct' || channel === 'private' || channel === 'operator';
12
- }
13
- function isValidSubjectKind(kind) {
14
- return kind === 'actor' || kind === 'companion' || kind === 'configured';
15
- }
16
- function validateRequestContext(context) {
17
- if (!context || typeof context !== 'object') {
18
- return {
19
- accepted: false,
20
- error: {
21
- code: 'MISSING_CONTEXT',
22
- fields: ['context'],
23
- },
24
- };
25
- }
26
- const ctx = context;
27
- const missingFields = [];
28
- if (!ctx.companionId || typeof ctx.companionId !== 'string' || ctx.companionId.trim() === '') {
29
- missingFields.push('companionId');
30
- }
31
- if (!ctx.actor || typeof ctx.actor !== 'object') {
32
- missingFields.push('actor');
33
- }
34
- else {
35
- if (!ctx.actor.actorId || typeof ctx.actor.actorId !== 'string' || ctx.actor.actorId.trim() === '') {
36
- missingFields.push('actor.actorId');
37
- }
38
- if (!ctx.actor.sessionId || typeof ctx.actor.sessionId !== 'string' || ctx.actor.sessionId.trim() === '') {
39
- missingFields.push('actor.sessionId');
40
- }
41
- if (!isValidAuthorizationRole(ctx.actor.authorizationRole)) {
42
- missingFields.push('actor.authorizationRole');
43
- }
44
- if (!Array.isArray(ctx.actor.capabilities)) {
45
- missingFields.push('actor.capabilities');
46
- }
47
- if (typeof ctx.actor.authenticated !== 'boolean') {
48
- missingFields.push('actor.authenticated');
49
- }
50
- }
51
- if (!ctx.conversation || typeof ctx.conversation !== 'object') {
52
- missingFields.push('conversation');
53
- }
54
- else {
55
- if (!isValidChannel(ctx.conversation.channel)) {
56
- missingFields.push('conversation.channel');
57
- }
58
- if (!ctx.conversation.audienceId || typeof ctx.conversation.audienceId !== 'string' || ctx.conversation.audienceId.trim() === '') {
59
- missingFields.push('conversation.audienceId');
60
- }
61
- if (!ctx.conversation.correlationId || typeof ctx.conversation.correlationId !== 'string' || ctx.conversation.correlationId.trim() === '') {
62
- missingFields.push('conversation.correlationId');
63
- }
64
- }
65
- if (ctx.subject !== undefined) {
66
- if (!ctx.subject || typeof ctx.subject !== 'object') {
67
- missingFields.push('subject');
68
- }
69
- else {
70
- if (!ctx.subject.subjectId || typeof ctx.subject.subjectId !== 'string' || ctx.subject.subjectId.trim() === '') {
71
- missingFields.push('subject.subjectId');
72
- }
73
- if (!isValidSubjectKind(ctx.subject.kind)) {
74
- missingFields.push('subject.kind');
75
- }
76
- }
77
- }
78
- if (missingFields.length > 0) {
79
- return {
80
- accepted: false,
81
- error: {
82
- code: 'MISSING_CONTEXT',
83
- fields: missingFields,
84
- correlationId: ctx.conversation?.correlationId,
85
- },
86
- };
87
- }
88
- return {
89
- accepted: true,
90
- context: ctx,
91
- };
92
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,109 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const context_1 = require("./context");
4
- describe('Core Context Contract (P1)', () => {
5
- const validContext = {
6
- companionId: 'companion-a',
7
- actor: {
8
- actorId: 'actor-a',
9
- sessionId: 'session-a',
10
- authorizationRole: 'viewer',
11
- capabilities: ['chat:public'],
12
- authenticated: false,
13
- },
14
- conversation: {
15
- channel: 'public',
16
- audienceId: 'audience-public',
17
- correlationId: 'corr-a',
18
- },
19
- subject: {
20
- subjectId: 'actor:actor-a',
21
- kind: 'actor',
22
- ownerActorId: 'actor-a',
23
- },
24
- };
25
- test('validates a correct neutral RequestContext', () => {
26
- const result = (0, context_1.validateRequestContext)(validContext);
27
- expect(result.accepted).toBe(true);
28
- expect(result.context).toEqual(validContext);
29
- expect(result.error).toBeUndefined();
30
- });
31
- test('validates a correct RequestContext without subject', () => {
32
- const { subject, ...contextWithoutSubject } = validContext;
33
- const result = (0, context_1.validateRequestContext)(contextWithoutSubject);
34
- expect(result.accepted).toBe(true);
35
- expect(result.context?.subject).toBeUndefined();
36
- });
37
- test('rejects missing root context or non-object', () => {
38
- const result = (0, context_1.validateRequestContext)(null);
39
- expect(result.accepted).toBe(false);
40
- expect(result.error?.code).toBe('MISSING_CONTEXT');
41
- expect(result.error?.fields).toContain('context');
42
- });
43
- test('rejects missing companionId, actor, or conversation', () => {
44
- const result = (0, context_1.validateRequestContext)({});
45
- expect(result.accepted).toBe(false);
46
- expect(result.error?.code).toBe('MISSING_CONTEXT');
47
- expect(result.error?.fields).toEqual(expect.arrayContaining(['companionId', 'actor', 'conversation']));
48
- });
49
- test('validates authorization role constraints', () => {
50
- expect((0, context_1.isValidAuthorizationRole)('viewer')).toBe(true);
51
- expect((0, context_1.isValidAuthorizationRole)('operator')).toBe(true);
52
- expect((0, context_1.isValidAuthorizationRole)('administrator')).toBe(true);
53
- expect((0, context_1.isValidAuthorizationRole)('owner')).toBe(false);
54
- expect((0, context_1.isValidAuthorizationRole)('user')).toBe(false);
55
- expect((0, context_1.isValidAuthorizationRole)('MASTER')).toBe(false);
56
- const invalidRoleCtx = {
57
- ...validContext,
58
- actor: { ...validContext.actor, authorizationRole: 'invalid_role' },
59
- };
60
- const result = (0, context_1.validateRequestContext)(invalidRoleCtx);
61
- expect(result.accepted).toBe(false);
62
- expect(result.error?.fields).toContain('actor.authorizationRole');
63
- });
64
- test('validates channel constraints', () => {
65
- expect((0, context_1.isValidChannel)('public')).toBe(true);
66
- expect((0, context_1.isValidChannel)('direct')).toBe(true);
67
- expect((0, context_1.isValidChannel)('private')).toBe(true);
68
- expect((0, context_1.isValidChannel)('operator')).toBe(true);
69
- expect((0, context_1.isValidChannel)('chat')).toBe(false);
70
- expect((0, context_1.isValidChannel)('MASTER_PRIVATE')).toBe(false);
71
- const invalidChannelCtx = {
72
- ...validContext,
73
- conversation: { ...validContext.conversation, channel: 'invalid_channel' },
74
- };
75
- const result = (0, context_1.validateRequestContext)(invalidChannelCtx);
76
- expect(result.accepted).toBe(false);
77
- expect(result.error?.fields).toContain('conversation.channel');
78
- });
79
- test('validates subject kinds and constraints', () => {
80
- expect((0, context_1.isValidSubjectKind)('actor')).toBe(true);
81
- expect((0, context_1.isValidSubjectKind)('companion')).toBe(true);
82
- expect((0, context_1.isValidSubjectKind)('configured')).toBe(true);
83
- expect((0, context_1.isValidSubjectKind)('user')).toBe(false);
84
- const invalidSubjectCtx = {
85
- ...validContext,
86
- subject: { subjectId: 'subject-1', kind: 'invalid_kind' },
87
- };
88
- const result = (0, context_1.validateRequestContext)(invalidSubjectCtx);
89
- expect(result.accepted).toBe(false);
90
- expect(result.error?.fields).toContain('subject.kind');
91
- });
92
- test('rejects missing correlationId and preserves correlationId in error if present', () => {
93
- const missingCorr = {
94
- ...validContext,
95
- conversation: { ...validContext.conversation, correlationId: '' },
96
- };
97
- const result = (0, context_1.validateRequestContext)(missingCorr);
98
- expect(result.accepted).toBe(false);
99
- expect(result.error?.fields).toContain('conversation.correlationId');
100
- const missingActorId = {
101
- ...validContext,
102
- actor: { ...validContext.actor, actorId: '' },
103
- };
104
- const result2 = (0, context_1.validateRequestContext)(missingActorId);
105
- expect(result2.accepted).toBe(false);
106
- expect(result2.error?.fields).toContain('actor.actorId');
107
- expect(result2.error?.correlationId).toBe('corr-a');
108
- });
109
- });
@@ -1,14 +0,0 @@
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
- }
@@ -1,40 +0,0 @@
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;
@@ -1 +0,0 @@
1
- export {};
@@ -1,60 +0,0 @@
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
- audienceId: 'audience-public',
37
- speech: 'Hello dispatch',
38
- language: 'en',
39
- });
40
- const summary = await dispatcher.dispatchEvents(events);
41
- expect(summary.dispatched).toBe(true);
42
- expect(summary.eventResults.length).toBe(2);
43
- expect(mockVoiceAdapter.handleEvent).toHaveBeenCalledTimes(1);
44
- expect(mockAvatarAdapter.handleEvent).toHaveBeenCalledTimes(1);
45
- });
46
- test('does not dispatch when no matching adapters registered', async () => {
47
- const emptyDispatcher = new dispatcher_1.ExperienceDispatcher();
48
- const events = (0, experience_1.createExperienceEvents)({
49
- responseId: 'resp-1',
50
- companionId: 'companion-a',
51
- correlationId: 'corr-1',
52
- channel: 'public',
53
- audienceId: 'audience-public',
54
- speech: 'Hello empty',
55
- });
56
- const summary = await emptyDispatcher.dispatchEvents(events);
57
- expect(summary.dispatched).toBe(false);
58
- expect(summary.eventResults.length).toBe(0);
59
- });
60
- });
@@ -1,33 +0,0 @@
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
- }
package/dist/ear-types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,72 +0,0 @@
1
- export type EvidenceOrigin = 'knowledge' | 'observation' | 'ocr' | 'platform' | 'conversation';
2
- export type EvidenceTrust = 'configured' | 'provider' | 'untrusted';
3
- export type EvidenceSensitivity = 'public' | 'private' | 'restricted';
4
- export interface EvidenceRecord {
5
- evidenceId: string;
6
- sourceId: string;
7
- documentId?: string;
8
- chunkId?: string;
9
- locator?: string;
10
- revision?: string;
11
- origin: EvidenceOrigin;
12
- confidence?: number;
13
- uncertainty?: string;
14
- createdAt: string;
15
- expiresAt?: string;
16
- trust: EvidenceTrust;
17
- sensitivity: EvidenceSensitivity;
18
- allowedAudiences: string[];
19
- companionId: string;
20
- correlationId: string;
21
- }
22
- export type ResponseApprovalStatus = 'STAGED' | 'APPROVED' | 'REJECTED' | 'EXPIRED' | 'EMITTED';
23
- export type ResponseGateReasonCode = 'APPROVED_DIRECT' | 'APPROVAL_REQUIRED' | 'UNKNOWN_APPROVAL_ID' | 'APPROVAL_ID_MISMATCH' | 'COMPANION_MISMATCH' | 'AUDIENCE_MISMATCH' | 'EVIDENCE_EXPIRED' | 'EVIDENCE_SENSITIVITY_EXCLUDED' | 'UNRESOLVED_LOW_CONFIDENCE' | 'EMPTY_SPEECH' | 'PROPOSAL_VALIDATION_FAILED' | 'EXPLICITLY_REJECTED';
24
- export interface ResponseCitation {
25
- sourceId: string;
26
- documentId?: string;
27
- chunkId?: string;
28
- locator?: string;
29
- revision?: string;
30
- }
31
- export interface StagedResponsePlan {
32
- responseId: string;
33
- companionId: string;
34
- correlationId: string;
35
- channel: 'public' | 'direct' | 'private' | 'operator';
36
- audienceId: 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?: any[];
48
- behaviorProposals?: any[];
49
- internalMonologue?: string;
50
- }
51
- export interface ResponseGateEvaluation {
52
- admissible: boolean;
53
- disposition: ResponseApprovalStatus;
54
- reasonCode: ResponseGateReasonCode;
55
- stagedPlan: StagedResponsePlan;
56
- filteredEvidenceIds: string[];
57
- filteredCitations: ResponseCitation[];
58
- diagnostics?: Record<string, string>;
59
- }
60
- export interface EvidenceFilterOptions {
61
- companionId: string;
62
- channel: 'public' | 'direct' | 'private' | 'operator';
63
- audienceId: string;
64
- now?: string | Date;
65
- }
66
- export declare function filterEvidenceRecords(records: EvidenceRecord[], options: EvidenceFilterOptions): {
67
- admitted: EvidenceRecord[];
68
- excluded: {
69
- record: EvidenceRecord;
70
- reason: string;
71
- }[];
72
- };
package/dist/evidence.js DELETED
@@ -1,45 +0,0 @@
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
- // 3. Audience intersection
23
- if (record.allowedAudiences &&
24
- record.allowedAudiences.length > 0 &&
25
- !record.allowedAudiences.includes(options.audienceId)) {
26
- excluded.push({ record, reason: 'audience_not_allowed' });
27
- continue;
28
- }
29
- // 4. Sensitivity policy based on channel
30
- if (options.channel === 'public') {
31
- if (record.sensitivity !== 'public') {
32
- excluded.push({ record, reason: 'sensitivity_private_in_public_channel' });
33
- continue;
34
- }
35
- }
36
- else if (options.channel === 'direct') {
37
- if (record.sensitivity === 'restricted') {
38
- excluded.push({ record, reason: 'sensitivity_restricted_in_direct_channel' });
39
- continue;
40
- }
41
- }
42
- admitted.push(record);
43
- }
44
- return { admitted, excluded };
45
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,101 +0,0 @@
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
- allowedAudiences: ['audience-public'],
12
- companionId: 'companion-a',
13
- correlationId: 'corr-1',
14
- createdAt: new Date(Date.now() - 5000).toISOString(),
15
- };
16
- test('companion isolation excludes foreign companion evidence', () => {
17
- const records = [
18
- { ...baseRecord, evidenceId: 'ev-mine', companionId: 'companion-a' },
19
- { ...baseRecord, evidenceId: 'ev-foreign', companionId: 'companion-b' },
20
- ];
21
- const options = {
22
- companionId: 'companion-a',
23
- channel: 'public',
24
- audienceId: 'audience-public',
25
- };
26
- const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
27
- expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-mine']);
28
- expect(excluded).toEqual([
29
- expect.objectContaining({
30
- record: expect.objectContaining({ evidenceId: 'ev-foreign' }),
31
- reason: 'companion_isolation_mismatch',
32
- }),
33
- ]);
34
- });
35
- test('expired evidence is excluded', () => {
36
- const records = [
37
- { ...baseRecord, evidenceId: 'ev-valid', expiresAt: new Date(Date.now() + 60000).toISOString() },
38
- { ...baseRecord, evidenceId: 'ev-expired', expiresAt: new Date(Date.now() - 1000).toISOString() },
39
- ];
40
- const options = {
41
- companionId: 'companion-a',
42
- channel: 'public',
43
- audienceId: 'audience-public',
44
- };
45
- const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, options);
46
- expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-valid']);
47
- expect(excluded).toEqual([
48
- expect.objectContaining({
49
- record: expect.objectContaining({ evidenceId: 'ev-expired' }),
50
- reason: 'evidence_expired',
51
- }),
52
- ]);
53
- });
54
- test('audience intersection excludes non-matching audiences', () => {
55
- const records = [
56
- { ...baseRecord, evidenceId: 'ev-public', allowedAudiences: ['audience-public'] },
57
- { ...baseRecord, evidenceId: 'ev-direct-only', allowedAudiences: ['audience-direct-a'] },
58
- ];
59
- const options = {
60
- companionId: 'companion-a',
61
- channel: 'public',
62
- audienceId: 'audience-public',
63
- };
64
- 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']);
86
- });
87
- test('direct channel permits private sensitivity but excludes restricted', () => {
88
- const records = [
89
- { ...baseRecord, evidenceId: 'ev-pub', sensitivity: 'public', allowedAudiences: ['audience-direct-a'] },
90
- { ...baseRecord, evidenceId: 'ev-priv', sensitivity: 'private', allowedAudiences: ['audience-direct-a'] },
91
- { ...baseRecord, evidenceId: 'ev-rest', sensitivity: 'restricted', allowedAudiences: ['audience-direct-a'] },
92
- ];
93
- const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(records, {
94
- companionId: 'companion-a',
95
- channel: 'direct',
96
- audienceId: 'audience-direct-a',
97
- });
98
- expect(admitted.map((e) => e.evidenceId)).toEqual(['ev-pub', 'ev-priv']);
99
- expect(excluded.map((e) => e.record.evidenceId)).toEqual(['ev-rest']);
100
- });
101
- });
@@ -1,56 +0,0 @@
1
- import { Channel } from './context';
2
- import { ResponseCitation } from './evidence';
3
- export type ExperienceEventKind = 'voice' | 'caption' | 'avatar' | 'platform_action';
4
- export type ExperienceEventLifecycle = 'STARTED' | 'PROGRESS' | 'COMPLETED' | 'FAILED';
5
- export interface ExperienceEvent {
6
- eventId: string;
7
- companionId: string;
8
- responseId: string;
9
- correlationId: string;
10
- channel: Channel;
11
- audienceId: string;
12
- approval: 'APPROVED';
13
- kind: ExperienceEventKind;
14
- lifecycle: ExperienceEventLifecycle;
15
- evidenceIds: string[];
16
- citations?: ResponseCitation[];
17
- text?: string;
18
- language?: string;
19
- action?: string;
20
- expression?: string;
21
- createdAt: string;
22
- expiresAt?: string;
23
- }
24
- export interface ExperienceAdapterResult {
25
- accepted: boolean;
26
- eventId: string;
27
- lifecycle: ExperienceEventLifecycle;
28
- error?: string;
29
- reason?: string;
30
- audioBuffer?: Uint8Array;
31
- metadata?: Record<string, unknown>;
32
- }
33
- export interface ExperienceAdapter {
34
- readonly kind: ExperienceEventKind;
35
- handleEvent(event: ExperienceEvent): Promise<ExperienceAdapterResult>;
36
- }
37
- export interface CreateExperienceEventsOptions {
38
- responseId: string;
39
- companionId: string;
40
- correlationId: string;
41
- channel: Channel;
42
- audienceId: string;
43
- speech: string;
44
- language?: string;
45
- evidenceIds?: string[];
46
- citations?: ResponseCitation[];
47
- expression?: string;
48
- action?: string;
49
- expiresAt?: string;
50
- now?: string | Date;
51
- }
52
- export declare function createExperienceEvents(options: CreateExperienceEventsOptions): ExperienceEvent[];
53
- export declare function validateExperienceEvent(event: unknown): {
54
- valid: boolean;
55
- error?: string;
56
- };