@siduri-x/core 1.0.5 → 1.0.7

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 (85) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +50 -0
  3. package/dist/action-policy.d.ts +45 -0
  4. package/dist/action-policy.js +219 -0
  5. package/dist/action-policy.test.d.ts +1 -0
  6. package/dist/action-policy.test.js +194 -0
  7. package/dist/action.d.ts +72 -0
  8. package/dist/action.js +2 -0
  9. package/dist/adversarial.test.d.ts +1 -0
  10. package/dist/adversarial.test.js +493 -0
  11. package/dist/architecture-boundary.test.d.ts +1 -0
  12. package/dist/architecture-boundary.test.js +117 -0
  13. package/dist/capability.d.ts +65 -0
  14. package/dist/capability.js +157 -0
  15. package/dist/capability.test.d.ts +1 -0
  16. package/dist/capability.test.js +269 -0
  17. package/dist/chat-contract.d.ts +81 -0
  18. package/dist/chat-contract.js +68 -0
  19. package/dist/cognition-planner.d.ts +15 -0
  20. package/dist/cognition-planner.js +23 -0
  21. package/dist/context-retriever.d.ts +24 -0
  22. package/dist/context-retriever.js +92 -0
  23. package/dist/context.d.ts +45 -0
  24. package/dist/context.js +72 -0
  25. package/dist/context.test.d.ts +1 -0
  26. package/dist/context.test.js +76 -0
  27. package/dist/dispatcher.d.ts +14 -0
  28. package/dist/dispatcher.js +40 -0
  29. package/dist/dispatcher.test.d.ts +1 -0
  30. package/dist/dispatcher.test.js +60 -0
  31. package/dist/ear-types.d.ts +33 -0
  32. package/dist/ear-types.js +2 -0
  33. package/dist/evidence.d.ts +76 -0
  34. package/dist/evidence.js +47 -0
  35. package/dist/evidence.test.d.ts +1 -0
  36. package/dist/evidence.test.js +101 -0
  37. package/dist/experience-emitter.d.ts +21 -0
  38. package/dist/experience-emitter.js +48 -0
  39. package/dist/experience.d.ts +57 -0
  40. package/dist/experience.js +79 -0
  41. package/dist/experience.test.d.ts +1 -0
  42. package/dist/experience.test.js +59 -0
  43. package/dist/gating.d.ts +46 -0
  44. package/dist/gating.js +185 -0
  45. package/dist/gating.test.d.ts +1 -0
  46. package/dist/gating.test.js +190 -0
  47. package/dist/index.d.ts +250 -0
  48. package/dist/index.js +43 -0
  49. package/dist/input-normalizer.d.ts +14 -0
  50. package/dist/input-normalizer.js +58 -0
  51. package/dist/input-normalizer.test.d.ts +1 -0
  52. package/dist/input-normalizer.test.js +39 -0
  53. package/dist/intent-classifier.d.ts +24 -0
  54. package/dist/intent-classifier.js +53 -0
  55. package/dist/intent-classifier.test.d.ts +1 -0
  56. package/dist/intent-classifier.test.js +68 -0
  57. package/dist/memory-settler.d.ts +27 -0
  58. package/dist/memory-settler.js +95 -0
  59. package/dist/mouth-types.d.ts +85 -0
  60. package/dist/mouth-types.js +2 -0
  61. package/dist/perception-cycle.test.d.ts +1 -0
  62. package/dist/perception-cycle.test.js +155 -0
  63. package/dist/prompt-compiler.d.ts +20 -0
  64. package/dist/prompt-compiler.js +57 -0
  65. package/dist/prompt-compiler.test.d.ts +1 -0
  66. package/dist/prompt-compiler.test.js +76 -0
  67. package/dist/proposals.d.ts +30 -0
  68. package/dist/proposals.js +2 -0
  69. package/dist/response-envelope.d.ts +25 -0
  70. package/dist/response-envelope.js +64 -0
  71. package/dist/runtime-facades.test.d.ts +1 -0
  72. package/dist/runtime-facades.test.js +69 -0
  73. package/dist/runtime.d.ts +114 -0
  74. package/dist/runtime.js +412 -0
  75. package/dist/session-history.d.ts +20 -0
  76. package/dist/session-history.js +55 -0
  77. package/dist/session-history.test.d.ts +1 -0
  78. package/dist/session-history.test.js +38 -0
  79. package/dist/sqlite-action-store.d.ts +20 -0
  80. package/dist/sqlite-action-store.js +225 -0
  81. package/dist/sqlite-action-store.test.d.ts +1 -0
  82. package/dist/sqlite-action-store.test.js +252 -0
  83. package/dist/teaching.d.ts +15 -0
  84. package/dist/teaching.js +159 -0
  85. package/package.json +1 -1
