@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
package/dist/gating.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
: requestContext.conversation.channel === 'operator' ||
|
|
38
|
+
evidenceRecords.some((e) => e.origin === 'ocr' || e.trust === 'untrusted');
|
|
39
|
+
const staged = {
|
|
40
|
+
responseId: generateId('resp'),
|
|
41
|
+
companionId: requestContext.companionId,
|
|
42
|
+
correlationId: requestContext.conversation.correlationId,
|
|
43
|
+
channel: requestContext.conversation.channel,
|
|
44
|
+
audienceId: requestContext.conversation.audienceId,
|
|
45
|
+
speech: options.candidateSpeech,
|
|
46
|
+
language: options.candidateLanguage,
|
|
47
|
+
evidenceIds,
|
|
48
|
+
citations: options.citations ?? [],
|
|
49
|
+
confidenceSummary,
|
|
50
|
+
uncertaintySummary,
|
|
51
|
+
requiresApproval,
|
|
52
|
+
status: 'STAGED',
|
|
53
|
+
createdAt: new Date(nowTime).toISOString(),
|
|
54
|
+
expiresAt,
|
|
55
|
+
memoryProposals: options.memoryProposals,
|
|
56
|
+
behaviorProposals: options.behaviorProposals,
|
|
57
|
+
internalMonologue: options.internalMonologue,
|
|
58
|
+
};
|
|
59
|
+
this.stagedPlans.set(staged.responseId, staged);
|
|
60
|
+
return staged;
|
|
61
|
+
}
|
|
62
|
+
evaluateGate(staged, allEvidence = [], now = new Date()) {
|
|
63
|
+
const nowTime = new Date(now).getTime();
|
|
64
|
+
// 1. Check if empty speech
|
|
65
|
+
if (!staged.speech || staged.speech.trim() === '') {
|
|
66
|
+
return {
|
|
67
|
+
admissible: false,
|
|
68
|
+
disposition: 'REJECTED',
|
|
69
|
+
reasonCode: 'EMPTY_SPEECH',
|
|
70
|
+
stagedPlan: staged,
|
|
71
|
+
filteredEvidenceIds: [],
|
|
72
|
+
filteredCitations: [],
|
|
73
|
+
diagnostics: { detail: 'Speech content is empty' },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// 2. Check if expired
|
|
77
|
+
if (staged.expiresAt && new Date(staged.expiresAt).getTime() <= nowTime) {
|
|
78
|
+
staged.status = 'EXPIRED';
|
|
79
|
+
return {
|
|
80
|
+
admissible: false,
|
|
81
|
+
disposition: 'EXPIRED',
|
|
82
|
+
reasonCode: 'EVIDENCE_EXPIRED',
|
|
83
|
+
stagedPlan: staged,
|
|
84
|
+
filteredEvidenceIds: [],
|
|
85
|
+
filteredCitations: [],
|
|
86
|
+
diagnostics: { detail: 'Staged response plan expired' },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// 3. Disclosure filter on attached evidence
|
|
90
|
+
const attachedEvidence = allEvidence.filter((e) => staged.evidenceIds.includes(e.evidenceId));
|
|
91
|
+
const { admitted, excluded } = (0, evidence_1.filterEvidenceRecords)(attachedEvidence, {
|
|
92
|
+
companionId: staged.companionId,
|
|
93
|
+
channel: staged.channel,
|
|
94
|
+
audienceId: staged.audienceId,
|
|
95
|
+
now,
|
|
96
|
+
});
|
|
97
|
+
// If any evidence attached to this plan violated disclosure in this channel, exclude it
|
|
98
|
+
const admittedEvidenceIds = admitted.map((e) => e.evidenceId);
|
|
99
|
+
const filteredCitations = staged.citations.filter((c) => admitted.some((e) => e.sourceId === c.sourceId || (e.documentId && e.documentId === c.documentId)));
|
|
100
|
+
// 4. Explicitly rejected or status check
|
|
101
|
+
if (staged.status === 'REJECTED') {
|
|
102
|
+
return {
|
|
103
|
+
admissible: false,
|
|
104
|
+
disposition: 'REJECTED',
|
|
105
|
+
reasonCode: 'EXPLICITLY_REJECTED',
|
|
106
|
+
stagedPlan: staged,
|
|
107
|
+
filteredEvidenceIds: admittedEvidenceIds,
|
|
108
|
+
filteredCitations,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// 5. Staged approval check
|
|
112
|
+
if (staged.requiresApproval && staged.status !== 'APPROVED') {
|
|
113
|
+
return {
|
|
114
|
+
admissible: false,
|
|
115
|
+
disposition: staged.status,
|
|
116
|
+
reasonCode: 'APPROVAL_REQUIRED',
|
|
117
|
+
stagedPlan: staged,
|
|
118
|
+
filteredEvidenceIds: admittedEvidenceIds,
|
|
119
|
+
filteredCitations,
|
|
120
|
+
diagnostics: {
|
|
121
|
+
detail: 'Response plan requires operator approval before external emission',
|
|
122
|
+
excludedEvidenceCount: String(excluded.length),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// 6. Direct approval / Admissible
|
|
127
|
+
return {
|
|
128
|
+
admissible: true,
|
|
129
|
+
disposition: staged.status === 'APPROVED' ? 'APPROVED' : 'APPROVED',
|
|
130
|
+
reasonCode: 'APPROVED_DIRECT',
|
|
131
|
+
stagedPlan: staged,
|
|
132
|
+
filteredEvidenceIds: admittedEvidenceIds,
|
|
133
|
+
filteredCitations,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
approveResponse(options) {
|
|
137
|
+
const plan = this.stagedPlans.get(options.responseId);
|
|
138
|
+
if (!plan) {
|
|
139
|
+
return { success: false, reason: 'UNKNOWN_APPROVAL_ID' };
|
|
140
|
+
}
|
|
141
|
+
if (this.consumedApprovals.has(options.responseId) || plan.status === 'APPROVED') {
|
|
142
|
+
return { success: false, reason: 'APPROVAL_ALREADY_CONSUMED' };
|
|
143
|
+
}
|
|
144
|
+
if (plan.companionId !== options.companionId) {
|
|
145
|
+
return { success: false, reason: 'COMPANION_MISMATCH' };
|
|
146
|
+
}
|
|
147
|
+
if (plan.correlationId !== options.correlationId) {
|
|
148
|
+
return { success: false, reason: 'APPROVAL_ID_MISMATCH' };
|
|
149
|
+
}
|
|
150
|
+
if (options.audienceId && plan.audienceId !== options.audienceId) {
|
|
151
|
+
return { success: false, reason: 'AUDIENCE_MISMATCH' };
|
|
152
|
+
}
|
|
153
|
+
if (plan.status === 'EXPIRED') {
|
|
154
|
+
return { success: false, reason: 'EVIDENCE_EXPIRED' };
|
|
155
|
+
}
|
|
156
|
+
if (plan.status === 'REJECTED') {
|
|
157
|
+
return { success: false, reason: 'EXPLICITLY_REJECTED' };
|
|
158
|
+
}
|
|
159
|
+
plan.status = 'APPROVED';
|
|
160
|
+
this.consumedApprovals.add(options.responseId);
|
|
161
|
+
return { success: true, plan };
|
|
162
|
+
}
|
|
163
|
+
rejectResponse(options) {
|
|
164
|
+
const plan = this.stagedPlans.get(options.responseId);
|
|
165
|
+
if (!plan) {
|
|
166
|
+
return { success: false, reason: 'UNKNOWN_APPROVAL_ID' };
|
|
167
|
+
}
|
|
168
|
+
if (plan.companionId !== options.companionId) {
|
|
169
|
+
return { success: false, reason: 'COMPANION_MISMATCH' };
|
|
170
|
+
}
|
|
171
|
+
if (plan.correlationId !== options.correlationId) {
|
|
172
|
+
return { success: false, reason: 'APPROVAL_ID_MISMATCH' };
|
|
173
|
+
}
|
|
174
|
+
plan.status = 'REJECTED';
|
|
175
|
+
return { success: true, plan };
|
|
176
|
+
}
|
|
177
|
+
getStagedPlan(responseId) {
|
|
178
|
+
return this.stagedPlans.get(responseId);
|
|
179
|
+
}
|
|
180
|
+
findStagedPlanByCorrelation(companionId, correlationId) {
|
|
181
|
+
for (const plan of this.stagedPlans.values()) {
|
|
182
|
+
if (plan.companionId === companionId && plan.correlationId === correlationId) {
|
|
183
|
+
return plan;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.ResponseGatingEngine = ResponseGatingEngine;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const gating_1 = require("./gating");
|
|
4
|
+
describe('T4 Response Gating & Staged Approval Suite', () => {
|
|
5
|
+
let engine;
|
|
6
|
+
const validPublicContext = {
|
|
7
|
+
companionId: 'companion-a',
|
|
8
|
+
actor: {
|
|
9
|
+
actorId: 'actor-a',
|
|
10
|
+
sessionId: 'session-a',
|
|
11
|
+
authorizationRole: 'viewer',
|
|
12
|
+
capabilities: ['chat:public'],
|
|
13
|
+
authenticated: false,
|
|
14
|
+
},
|
|
15
|
+
conversation: {
|
|
16
|
+
channel: 'public',
|
|
17
|
+
audienceId: 'audience-public',
|
|
18
|
+
correlationId: 'corr-gate-1',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
engine = new gating_1.ResponseGatingEngine();
|
|
23
|
+
});
|
|
24
|
+
test('valid grounded response without special approval requirement is approved/admissible directly', () => {
|
|
25
|
+
const evidence = {
|
|
26
|
+
evidenceId: 'ev-pub-1',
|
|
27
|
+
sourceId: 'src-wiki',
|
|
28
|
+
origin: 'knowledge',
|
|
29
|
+
trust: 'configured',
|
|
30
|
+
sensitivity: 'public',
|
|
31
|
+
allowedAudiences: ['audience-public'],
|
|
32
|
+
companionId: 'companion-a',
|
|
33
|
+
correlationId: 'corr-gate-1',
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
const staged = engine.stageResponse({
|
|
37
|
+
requestContext: validPublicContext,
|
|
38
|
+
candidateSpeech: 'Here is grounded public knowledge.',
|
|
39
|
+
candidateLanguage: 'en',
|
|
40
|
+
evidenceRecords: [evidence],
|
|
41
|
+
citations: [{ sourceId: 'src-wiki', revision: 'r1' }],
|
|
42
|
+
});
|
|
43
|
+
expect(staged.requiresApproval).toBe(false);
|
|
44
|
+
expect(staged.status).toBe('STAGED');
|
|
45
|
+
const evaluation = engine.evaluateGate(staged, [evidence]);
|
|
46
|
+
expect(evaluation.admissible).toBe(true);
|
|
47
|
+
expect(evaluation.disposition).toBe('APPROVED');
|
|
48
|
+
expect(evaluation.reasonCode).toBe('APPROVED_DIRECT');
|
|
49
|
+
expect(evaluation.filteredEvidenceIds).toEqual(['ev-pub-1']);
|
|
50
|
+
expect(evaluation.filteredCitations.length).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
test('response containing untrusted OCR evidence requires approval and remains staged until approved', () => {
|
|
53
|
+
const ocrEvidence = {
|
|
54
|
+
evidenceId: 'ev-ocr-1',
|
|
55
|
+
sourceId: 'vision-ocr',
|
|
56
|
+
origin: 'ocr',
|
|
57
|
+
trust: 'untrusted',
|
|
58
|
+
sensitivity: 'public',
|
|
59
|
+
allowedAudiences: ['audience-public'],
|
|
60
|
+
companionId: 'companion-a',
|
|
61
|
+
correlationId: 'corr-gate-2',
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
uncertainty: 'OCR detected prompt injection text',
|
|
64
|
+
};
|
|
65
|
+
const staged = engine.stageResponse({
|
|
66
|
+
requestContext: {
|
|
67
|
+
...validPublicContext,
|
|
68
|
+
conversation: { ...validPublicContext.conversation, correlationId: 'corr-gate-2' },
|
|
69
|
+
},
|
|
70
|
+
candidateSpeech: 'Text extracted from image.',
|
|
71
|
+
candidateLanguage: 'en',
|
|
72
|
+
evidenceRecords: [ocrEvidence],
|
|
73
|
+
});
|
|
74
|
+
expect(staged.requiresApproval).toBe(true);
|
|
75
|
+
expect(staged.status).toBe('STAGED');
|
|
76
|
+
// Before approval -> gating holds it
|
|
77
|
+
const beforeApproval = engine.evaluateGate(staged, [ocrEvidence]);
|
|
78
|
+
expect(beforeApproval.admissible).toBe(false);
|
|
79
|
+
expect(beforeApproval.disposition).toBe('STAGED');
|
|
80
|
+
expect(beforeApproval.reasonCode).toBe('APPROVAL_REQUIRED');
|
|
81
|
+
// Operator approves the response
|
|
82
|
+
const approveResult = engine.approveResponse({
|
|
83
|
+
responseId: staged.responseId,
|
|
84
|
+
companionId: 'companion-a',
|
|
85
|
+
correlationId: 'corr-gate-2',
|
|
86
|
+
});
|
|
87
|
+
expect(approveResult.success).toBe(true);
|
|
88
|
+
// After approval -> gating allows it
|
|
89
|
+
const afterApproval = engine.evaluateGate(staged, [ocrEvidence]);
|
|
90
|
+
expect(afterApproval.admissible).toBe(true);
|
|
91
|
+
expect(afterApproval.disposition).toBe('APPROVED');
|
|
92
|
+
expect(afterApproval.reasonCode).toBe('APPROVED_DIRECT');
|
|
93
|
+
});
|
|
94
|
+
test('unknown or mismatched approval ID is rejected', () => {
|
|
95
|
+
const staged = engine.stageResponse({
|
|
96
|
+
requestContext: validPublicContext,
|
|
97
|
+
candidateSpeech: 'Requires approval',
|
|
98
|
+
candidateLanguage: 'en',
|
|
99
|
+
requiresApproval: true,
|
|
100
|
+
});
|
|
101
|
+
// Unknown response ID
|
|
102
|
+
const unknownRes = engine.approveResponse({
|
|
103
|
+
responseId: 'non-existent-resp',
|
|
104
|
+
companionId: 'companion-a',
|
|
105
|
+
correlationId: 'corr-gate-1',
|
|
106
|
+
});
|
|
107
|
+
expect(unknownRes.success).toBe(false);
|
|
108
|
+
expect(unknownRes.reason).toBe('UNKNOWN_APPROVAL_ID');
|
|
109
|
+
// Companion mismatch
|
|
110
|
+
const companionMismatch = engine.approveResponse({
|
|
111
|
+
responseId: staged.responseId,
|
|
112
|
+
companionId: 'companion-other',
|
|
113
|
+
correlationId: 'corr-gate-1',
|
|
114
|
+
});
|
|
115
|
+
expect(companionMismatch.success).toBe(false);
|
|
116
|
+
expect(companionMismatch.reason).toBe('COMPANION_MISMATCH');
|
|
117
|
+
// Correlation ID mismatch
|
|
118
|
+
const correlationMismatch = engine.approveResponse({
|
|
119
|
+
responseId: staged.responseId,
|
|
120
|
+
companionId: 'companion-a',
|
|
121
|
+
correlationId: 'corr-mismatch',
|
|
122
|
+
});
|
|
123
|
+
expect(correlationMismatch.success).toBe(false);
|
|
124
|
+
expect(correlationMismatch.reason).toBe('APPROVAL_ID_MISMATCH');
|
|
125
|
+
});
|
|
126
|
+
test('rejection prevents response from ever reaching emission', () => {
|
|
127
|
+
const staged = engine.stageResponse({
|
|
128
|
+
requestContext: validPublicContext,
|
|
129
|
+
candidateSpeech: 'Potentially unsafe response',
|
|
130
|
+
candidateLanguage: 'en',
|
|
131
|
+
requiresApproval: true,
|
|
132
|
+
});
|
|
133
|
+
const rejectRes = engine.rejectResponse({
|
|
134
|
+
responseId: staged.responseId,
|
|
135
|
+
companionId: 'companion-a',
|
|
136
|
+
correlationId: 'corr-gate-1',
|
|
137
|
+
});
|
|
138
|
+
expect(rejectRes.success).toBe(true);
|
|
139
|
+
const evalRes = engine.evaluateGate(staged);
|
|
140
|
+
expect(evalRes.admissible).toBe(false);
|
|
141
|
+
expect(evalRes.disposition).toBe('REJECTED');
|
|
142
|
+
expect(evalRes.reasonCode).toBe('EXPLICITLY_REJECTED');
|
|
143
|
+
});
|
|
144
|
+
test('expired staged response plan cannot be approved or emitted', () => {
|
|
145
|
+
const staged = engine.stageResponse({
|
|
146
|
+
requestContext: validPublicContext,
|
|
147
|
+
candidateSpeech: 'Expired response',
|
|
148
|
+
candidateLanguage: 'en',
|
|
149
|
+
requiresApproval: true,
|
|
150
|
+
ttlMs: 100,
|
|
151
|
+
now: new Date(Date.now() - 500),
|
|
152
|
+
});
|
|
153
|
+
const evalRes = engine.evaluateGate(staged, [], new Date());
|
|
154
|
+
expect(evalRes.admissible).toBe(false);
|
|
155
|
+
expect(evalRes.disposition).toBe('EXPIRED');
|
|
156
|
+
expect(evalRes.reasonCode).toBe('EVIDENCE_EXPIRED');
|
|
157
|
+
const approveRes = engine.approveResponse({
|
|
158
|
+
responseId: staged.responseId,
|
|
159
|
+
companionId: 'companion-a',
|
|
160
|
+
correlationId: 'corr-gate-1',
|
|
161
|
+
});
|
|
162
|
+
expect(approveRes.success).toBe(false);
|
|
163
|
+
expect(approveRes.reason).toBe('EVIDENCE_EXPIRED');
|
|
164
|
+
});
|
|
165
|
+
test('private evidence is filtered out and absent from public citations/evidence list', () => {
|
|
166
|
+
const privateEvidence = {
|
|
167
|
+
evidenceId: 'ev-priv-1',
|
|
168
|
+
sourceId: 'src-secret',
|
|
169
|
+
origin: 'knowledge',
|
|
170
|
+
trust: 'configured',
|
|
171
|
+
sensitivity: 'private',
|
|
172
|
+
allowedAudiences: ['audience-direct-a'],
|
|
173
|
+
companionId: 'companion-a',
|
|
174
|
+
correlationId: 'corr-gate-1',
|
|
175
|
+
createdAt: new Date().toISOString(),
|
|
176
|
+
};
|
|
177
|
+
const staged = engine.stageResponse({
|
|
178
|
+
requestContext: validPublicContext, // public channel
|
|
179
|
+
candidateSpeech: 'Public response text',
|
|
180
|
+
candidateLanguage: 'en',
|
|
181
|
+
evidenceRecords: [privateEvidence],
|
|
182
|
+
citations: [{ sourceId: 'src-secret' }],
|
|
183
|
+
});
|
|
184
|
+
const evalRes = engine.evaluateGate(staged, [privateEvidence]);
|
|
185
|
+
expect(evalRes.admissible).toBe(true);
|
|
186
|
+
// Private evidence and its citation must be stripped from public output metadata
|
|
187
|
+
expect(evalRes.filteredEvidenceIds).toEqual([]);
|
|
188
|
+
expect(evalRes.filteredCitations).toEqual([]);
|
|
189
|
+
});
|
|
190
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export * from './context';
|
|
2
|
+
export * from './evidence';
|
|
3
|
+
export * from './gating';
|
|
4
|
+
export * from './experience';
|
|
5
|
+
export * from './dispatcher';
|
|
6
|
+
export * from './action';
|
|
7
|
+
export * from './action-policy';
|
|
8
|
+
export * from './ear-types';
|
|
9
|
+
export * from './capability';
|
|
10
|
+
export * from './teaching';
|
|
11
|
+
export * from './runtime';
|
|
12
|
+
import { ActionIntent } from './action';
|
|
13
|
+
import { EarIngestOptions } from './ear-types';
|
|
14
|
+
export interface OrganConfig {
|
|
15
|
+
provider: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
export interface CompanionConfig {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
brain: OrganConfig;
|
|
22
|
+
voice: OrganConfig;
|
|
23
|
+
memory: OrganConfig;
|
|
24
|
+
knowledge: OrganConfig;
|
|
25
|
+
behavior: OrganConfig;
|
|
26
|
+
body: OrganConfig;
|
|
27
|
+
vision: OrganConfig;
|
|
28
|
+
}
|
|
29
|
+
export interface Message {
|
|
30
|
+
role: 'user' | 'assistant' | 'system';
|
|
31
|
+
content: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface BrainContext {
|
|
35
|
+
systemPrompt: string;
|
|
36
|
+
contextPrompt: string;
|
|
37
|
+
recentMessages: Message[];
|
|
38
|
+
recipient?: MemoryScope;
|
|
39
|
+
}
|
|
40
|
+
export type ClaimType = 'semantic' | 'preference' | 'episodic' | 'relationship';
|
|
41
|
+
export type ClaimAuthority = 'user_explicit' | 'user_correction' | 'import' | 'repeated_dialogue' | 'inference' | 'observation';
|
|
42
|
+
export type ClaimStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'SESSION_ONLY' | 'EXPIRED' | 'SUPERSEDED' | 'REVOKED';
|
|
43
|
+
export interface SourceEvent {
|
|
44
|
+
id: string;
|
|
45
|
+
sourceType: string;
|
|
46
|
+
occurredAt: string;
|
|
47
|
+
payload: Record<string, unknown>;
|
|
48
|
+
schemaVersion?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface MemoryProposal {
|
|
51
|
+
subject: string;
|
|
52
|
+
predicate: string;
|
|
53
|
+
value: string;
|
|
54
|
+
content?: string;
|
|
55
|
+
provenance?: string;
|
|
56
|
+
claimType?: ClaimType;
|
|
57
|
+
sensitivity?: string;
|
|
58
|
+
allowedAudiences?: string[];
|
|
59
|
+
sourceEventId?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface BehaviorProposal {
|
|
62
|
+
directive: string;
|
|
63
|
+
priority: number;
|
|
64
|
+
subject?: string;
|
|
65
|
+
predicate?: string;
|
|
66
|
+
value?: string;
|
|
67
|
+
memoryClass?: 'identity' | 'relationship' | 'behavioral' | 'semantic' | 'episodic';
|
|
68
|
+
sourceEventId?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface ResponsePlan {
|
|
71
|
+
speech: string;
|
|
72
|
+
language: string;
|
|
73
|
+
memoryProposals?: MemoryProposal[];
|
|
74
|
+
behaviorProposals?: BehaviorProposal[];
|
|
75
|
+
actionIntents?: ActionIntent[];
|
|
76
|
+
internalMonologue?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface BrainOrgan {
|
|
79
|
+
generatePlan(context: BrainContext): Promise<ResponsePlan>;
|
|
80
|
+
}
|
|
81
|
+
export type MemoryScope = 'OWNER' | 'VIEWER' | 'OPERATOR' | 'PUBLIC';
|
|
82
|
+
export interface Claim {
|
|
83
|
+
id: string;
|
|
84
|
+
subject: string;
|
|
85
|
+
predicate: string;
|
|
86
|
+
value: string;
|
|
87
|
+
status: ClaimStatus;
|
|
88
|
+
evidence?: string[];
|
|
89
|
+
scope: MemoryScope;
|
|
90
|
+
companionId: string;
|
|
91
|
+
provenance?: string;
|
|
92
|
+
sourceEventId?: string;
|
|
93
|
+
claimType?: ClaimType;
|
|
94
|
+
authority?: ClaimAuthority;
|
|
95
|
+
userConfirmation?: 'explicit' | 'implied' | 'none';
|
|
96
|
+
sensitivity?: string;
|
|
97
|
+
allowedAudiences?: string[];
|
|
98
|
+
confidence?: number;
|
|
99
|
+
assertedAt?: string;
|
|
100
|
+
validFrom?: string;
|
|
101
|
+
validUntil?: string;
|
|
102
|
+
supersedes?: string;
|
|
103
|
+
replaces?: string;
|
|
104
|
+
}
|
|
105
|
+
export interface BehaviorDirective {
|
|
106
|
+
id: string;
|
|
107
|
+
companionId: string;
|
|
108
|
+
directive: string;
|
|
109
|
+
scopeMatcher: string[];
|
|
110
|
+
priority: number;
|
|
111
|
+
status: 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
|
|
112
|
+
supersedesId?: string;
|
|
113
|
+
memoryClass?: 'identity' | 'relationship' | 'behavioral';
|
|
114
|
+
subject?: string;
|
|
115
|
+
predicate?: string;
|
|
116
|
+
value?: string;
|
|
117
|
+
allowedAudiences?: string[];
|
|
118
|
+
validFrom?: string;
|
|
119
|
+
validUntil?: string;
|
|
120
|
+
}
|
|
121
|
+
export interface MemoryQueryOptions {
|
|
122
|
+
channel?: 'public' | 'direct' | 'private' | 'operator';
|
|
123
|
+
audienceId?: string;
|
|
124
|
+
sensitivity?: string;
|
|
125
|
+
limit?: number;
|
|
126
|
+
}
|
|
127
|
+
export interface MemoryOrgan {
|
|
128
|
+
initialize(companionId: string): Promise<void>;
|
|
129
|
+
proposeClaim(claim: Omit<Claim, 'id' | 'status' | 'companionId'>): Promise<Claim>;
|
|
130
|
+
searchClaims(query: string, scopeOrOptions: MemoryScope | MemoryQueryOptions, limit?: number): Promise<Claim[]>;
|
|
131
|
+
getClaims(limit?: number): Promise<Claim[]>;
|
|
132
|
+
getPendingClaims(limit?: number): Promise<Claim[]>;
|
|
133
|
+
approveClaim(id: string): Promise<void>;
|
|
134
|
+
rejectClaim(id: string): Promise<void>;
|
|
135
|
+
markClaimSessionOnly?(id: string): Promise<void>;
|
|
136
|
+
expireClaim?(id: string): Promise<void>;
|
|
137
|
+
revokeClaim?(id: string, reason?: string): Promise<void>;
|
|
138
|
+
getDirectives(): Promise<BehaviorDirective[]>;
|
|
139
|
+
proposeDirective(directiveData: Omit<BehaviorDirective, 'id' | 'status' | 'companionId'>): Promise<BehaviorDirective>;
|
|
140
|
+
approveDirective(id: string): Promise<void>;
|
|
141
|
+
rejectDirective(id: string): Promise<void>;
|
|
142
|
+
revokeDirective(id: string): Promise<void>;
|
|
143
|
+
disableDirective(id: string): Promise<void>;
|
|
144
|
+
expireDirective?(id: string): Promise<void>;
|
|
145
|
+
supersedeClaim?(id: string, replacement: Omit<Claim, 'id' | 'status' | 'companionId'>): Promise<Claim>;
|
|
146
|
+
addSourceEvent?(event: SourceEvent): Promise<SourceEvent>;
|
|
147
|
+
getSourceEvent?(id: string): Promise<SourceEvent | undefined>;
|
|
148
|
+
}
|
|
149
|
+
export interface AudioEvent {
|
|
150
|
+
type: 'STARTED' | 'COMPLETED' | 'FAILED';
|
|
151
|
+
speechId: string;
|
|
152
|
+
text?: string;
|
|
153
|
+
language?: string;
|
|
154
|
+
audioBuffer?: Uint8Array;
|
|
155
|
+
}
|
|
156
|
+
export interface VoiceOrgan {
|
|
157
|
+
enqueueSpeech(text: string, language: string, priority?: number): string;
|
|
158
|
+
onLifecycleEvent(callback: (event: AudioEvent) => void): void;
|
|
159
|
+
getQueueStatus(): {
|
|
160
|
+
pending: number;
|
|
161
|
+
current?: string;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export interface VisionOrgan {
|
|
165
|
+
analyze(imageUrl: string, prompt: string): Promise<string>;
|
|
166
|
+
}
|
|
167
|
+
export interface ActiveSelfProjection {
|
|
168
|
+
identityFacts: string[];
|
|
169
|
+
relationshipFacts: string[];
|
|
170
|
+
behavioralRules: string[];
|
|
171
|
+
activeIds: string[];
|
|
172
|
+
excludedIds: string[];
|
|
173
|
+
diagnostics: Record<string, string>;
|
|
174
|
+
render(): string;
|
|
175
|
+
}
|
|
176
|
+
export interface BehaviorContext {
|
|
177
|
+
activeRole: string;
|
|
178
|
+
directives: BehaviorDirective[];
|
|
179
|
+
companionId?: string;
|
|
180
|
+
channel?: 'public' | 'direct' | 'private' | 'operator';
|
|
181
|
+
audienceId?: string;
|
|
182
|
+
actorId?: string;
|
|
183
|
+
sessionId?: string;
|
|
184
|
+
now?: string;
|
|
185
|
+
}
|
|
186
|
+
export interface BehaviorOrgan {
|
|
187
|
+
compile(context: BehaviorContext): Promise<string>;
|
|
188
|
+
compileProjection?(context: BehaviorContext): Promise<ActiveSelfProjection>;
|
|
189
|
+
}
|
|
190
|
+
export interface KnowledgeItem {
|
|
191
|
+
content: string;
|
|
192
|
+
provenance: string;
|
|
193
|
+
revision: string;
|
|
194
|
+
citations: KnowledgeCitation[];
|
|
195
|
+
}
|
|
196
|
+
export interface KnowledgeCitation {
|
|
197
|
+
sourceId: string;
|
|
198
|
+
documentId?: string;
|
|
199
|
+
chunkId?: string;
|
|
200
|
+
locator?: string;
|
|
201
|
+
}
|
|
202
|
+
export interface KnowledgeOrgan {
|
|
203
|
+
search(query: string): Promise<KnowledgeItem[]>;
|
|
204
|
+
}
|
|
205
|
+
export interface EarPerception {
|
|
206
|
+
id: string;
|
|
207
|
+
source: string;
|
|
208
|
+
text?: string;
|
|
209
|
+
audioBuffer?: Uint8Array;
|
|
210
|
+
metadata?: Record<string, unknown>;
|
|
211
|
+
timestamp: string;
|
|
212
|
+
}
|
|
213
|
+
export interface EarOrgan {
|
|
214
|
+
listen(source: string, payload: unknown, options?: EarIngestOptions): Promise<EarPerception>;
|
|
215
|
+
transcribeAudio?(audio: Uint8Array): Promise<string>;
|
|
216
|
+
}
|
|
217
|
+
export interface BodyOrgan {
|
|
218
|
+
setExpression(expression: string): void;
|
|
219
|
+
speak(speechId: string, text?: string, language?: string): void;
|
|
220
|
+
act(action: string): void;
|
|
221
|
+
completeAction?(): void;
|
|
222
|
+
}
|
|
223
|
+
export interface ObservationReading {
|
|
224
|
+
entity: string;
|
|
225
|
+
value: string;
|
|
226
|
+
confidence: number;
|
|
227
|
+
sourceCrop?: string;
|
|
228
|
+
ocrText?: string;
|
|
229
|
+
competingInterpretations?: string[];
|
|
230
|
+
}
|
|
231
|
+
export interface Observation {
|
|
232
|
+
observationId: string;
|
|
233
|
+
evidenceId: string;
|
|
234
|
+
sourceName: string;
|
|
235
|
+
providerId: string;
|
|
236
|
+
readings: ObservationReading[];
|
|
237
|
+
confidence: number;
|
|
238
|
+
createdAt: string;
|
|
239
|
+
expiresAt: string;
|
|
240
|
+
frameDigest: string;
|
|
241
|
+
}
|
|
242
|
+
export interface ObservationResult {
|
|
243
|
+
observation?: Observation;
|
|
244
|
+
duplicate: boolean;
|
|
245
|
+
reason?: 'empty_frame' | 'duplicate_frame' | 'invalid_reading' | 'provider_failure';
|
|
246
|
+
}
|
|
247
|
+
export interface ObservationOrgan {
|
|
248
|
+
ingest(frame: Uint8Array, sourceName: string, providerId?: string): Promise<ObservationResult>;
|
|
249
|
+
current(now?: Date): Observation[];
|
|
250
|
+
clearExpired(now?: Date): number;
|
|
251
|
+
}
|
|
252
|
+
export interface HealthProbeContext {
|
|
253
|
+
config: unknown;
|
|
254
|
+
env: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
255
|
+
}
|
|
256
|
+
export interface HealthProbeResult {
|
|
257
|
+
ok: boolean;
|
|
258
|
+
message?: string;
|
|
259
|
+
details?: Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Export neutral context types and validator
|
|
18
|
+
__exportStar(require("./context"), exports);
|
|
19
|
+
__exportStar(require("./evidence"), exports);
|
|
20
|
+
__exportStar(require("./gating"), exports);
|
|
21
|
+
__exportStar(require("./experience"), exports);
|
|
22
|
+
__exportStar(require("./dispatcher"), exports);
|
|
23
|
+
__exportStar(require("./action"), exports);
|
|
24
|
+
__exportStar(require("./action-policy"), exports);
|
|
25
|
+
__exportStar(require("./ear-types"), exports);
|
|
26
|
+
__exportStar(require("./capability"), exports);
|
|
27
|
+
__exportStar(require("./teaching"), exports);
|
|
28
|
+
__exportStar(require("./runtime"), exports);
|