@siduri-x/core 1.0.0
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.d.ts +45 -0
- package/dist/action-policy.js +207 -0
- package/dist/action-policy.test.d.ts +1 -0
- package/dist/action-policy.test.js +157 -0
- package/dist/action.d.ts +70 -0
- package/dist/action.js +2 -0
- package/dist/architecture-boundary.test.d.ts +1 -0
- package/dist/architecture-boundary.test.js +116 -0
- package/dist/capability.d.ts +52 -0
- package/dist/capability.js +102 -0
- package/dist/capability.test.d.ts +1 -0
- package/dist/capability.test.js +180 -0
- package/dist/context.d.ts +47 -0
- package/dist/context.js +92 -0
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +109 -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 +60 -0
- package/dist/ear-types.d.ts +33 -0
- package/dist/ear-types.js +2 -0
- package/dist/evidence.d.ts +72 -0
- package/dist/evidence.js +45 -0
- package/dist/evidence.test.d.ts +1 -0
- package/dist/evidence.test.js +101 -0
- package/dist/experience.d.ts +56 -0
- package/dist/experience.js +78 -0
- package/dist/experience.test.d.ts +1 -0
- package/dist/experience.test.js +58 -0
- package/dist/gating.d.ts +45 -0
- package/dist/gating.js +189 -0
- package/dist/gating.test.d.ts +1 -0
- package/dist/gating.test.js +190 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +28 -0
- package/dist/runtime.d.ts +50 -0
- package/dist/runtime.js +404 -0
- package/dist/teaching.d.ts +15 -0
- package/dist/teaching.js +159 -0
- package/package.json +43 -0
|
@@ -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,72 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createExperienceEvents = createExperienceEvents;
|
|
4
|
+
exports.validateExperienceEvent = validateExperienceEvent;
|
|
5
|
+
function generateEventId(kind) {
|
|
6
|
+
return `evt-${kind}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
7
|
+
}
|
|
8
|
+
function createExperienceEvents(options) {
|
|
9
|
+
const nowStr = options.now
|
|
10
|
+
? new Date(options.now).toISOString()
|
|
11
|
+
: new Date().toISOString();
|
|
12
|
+
const events = [];
|
|
13
|
+
// 1. Voice event
|
|
14
|
+
events.push({
|
|
15
|
+
eventId: generateEventId('voice'),
|
|
16
|
+
companionId: options.companionId,
|
|
17
|
+
responseId: options.responseId,
|
|
18
|
+
correlationId: options.correlationId,
|
|
19
|
+
channel: options.channel,
|
|
20
|
+
audienceId: options.audienceId,
|
|
21
|
+
approval: 'APPROVED',
|
|
22
|
+
kind: 'voice',
|
|
23
|
+
lifecycle: 'STARTED',
|
|
24
|
+
evidenceIds: options.evidenceIds ?? [],
|
|
25
|
+
citations: options.citations,
|
|
26
|
+
text: options.speech,
|
|
27
|
+
language: options.language || 'ja',
|
|
28
|
+
createdAt: nowStr,
|
|
29
|
+
expiresAt: options.expiresAt,
|
|
30
|
+
});
|
|
31
|
+
// 2. Avatar/Body event
|
|
32
|
+
events.push({
|
|
33
|
+
eventId: generateEventId('avatar'),
|
|
34
|
+
companionId: options.companionId,
|
|
35
|
+
responseId: options.responseId,
|
|
36
|
+
correlationId: options.correlationId,
|
|
37
|
+
channel: options.channel,
|
|
38
|
+
audienceId: options.audienceId,
|
|
39
|
+
approval: 'APPROVED',
|
|
40
|
+
kind: 'avatar',
|
|
41
|
+
lifecycle: 'STARTED',
|
|
42
|
+
evidenceIds: options.evidenceIds ?? [],
|
|
43
|
+
text: options.speech,
|
|
44
|
+
language: options.language || 'ja',
|
|
45
|
+
expression: options.expression || 'neutral',
|
|
46
|
+
action: options.action || 'talk',
|
|
47
|
+
createdAt: nowStr,
|
|
48
|
+
expiresAt: options.expiresAt,
|
|
49
|
+
});
|
|
50
|
+
return events;
|
|
51
|
+
}
|
|
52
|
+
function validateExperienceEvent(event) {
|
|
53
|
+
if (!event || typeof event !== 'object') {
|
|
54
|
+
return { valid: false, error: 'Event must be an object' };
|
|
55
|
+
}
|
|
56
|
+
const e = event;
|
|
57
|
+
if (!e.eventId || typeof e.eventId !== 'string')
|
|
58
|
+
return { valid: false, error: 'Missing or invalid eventId' };
|
|
59
|
+
if (!e.companionId || typeof e.companionId !== 'string')
|
|
60
|
+
return { valid: false, error: 'Missing or invalid companionId' };
|
|
61
|
+
if (!e.responseId || typeof e.responseId !== 'string')
|
|
62
|
+
return { valid: false, error: 'Missing or invalid responseId' };
|
|
63
|
+
if (!e.correlationId || typeof e.correlationId !== 'string')
|
|
64
|
+
return { valid: false, error: 'Missing or invalid correlationId' };
|
|
65
|
+
if (!e.audienceId || typeof e.audienceId !== 'string')
|
|
66
|
+
return { valid: false, error: 'Missing or invalid audienceId' };
|
|
67
|
+
if (e.approval !== 'APPROVED')
|
|
68
|
+
return { valid: false, error: 'Event approval must be APPROVED' };
|
|
69
|
+
if (!['voice', 'caption', 'avatar', 'platform_action'].includes(e.kind)) {
|
|
70
|
+
return { valid: false, error: `Invalid kind: ${e.kind}` };
|
|
71
|
+
}
|
|
72
|
+
if (!['STARTED', 'PROGRESS', 'COMPLETED', 'FAILED'].includes(e.lifecycle)) {
|
|
73
|
+
return { valid: false, error: `Invalid lifecycle: ${e.lifecycle}` };
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(e.evidenceIds))
|
|
76
|
+
return { valid: false, error: 'Missing or invalid evidenceIds array' };
|
|
77
|
+
return { valid: true };
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const experience_1 = require("./experience");
|
|
4
|
+
describe('T5 Experience Event Contract Suite', () => {
|
|
5
|
+
const baseEventOptions = {
|
|
6
|
+
responseId: 'resp-123',
|
|
7
|
+
companionId: 'companion-a',
|
|
8
|
+
correlationId: 'corr-123',
|
|
9
|
+
channel: 'public',
|
|
10
|
+
audienceId: 'audience-public',
|
|
11
|
+
speech: 'Hello world',
|
|
12
|
+
language: 'en',
|
|
13
|
+
evidenceIds: ['ev-1'],
|
|
14
|
+
expression: 'happy',
|
|
15
|
+
action: 'wave',
|
|
16
|
+
};
|
|
17
|
+
test('creates structured voice and avatar experience events from approved response options', () => {
|
|
18
|
+
const events = (0, experience_1.createExperienceEvents)(baseEventOptions);
|
|
19
|
+
expect(events.length).toBe(2);
|
|
20
|
+
const voiceEvent = events.find((e) => e.kind === 'voice');
|
|
21
|
+
expect(voiceEvent).toBeDefined();
|
|
22
|
+
expect(voiceEvent?.approval).toBe('APPROVED');
|
|
23
|
+
expect(voiceEvent?.companionId).toBe('companion-a');
|
|
24
|
+
expect(voiceEvent?.correlationId).toBe('corr-123');
|
|
25
|
+
expect(voiceEvent?.text).toBe('Hello world');
|
|
26
|
+
expect(voiceEvent?.language).toBe('en');
|
|
27
|
+
expect(voiceEvent?.evidenceIds).toEqual(['ev-1']);
|
|
28
|
+
const avatarEvent = events.find((e) => e.kind === 'avatar');
|
|
29
|
+
expect(avatarEvent).toBeDefined();
|
|
30
|
+
expect(avatarEvent?.approval).toBe('APPROVED');
|
|
31
|
+
expect(avatarEvent?.expression).toBe('happy');
|
|
32
|
+
expect(avatarEvent?.action).toBe('wave');
|
|
33
|
+
});
|
|
34
|
+
test('validates experience event envelope correctly', () => {
|
|
35
|
+
const events = (0, experience_1.createExperienceEvents)(baseEventOptions);
|
|
36
|
+
for (const e of events) {
|
|
37
|
+
const val = (0, experience_1.validateExperienceEvent)(e);
|
|
38
|
+
expect(val.valid).toBe(true);
|
|
39
|
+
expect(val.error).toBeUndefined();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
test('rejects unapproved experience event', () => {
|
|
43
|
+
const events = (0, experience_1.createExperienceEvents)(baseEventOptions);
|
|
44
|
+
const unapproved = { ...events[0], approval: 'STAGED' };
|
|
45
|
+
const val = (0, experience_1.validateExperienceEvent)(unapproved);
|
|
46
|
+
expect(val.valid).toBe(false);
|
|
47
|
+
expect(val.error).toContain('Event approval must be APPROVED');
|
|
48
|
+
});
|
|
49
|
+
test('rejects missing metadata (companionId, responseId, etc.)', () => {
|
|
50
|
+
const events = (0, experience_1.createExperienceEvents)(baseEventOptions);
|
|
51
|
+
const missingCompanion = { ...events[0], companionId: '' };
|
|
52
|
+
expect((0, experience_1.validateExperienceEvent)(missingCompanion).valid).toBe(false);
|
|
53
|
+
const missingCorr = { ...events[0], correlationId: '' };
|
|
54
|
+
expect((0, experience_1.validateExperienceEvent)(missingCorr).valid).toBe(false);
|
|
55
|
+
const missingAudience = { ...events[0], audienceId: '' };
|
|
56
|
+
expect((0, experience_1.validateExperienceEvent)(missingAudience).valid).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/dist/gating.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RequestContext } from './context';
|
|
2
|
+
import { EvidenceRecord, StagedResponsePlan, ResponseGateEvaluation, ResponseCitation } from './evidence';
|
|
3
|
+
export interface StageResponseOptions {
|
|
4
|
+
requestContext: RequestContext;
|
|
5
|
+
candidateSpeech: string;
|
|
6
|
+
candidateLanguage: string;
|
|
7
|
+
internalMonologue?: string;
|
|
8
|
+
memoryProposals?: any[];
|
|
9
|
+
behaviorProposals?: any[];
|
|
10
|
+
evidenceRecords?: EvidenceRecord[];
|
|
11
|
+
citations?: ResponseCitation[];
|
|
12
|
+
requiresApproval?: boolean;
|
|
13
|
+
ttlMs?: number;
|
|
14
|
+
now?: string | Date;
|
|
15
|
+
}
|
|
16
|
+
export interface ApproveResponseOptions {
|
|
17
|
+
responseId: string;
|
|
18
|
+
companionId: string;
|
|
19
|
+
correlationId: string;
|
|
20
|
+
audienceId?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RejectResponseOptions {
|
|
23
|
+
responseId: string;
|
|
24
|
+
companionId: string;
|
|
25
|
+
correlationId: string;
|
|
26
|
+
reason?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare class ResponseGatingEngine {
|
|
29
|
+
private readonly stagedPlans;
|
|
30
|
+
private readonly consumedApprovals;
|
|
31
|
+
stageResponse(options: StageResponseOptions): StagedResponsePlan;
|
|
32
|
+
evaluateGate(staged: StagedResponsePlan, allEvidence?: EvidenceRecord[], now?: string | Date): ResponseGateEvaluation;
|
|
33
|
+
approveResponse(options: ApproveResponseOptions): {
|
|
34
|
+
success: boolean;
|
|
35
|
+
reason?: string;
|
|
36
|
+
plan?: StagedResponsePlan;
|
|
37
|
+
};
|
|
38
|
+
rejectResponse(options: RejectResponseOptions): {
|
|
39
|
+
success: boolean;
|
|
40
|
+
reason?: string;
|
|
41
|
+
plan?: StagedResponsePlan;
|
|
42
|
+
};
|
|
43
|
+
getStagedPlan(responseId: string): StagedResponsePlan | undefined;
|
|
44
|
+
findStagedPlanByCorrelation(companionId: string, correlationId: string): StagedResponsePlan | undefined;
|
|
45
|
+
}
|