@@ -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,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>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitExperienceEvents = emitExperienceEvents;
4
+ const index_1 = require("./index");
5
+ /**
6
+ * Creates ExperienceEvents, dispatches them through the ExperienceDispatcher,
7
+ * and coordinates backward-compatible fallbacks for legacy Voice/Body adapters.
8
+ */
9
+ async function emitExperienceEvents(params) {
10
+ const { companionId, requestContext, stagedPlan, gateEval, speech, language, dispatcher, voice, body, } = params;
11
+ const experienceEvents = (0, index_1.createExperienceEvents)({
12
+ responseId: stagedPlan.responseId,
13
+ companionId,
14
+ correlationId: requestContext.conversation.correlationId,
15
+ channel: requestContext.conversation?.channel,
16
+ audienceId: requestContext.conversation?.audienceId,
17
+ speech,
18
+ language: language || 'ja',
19
+ evidenceIds: gateEval.filteredEvidenceIds,
20
+ citations: gateEval.filteredCitations,
21
+ expression: 'neutral',
22
+ action: 'talk',
23
+ expiresAt: stagedPlan.expiresAt,
24
+ });
25
+ const dispatchResult = await dispatcher.dispatchEvents(experienceEvents);
26
+ let speechId;
27
+ const voiceResult = dispatchResult.eventResults.find((r) => r.event.kind === 'voice');
28
+ if (voiceResult?.result?.metadata?.speechId) {
29
+ speechId = voiceResult.result.metadata.speechId;
30
+ }
31
+ else if (voice &&
32
+ typeof voice.handleEvent !== 'function' &&
33
+ typeof voice.enqueueSpeech === 'function') {
34
+ speechId = voice.enqueueSpeech(speech, language || 'ja', 1);
35
+ }
36
+ if (body && typeof body.handleEvent !== 'function') {
37
+ if (typeof body.setExpression === 'function') {
38
+ body.setExpression('neutral');
39
+ }
40
+ if (typeof body.act === 'function') {
41
+ body.act('talk');
42
+ }
43
+ }
44
+ return {
45
+ experienceEvents,
46
+ speechId,
47
+ };
48
+ }
@@ -0,0 +1,57 @@
1
+ import { ResponseCitation } from './evidence';
2
+ export type ExperienceEventKind = 'voice' | 'caption' | 'avatar' | 'platform_action';
3
+ export type ExperienceEventLifecycle = 'STARTED' | 'PROGRESS' | 'COMPLETED' | 'FAILED';
4
+ export interface ExperienceEvent {
5
+ eventId: string;
6
+ companionId: string;
7
+ responseId: string;
8
+ correlationId: string;
9
+ channel?: string;
10
+ audienceId?: string;
11
+ approval: 'APPROVED';
12
+ kind: ExperienceEventKind;
13
+ lifecycle: ExperienceEventLifecycle;
14
+ evidenceIds: string[];
15
+ citations?: ResponseCitation[];
16
+ text?: string;
17
+ language?: string;
18
+ action?: string;
19
+ expression?: string;
20
+ createdAt: string;
21
+ expiresAt?: string;
22
+ [key: string]: unknown;
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?: string;
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
+ [key: string]: unknown;
52
+ }
53
+ export declare function createExperienceEvents(options: CreateExperienceEventsOptions): ExperienceEvent[];
54
+ export declare function validateExperienceEvent(event: unknown): {
55
+ valid: boolean;
56
+ error?: string;
57
+ };
@@ -0,0 +1,79 @@
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 !== undefined && typeof e.audienceId !== 'string') {
66
+ return { valid: false, error: 'Invalid audienceId: must be a string' };
67
+ }
68
+ if (e.approval !== 'APPROVED')
69
+ return { valid: false, error: 'Event approval must be APPROVED' };
70
+ if (!['voice', 'caption', 'avatar', 'platform_action'].includes(e.kind)) {
71
+ return { valid: false, error: `Invalid kind: ${e.kind}` };
72
+ }
73
+ if (!['STARTED', 'PROGRESS', 'COMPLETED', 'FAILED'].includes(e.lifecycle)) {
74
+ return { valid: false, error: `Invalid lifecycle: ${e.lifecycle}` };
75
+ }
76
+ if (!Array.isArray(e.evidenceIds))
77
+ return { valid: false, error: 'Missing or invalid evidenceIds array' };
78
+ return { valid: true };
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
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 missingResponseId = { ...events[0], responseId: '' };
54
+ 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
+ });
59
+ });
@@ -0,0 +1,46 @@
1
+ import { RequestContext } from './context';
2
+ import { MemoryProposal, BehaviorProposal } from './proposals';
3
+ import { EvidenceRecord, StagedResponsePlan, ResponseGateEvaluation, ResponseCitation } from './evidence';
4
+ export interface StageResponseOptions {
5
+ requestContext: RequestContext;
6
+ candidateSpeech: string;
7
+ candidateLanguage: string;
8
+ internalMonologue?: string;
9
+ memoryProposals?: MemoryProposal[];
10
+ behaviorProposals?: BehaviorProposal[];
11
+ evidenceRecords?: EvidenceRecord[];
12
+ citations?: ResponseCitation[];
13
+ requiresApproval?: boolean;
14
+ ttlMs?: number;
15
+ now?: string | Date;
16
+ }
17
+ export interface ApproveResponseOptions {
18
+ responseId: string;
19
+ companionId: string;
20
+ correlationId: string;
21
+ audienceId?: string;
22
+ }
23
+ export interface RejectResponseOptions {
24
+ responseId: string;
25
+ companionId: string;
26
+ correlationId: string;
27
+ reason?: string;
28
+ }
29
+ export declare class ResponseGatingEngine {
30
+ private readonly stagedPlans;
31
+ private readonly consumedApprovals;
32
+ stageResponse(options: StageResponseOptions): StagedResponsePlan;
33
+ evaluateGate(staged: StagedResponsePlan, allEvidence?: EvidenceRecord[], now?: string | Date): ResponseGateEvaluation;
34
+ approveResponse(options: ApproveResponseOptions): {
35
+ success: boolean;
36
+ reason?: string;
37
+ plan?: StagedResponsePlan;
38
+ };
39
+ rejectResponse(options: RejectResponseOptions): {
40
+ success: boolean;
41
+ reason?: string;
42
+ plan?: StagedResponsePlan;
43
+ };
44
+ getStagedPlan(responseId: string): StagedResponsePlan | undefined;
45
+ findStagedPlanByCorrelation(companionId: string, correlationId: string): StagedResponsePlan | undefined;
46
+ }
package/dist/gating.js ADDED
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponseGatingEngine = void 0;
4
+ const evidence_1 = require("./evidence");
5
+ function generateId(prefix) {
6
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
7
+ }
8
+ class ResponseGatingEngine {
9
+ stagedPlans = new Map();
10
+ consumedApprovals = new Set();
11
+ stageResponse(options) {
12
+ const { requestContext } = options;
13
+ const nowTime = options.now ? new Date(options.now).getTime() : Date.now();
14
+ const ttlMs = options.ttlMs ?? 60_000;
15
+ const expiresAt = new Date(nowTime + ttlMs).toISOString();
16
+ const evidenceRecords = options.evidenceRecords ?? [];
17
+ const evidenceIds = evidenceRecords.map((e) => e.evidenceId);
18
+ // Calculate aggregate confidence from evidence records if present
19
+ let confidenceSummary = 1.0;
20
+ let uncertaintySummary;
21
+ if (evidenceRecords.length > 0) {
22
+ const confidences = evidenceRecords
23
+ .map((e) => e.confidence)
24
+ .filter((c) => typeof c === 'number' && !isNaN(c));
25
+ if (confidences.length > 0) {
26
+ confidenceSummary = confidences.reduce((sum, c) => sum + c, 0) / confidences.length;
27
+ }
28
+ const uncertainties = evidenceRecords
29
+ .map((e) => e.uncertainty)
30
+ .filter((u) => typeof u === 'string' && u.trim() !== '');
31
+ if (uncertainties.length > 0) {
32
+ uncertaintySummary = uncertainties.join('; ');
33
+ }
34
+ }
35
+ const requiresApproval = options.requiresApproval !== undefined
36
+ ? options.requiresApproval
37
+ : evidenceRecords.some((e) => e.origin === 'ocr' || e.trust === 'untrusted');
38
+ const staged = {
39
+ responseId: generateId('resp'),
40
+ companionId: requestContext.companionId,
41
+ correlationId: requestContext.conversation.correlationId,
42
+ channel: requestContext.conversation?.channel,
43
+ audienceId: requestContext.conversation?.audienceId,
44
+ speech: options.candidateSpeech,
45
+ language: options.candidateLanguage,
46
+ evidenceIds,
47
+ citations: options.citations ?? [],
48
+ confidenceSummary,
49
+ uncertaintySummary,
50
+ requiresApproval,
51
+ status: 'STAGED',
52
+ createdAt: new Date(nowTime).toISOString(),
53
+ expiresAt,
54
+ memoryProposals: options.memoryProposals,
55
+ behaviorProposals: options.behaviorProposals,
56
+ internalMonologue: options.internalMonologue,
57
+ };
58
+ this.stagedPlans.set(staged.responseId, staged);
59
+ return staged;
60
+ }
61
+ evaluateGate(staged, allEvidence = [], now = new Date()) {
62
+ const nowTime = new Date(now).getTime();
63
+ // 1. Check if empty speech
64
+ if (!staged.speech || staged.speech.trim() === '') {
65
+ return {
66
+ admissible: false,
67
+ disposition: 'REJECTED',
68
+ reasonCode: 'EMPTY_SPEECH',
69
+ stagedPlan: staged,
70
+ filteredEvidenceIds: [],
71
+ filteredCitations: [],
72
+ diagnostics: { detail: 'Speech content is empty' },
73
+ };
74
+ }
75
+ // 2. Check if expired
76
+ if (staged.expiresAt && new Date(staged.expiresAt).getTime() <= nowTime) {
77
+ staged.status = 'EXPIRED';
78
+ return {
79
+ admissible: false,
80
+ disposition: 'EXPIRED',
81
+ reasonCode: 'EVIDENCE_EXPIRED',
82
+ stagedPlan: staged,
83
+ filteredEvidenceIds: [],
84
+ filteredCitations: [],
85
+ diagnostics: { detail: 'Staged response plan expired' },
86
+ };
87
+ }
88
+ // 3. Disclosure filter on attached evidence
89
+ const attachedEvidence = allEvidence.filter((e) => staged.evidenceIds.includes(e.evidenceId));
90
+ const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(attachedEvidence, {
91
+ companionId: staged.companionId,
92
+ channel: staged.channel,
93
+ audienceId: staged.audienceId,
94
+ now,
95
+ });
96
+ // If any evidence attached to this plan violated disclosure in this channel, exclude it
97
+ const admittedEvidenceIds = admitted.map((e) => e.evidenceId);
98
+ const filteredCitations = staged.citations.filter((c) => admitted.some((e) => e.sourceId === c.sourceId || (e.documentId && e.documentId === c.documentId)));
99
+ // 4. Explicitly rejected or status check
100
+ if (staged.status === 'REJECTED') {
101
+ return {
102
+ admissible: false,
103
+ disposition: 'REJECTED',
104
+ reasonCode: 'EXPLICITLY_REJECTED',
105
+ stagedPlan: staged,
106
+ filteredEvidenceIds: admittedEvidenceIds,
107
+ filteredCitations,
108
+ };
109
+ }
110
+ // 5. Staged approval check
111
+ if (staged.requiresApproval && staged.status !== 'APPROVED') {
112
+ return {
113
+ admissible: false,
114
+ disposition: staged.status,
115
+ reasonCode: 'APPROVAL_REQUIRED',
116
+ stagedPlan: staged,
117
+ filteredEvidenceIds: admittedEvidenceIds,
118
+ filteredCitations,
119
+ diagnostics: {
120
+ detail: 'Response plan requires approval before external emission',
121
+ excludedEvidenceCount: String(excluded.length),
122
+ },
123
+ };
124
+ }
125
+ // 6. Direct approval / Admissible
126
+ return {
127
+ admissible: true,
128
+ disposition: staged.status === 'APPROVED' ? 'APPROVED' : 'APPROVED',
129
+ reasonCode: 'APPROVED_DIRECT',
130
+ stagedPlan: staged,
131
+ filteredEvidenceIds: admittedEvidenceIds,
132
+ filteredCitations,
133
+ };
134
+ }
135
+ approveResponse(options) {
136
+ const plan = this.stagedPlans.get(options.responseId);
137
+ if (!plan) {
138
+ return { success: false, reason: 'UNKNOWN_APPROVAL_ID' };
139
+ }
140
+ if (this.consumedApprovals.has(options.responseId) || plan.status === 'APPROVED') {
141
+ return { success: false, reason: 'APPROVAL_ALREADY_CONSUMED' };
142
+ }
143
+ if (plan.companionId !== options.companionId) {
144
+ return { success: false, reason: 'COMPANION_MISMATCH' };
145
+ }
146
+ if (plan.correlationId !== options.correlationId) {
147
+ return { success: false, reason: 'APPROVAL_ID_MISMATCH' };
148
+ }
149
+ if (plan.status === 'EXPIRED') {
150
+ return { success: false, reason: 'EVIDENCE_EXPIRED' };
151
+ }
152
+ if (plan.status === 'REJECTED') {
153
+ return { success: false, reason: 'EXPLICITLY_REJECTED' };
154
+ }
155
+ plan.status = 'APPROVED';
156
+ this.consumedApprovals.add(options.responseId);
157
+ return { success: true, plan };
158
+ }
159
+ rejectResponse(options) {
160
+ const plan = this.stagedPlans.get(options.responseId);
161
+ if (!plan) {
162
+ return { success: false, reason: 'UNKNOWN_APPROVAL_ID' };
163
+ }
164
+ if (plan.companionId !== options.companionId) {
165
+ return { success: false, reason: 'COMPANION_MISMATCH' };
166
+ }
167
+ if (plan.correlationId !== options.correlationId) {
168
+ return { success: false, reason: 'APPROVAL_ID_MISMATCH' };
169
+ }
170
+ plan.status = 'REJECTED';
171
+ return { success: true, plan };
172
+ }
173
+ getStagedPlan(responseId) {
174
+ return this.stagedPlans.get(responseId);
175
+ }
176
+ findStagedPlanByCorrelation(companionId, correlationId) {
177
+ for (const plan of this.stagedPlans.values()) {
178
+ if (plan.companionId === companionId && plan.correlationId === correlationId) {
179
+ return plan;
180
+ }
181
+ }
182
+ return undefined;
183
+ }
184
+ }
185
+ exports.ResponseGatingEngine = ResponseGatingEngine;
@@ -0,0 +1 @@
1
+ export {};