@siduri-x/core 1.0.4 → 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.
- package/dist/action-executor.d.ts +12 -0
- package/dist/action-executor.js +50 -0
- package/dist/action-policy.js +4 -9
- package/dist/architecture-boundary.test.js +1 -0
- package/dist/capability.d.ts +8 -0
- package/dist/capability.js +31 -4
- package/dist/chat-contract.d.ts +5 -1
- package/dist/chat-contract.js +26 -23
- package/dist/cognition-planner.d.ts +15 -0
- package/dist/cognition-planner.js +23 -0
- package/dist/context-retriever.d.ts +24 -0
- package/dist/context-retriever.js +92 -0
- package/dist/context.d.ts +11 -13
- package/dist/context.js +3 -23
- package/dist/context.test.js +7 -40
- package/dist/evidence.d.ts +12 -8
- package/dist/evidence.js +6 -4
- package/dist/experience-emitter.d.ts +21 -0
- package/dist/experience-emitter.js +48 -0
- package/dist/experience.d.ts +6 -5
- package/dist/experience.js +3 -2
- package/dist/experience.test.js +5 -4
- package/dist/gating.d.ts +3 -2
- package/dist/gating.js +4 -8
- package/dist/index.d.ts +27 -43
- package/dist/index.js +14 -0
- package/dist/input-normalizer.d.ts +14 -0
- package/dist/input-normalizer.js +58 -0
- package/dist/input-normalizer.test.d.ts +1 -0
- package/dist/input-normalizer.test.js +39 -0
- package/dist/intent-classifier.d.ts +24 -0
- package/dist/intent-classifier.js +53 -0
- package/dist/intent-classifier.test.d.ts +1 -0
- package/dist/intent-classifier.test.js +68 -0
- package/dist/memory-settler.d.ts +27 -0
- package/dist/memory-settler.js +95 -0
- package/dist/mouth-types.d.ts +85 -0
- package/dist/mouth-types.js +2 -0
- package/dist/perception-cycle.test.d.ts +1 -0
- package/dist/perception-cycle.test.js +155 -0
- package/dist/prompt-compiler.d.ts +20 -0
- package/dist/prompt-compiler.js +57 -0
- package/dist/prompt-compiler.test.d.ts +1 -0
- package/dist/prompt-compiler.test.js +76 -0
- package/dist/proposals.d.ts +30 -0
- package/dist/proposals.js +2 -0
- package/dist/response-envelope.d.ts +25 -0
- package/dist/response-envelope.js +64 -0
- package/dist/runtime-facades.test.d.ts +1 -0
- package/dist/runtime-facades.test.js +69 -0
- package/dist/runtime.d.ts +79 -15
- package/dist/runtime.js +327 -326
- package/dist/session-history.d.ts +20 -0
- package/dist/session-history.js +55 -0
- package/dist/session-history.test.d.ts +1 -0
- package/dist/session-history.test.js +38 -0
- package/dist/sqlite-action-store.d.ts +20 -0
- package/dist/sqlite-action-store.js +225 -0
- package/dist/sqlite-action-store.test.d.ts +1 -0
- package/dist/sqlite-action-store.test.js +252 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ActionIntent, ActionExecutionResult, ActionPolicyEngine, HandsOrgan, RequestContext } from './index';
|
|
2
|
+
export interface ActionExecutionParams {
|
|
3
|
+
actionIntents?: ActionIntent[];
|
|
4
|
+
requestContext: RequestContext;
|
|
5
|
+
actionPolicy: ActionPolicyEngine;
|
|
6
|
+
hands?: HandsOrgan;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Executes proposed actions under the primary security invariant:
|
|
10
|
+
* "Brain proposes; the policy layer authorizes; Hands executes; the audit layer records."
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeActionIntents(params: ActionExecutionParams): Promise<ActionExecutionResult[]>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeActionIntents = executeActionIntents;
|
|
4
|
+
/**
|
|
5
|
+
* Executes proposed actions under the primary security invariant:
|
|
6
|
+
* "Brain proposes; the policy layer authorizes; Hands executes; the audit layer records."
|
|
7
|
+
*/
|
|
8
|
+
async function executeActionIntents(params) {
|
|
9
|
+
const { actionIntents, requestContext, actionPolicy, hands } = params;
|
|
10
|
+
const actionResults = [];
|
|
11
|
+
if (!hands ||
|
|
12
|
+
!actionIntents ||
|
|
13
|
+
actionIntents.length === 0 ||
|
|
14
|
+
typeof hands.executeAction !== 'function') {
|
|
15
|
+
return actionResults;
|
|
16
|
+
}
|
|
17
|
+
for (const rawAction of actionIntents) {
|
|
18
|
+
// 1. Context Propagation: Attach request provenance to ActionIntent
|
|
19
|
+
const actionWithContext = {
|
|
20
|
+
...rawAction,
|
|
21
|
+
context: requestContext,
|
|
22
|
+
executionId: rawAction.executionId ||
|
|
23
|
+
`exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
24
|
+
};
|
|
25
|
+
// 2. Action Policy Authorization Boundary Check
|
|
26
|
+
const { decision, capability } = await actionPolicy.evaluateAction(actionWithContext, requestContext);
|
|
27
|
+
if (!decision.allowed || !capability) {
|
|
28
|
+
// Action was rejected by policy
|
|
29
|
+
actionResults.push({
|
|
30
|
+
actionId: actionWithContext.actionId,
|
|
31
|
+
executionId: decision.executionId,
|
|
32
|
+
toolName: actionWithContext.toolName,
|
|
33
|
+
lifecycle: 'REJECTED',
|
|
34
|
+
success: false,
|
|
35
|
+
error: `Action authorization rejected by policy: ${decision.reason}`,
|
|
36
|
+
decision,
|
|
37
|
+
});
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// 3. Hands Execution (only authorized actions execute with AuthorizationCapability)
|
|
41
|
+
const res = await hands.executeAction(actionWithContext, capability);
|
|
42
|
+
actionResults.push({
|
|
43
|
+
...res,
|
|
44
|
+
decision,
|
|
45
|
+
});
|
|
46
|
+
// 4. Audit recording for execution outcome
|
|
47
|
+
await actionPolicy.recordAudit(actionWithContext, requestContext, decision, res.lifecycle, res.result, res.error, res.durationMs);
|
|
48
|
+
}
|
|
49
|
+
return actionResults;
|
|
50
|
+
}
|
package/dist/action-policy.js
CHANGED
|
@@ -15,12 +15,7 @@ class ActionPolicyEngine {
|
|
|
15
15
|
this.defaultRiskLevel = options.defaultRiskLevel ?? 'HIGH';
|
|
16
16
|
this.defaultRequireApprovalForHighRisk = options.defaultRequireApprovalForHighRisk ?? true;
|
|
17
17
|
this.store = options.store ?? new capability_1.InMemoryActionStore();
|
|
18
|
-
|
|
19
|
-
const providedSecret = options.secretKey || envSecret;
|
|
20
|
-
if (!providedSecret && typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
21
|
-
throw new Error('FATAL: ACTION_POLICY_SECRET is required in production environment');
|
|
22
|
-
}
|
|
23
|
-
this.secretKey = providedSecret ?? 'siduri_y_action_policy_secret';
|
|
18
|
+
this.secretKey = (0, capability_1.getOrGenerateLocalActionPolicySecret)(options.secretKey);
|
|
24
19
|
}
|
|
25
20
|
registerToolDefinition(tool) {
|
|
26
21
|
const key = tool.providerId ? `${tool.providerId}/${tool.name}` : tool.name;
|
|
@@ -75,9 +70,9 @@ class ActionPolicyEngine {
|
|
|
75
70
|
}
|
|
76
71
|
// Role check if tool restricts roles (supports administrator/owner role parity)
|
|
77
72
|
if (toolDef.allowedRoles && toolDef.allowedRoles.length > 0) {
|
|
78
|
-
const actorRole = effectiveContext.actor.authorizationRole;
|
|
73
|
+
const actorRole = effectiveContext.actor.authorizationRole || effectiveContext.actor.role || 'owner';
|
|
79
74
|
const normalizedActorRoles = new Set([actorRole.toLowerCase()]);
|
|
80
|
-
if (actorRole === 'administrator' || actorRole === 'owner') {
|
|
75
|
+
if (actorRole.toLowerCase() === 'administrator' || actorRole.toLowerCase() === 'owner') {
|
|
81
76
|
normalizedActorRoles.add('administrator');
|
|
82
77
|
normalizedActorRoles.add('owner');
|
|
83
78
|
normalizedActorRoles.add('admin');
|
|
@@ -98,7 +93,7 @@ class ActionPolicyEngine {
|
|
|
98
93
|
}
|
|
99
94
|
// Channel check if tool restricts channels
|
|
100
95
|
if (toolDef.allowedChannels && toolDef.allowedChannels.length > 0) {
|
|
101
|
-
const channel = effectiveContext.conversation.channel;
|
|
96
|
+
const channel = effectiveContext.conversation.channel || 'direct';
|
|
102
97
|
if (!toolDef.allowedChannels.includes(channel)) {
|
|
103
98
|
const decision = {
|
|
104
99
|
allowed: false,
|
|
@@ -50,6 +50,7 @@ describe('Architecture: Core & Organ Package Boundaries (Phase 2)', () => {
|
|
|
50
50
|
{ dir: 'body', name: '@siduri-x/body', organType: 'body', configKey: 'body' },
|
|
51
51
|
{ dir: 'voice', name: '@siduri-x/voice', organType: 'voice', configKey: 'voice' },
|
|
52
52
|
{ dir: 'observation', name: '@siduri-x/observation', organType: 'observation', configKey: 'observation' },
|
|
53
|
+
{ dir: 'mouth', name: '@siduri-x/mouth', organType: 'mouth', configKey: 'mouth' },
|
|
53
54
|
];
|
|
54
55
|
it('package.json has zero dependencies on @siduri-x organ packages', () => {
|
|
55
56
|
const pkg = JSON.parse(fs.readFileSync(corePackageJsonPath, 'utf8'));
|
package/dist/capability.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the action policy secret key.
|
|
3
|
+
* In production (NODE_ENV=production), ACTION_POLICY_SECRET is required.
|
|
4
|
+
* In local/development environments, if no secret is provided via environment
|
|
5
|
+
* or options, generates an ephemeral cryptographically strong secret per-process,
|
|
6
|
+
* avoiding shared hardcoded fallback secrets.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getOrGenerateLocalActionPolicySecret(provided?: string): string;
|
|
1
9
|
import { ActionRiskLevel, ActionLifecycleState, ActionAuditEvent, ActionPolicyDecision } from './action';
|
|
2
10
|
export interface AuthorizationCapability {
|
|
3
11
|
executionId: string;
|
package/dist/capability.js
CHANGED
|
@@ -3,12 +3,37 @@
|
|
|
3
3
|
// using node's built-in crypto module dynamically or via require for universal CommonJS compatibility
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.InMemoryActionStore = void 0;
|
|
6
|
+
exports.getOrGenerateLocalActionPolicySecret = getOrGenerateLocalActionPolicySecret;
|
|
6
7
|
exports.canonicalizeJson = canonicalizeJson;
|
|
7
8
|
exports.computeParametersHash = computeParametersHash;
|
|
8
9
|
exports.signCapabilityPayload = signCapabilityPayload;
|
|
9
10
|
exports.verifyCapabilitySignature = verifyCapabilitySignature;
|
|
10
11
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
12
|
const crypto = require('crypto');
|
|
13
|
+
let cachedEphemeralSecret = null;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the action policy secret key.
|
|
16
|
+
* In production (NODE_ENV=production), ACTION_POLICY_SECRET is required.
|
|
17
|
+
* In local/development environments, if no secret is provided via environment
|
|
18
|
+
* or options, generates an ephemeral cryptographically strong secret per-process,
|
|
19
|
+
* avoiding shared hardcoded fallback secrets.
|
|
20
|
+
*/
|
|
21
|
+
function getOrGenerateLocalActionPolicySecret(provided) {
|
|
22
|
+
if (provided) {
|
|
23
|
+
return provided;
|
|
24
|
+
}
|
|
25
|
+
const envSecret = typeof process !== 'undefined' && process.env ? process.env.ACTION_POLICY_SECRET : undefined;
|
|
26
|
+
if (envSecret) {
|
|
27
|
+
return envSecret;
|
|
28
|
+
}
|
|
29
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
30
|
+
throw new Error('FATAL: ACTION_POLICY_SECRET is required in production environment');
|
|
31
|
+
}
|
|
32
|
+
if (!cachedEphemeralSecret) {
|
|
33
|
+
cachedEphemeralSecret = crypto.randomBytes(32).toString('hex');
|
|
34
|
+
}
|
|
35
|
+
return cachedEphemeralSecret;
|
|
36
|
+
}
|
|
12
37
|
class InMemoryActionStore {
|
|
13
38
|
executions = new Map();
|
|
14
39
|
approvals = new Map();
|
|
@@ -100,17 +125,19 @@ function computeParametersHash(params) {
|
|
|
100
125
|
const canonical = canonicalizeJson(params || {});
|
|
101
126
|
return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex');
|
|
102
127
|
}
|
|
103
|
-
function signCapabilityPayload(payload, secretKey
|
|
128
|
+
function signCapabilityPayload(payload, secretKey) {
|
|
129
|
+
const resolvedKey = getOrGenerateLocalActionPolicySecret(secretKey);
|
|
104
130
|
const canonicalStr = canonicalizeJson(payload);
|
|
105
|
-
return crypto.createHmac('sha256',
|
|
131
|
+
return crypto.createHmac('sha256', resolvedKey).update(canonicalStr, 'utf8').digest('hex');
|
|
106
132
|
}
|
|
107
|
-
function verifyCapabilitySignature(capability, secretKey
|
|
133
|
+
function verifyCapabilitySignature(capability, secretKey) {
|
|
108
134
|
if (!capability || capability.allowed !== true || typeof capability.signature !== 'string') {
|
|
109
135
|
return false;
|
|
110
136
|
}
|
|
137
|
+
const resolvedKey = getOrGenerateLocalActionPolicySecret(secretKey);
|
|
111
138
|
const { signature, allowed, ...rest } = capability;
|
|
112
139
|
const canonicalStr = canonicalizeJson(rest);
|
|
113
|
-
const expectedSigHex = crypto.createHmac('sha256',
|
|
140
|
+
const expectedSigHex = crypto.createHmac('sha256', resolvedKey).update(canonicalStr, 'utf8').digest('hex');
|
|
114
141
|
// Constant-time comparison to prevent timing attacks
|
|
115
142
|
try {
|
|
116
143
|
const sigBuffer = globalThis.Buffer
|
package/dist/chat-contract.d.ts
CHANGED
|
@@ -3,13 +3,16 @@ import { Message, Claim } from './index';
|
|
|
3
3
|
import { ActionExecutionResult } from './action';
|
|
4
4
|
import { ResponseCitation } from './evidence';
|
|
5
5
|
import { SiduriRuntime } from './runtime';
|
|
6
|
+
import { MouthMedium, FormattedMouthOutput } from './mouth-types';
|
|
6
7
|
export interface ChatRequest {
|
|
7
8
|
id?: string;
|
|
8
9
|
companionId?: string;
|
|
9
10
|
message: string;
|
|
10
|
-
role?: 'OWNER' | 'VIEWER' | 'OPERATOR';
|
|
11
|
+
role?: 'OWNER' | 'VIEWER' | 'OPERATOR' | string;
|
|
11
12
|
context?: RequestContext;
|
|
12
13
|
history?: Message[];
|
|
14
|
+
medium?: MouthMedium;
|
|
15
|
+
signal?: AbortSignal;
|
|
13
16
|
[key: string]: any;
|
|
14
17
|
}
|
|
15
18
|
export interface ChatResponseMetadataEvent {
|
|
@@ -65,6 +68,7 @@ export interface ChatResponse {
|
|
|
65
68
|
correlation_id?: string;
|
|
66
69
|
response: ChatResponsePlan;
|
|
67
70
|
metadata?: ChatResponseMetadata;
|
|
71
|
+
delivery?: FormattedMouthOutput;
|
|
68
72
|
reply?: string;
|
|
69
73
|
text?: string;
|
|
70
74
|
audioUrl?: string;
|
package/dist/chat-contract.js
CHANGED
|
@@ -9,40 +9,42 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
9
9
|
const userMessage = payload.message || payload.text || '';
|
|
10
10
|
const history = Array.isArray(payload.history) ? payload.history : [];
|
|
11
11
|
let roleOrContext;
|
|
12
|
-
if (payload.
|
|
13
|
-
roleOrContext = payload.
|
|
12
|
+
if (payload.context) {
|
|
13
|
+
roleOrContext = payload.context;
|
|
14
14
|
}
|
|
15
|
-
else if (payload.
|
|
16
|
-
|
|
17
|
-
const authRole = payload.context.actor?.authorizationRole;
|
|
18
|
-
roleOrContext =
|
|
19
|
-
authRole === 'administrator'
|
|
20
|
-
? 'OWNER'
|
|
21
|
-
: authRole === 'operator'
|
|
22
|
-
? 'OPERATOR'
|
|
23
|
-
: (authRole === 'viewer' ? 'VIEWER' : 'OWNER');
|
|
15
|
+
else if (payload.role) {
|
|
16
|
+
roleOrContext = payload.role;
|
|
24
17
|
}
|
|
25
18
|
else {
|
|
26
19
|
roleOrContext = 'OWNER';
|
|
27
20
|
}
|
|
28
|
-
const runtimeResult =
|
|
21
|
+
const runtimeResult = (payload.medium || payload.signal)
|
|
22
|
+
? await runtime.handleUserMessage(userMessage, roleOrContext, history, payload.medium, payload.signal)
|
|
23
|
+
: await runtime.handleUserMessage(userMessage, roleOrContext, history);
|
|
24
|
+
const delivery = runtimeResult?.delivery;
|
|
29
25
|
// Normalize response plan
|
|
30
|
-
const speech =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
const speech = delivery?.displayText ||
|
|
27
|
+
delivery?.text ||
|
|
28
|
+
runtimeResult?.response?.subtitle_ja ||
|
|
29
|
+
runtimeResult?.response?.subtitle_en ||
|
|
30
|
+
'';
|
|
31
|
+
const audioUrl = delivery?.audioUrl || runtimeResult?.response?.audio_url;
|
|
32
|
+
// Extract avatar expression if any event was generated or provided by Mouth delivery
|
|
33
|
+
let expression = delivery?.expression || 'neutral';
|
|
34
|
+
if (expression === 'neutral') {
|
|
35
|
+
const events = runtimeResult?.metadata?.events || [];
|
|
36
|
+
const avatarEvent = events.find((e) => e.kind === 'avatar' || e.kind === 'body');
|
|
37
|
+
if (avatarEvent && avatarEvent.expression) {
|
|
38
|
+
expression = avatarEvent.expression;
|
|
39
|
+
}
|
|
38
40
|
}
|
|
39
41
|
// Ensure both spoken_ja and subtitle_en are accessible alongside speech_id and evidence_ids
|
|
40
42
|
const responsePlan = {
|
|
41
43
|
speech_id: runtimeResult?.response?.speech_id,
|
|
42
44
|
audio_url: audioUrl,
|
|
43
|
-
subtitle_ja: runtimeResult?.response?.subtitle_ja ?? speech,
|
|
44
|
-
subtitle_en: runtimeResult?.response?.subtitle_en ?? speech,
|
|
45
|
-
spoken_ja: runtimeResult?.response?.spoken_ja ?? runtimeResult?.response?.subtitle_ja ?? speech,
|
|
45
|
+
subtitle_ja: delivery?.subtitles?.ja ?? runtimeResult?.response?.subtitle_ja ?? speech,
|
|
46
|
+
subtitle_en: delivery?.subtitles?.en ?? runtimeResult?.response?.subtitle_en ?? speech,
|
|
47
|
+
spoken_ja: delivery?.subtitles?.spoken ?? runtimeResult?.response?.spoken_ja ?? runtimeResult?.response?.subtitle_ja ?? speech,
|
|
46
48
|
evidence_ids: runtimeResult?.metadata?.evidence_ids ?? runtimeResult?.response?.evidence_ids ?? [],
|
|
47
49
|
};
|
|
48
50
|
const metadata = {
|
|
@@ -55,6 +57,7 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
55
57
|
response_id: runtimeResult?.response_id,
|
|
56
58
|
correlation_id: runtimeResult?.correlation_id,
|
|
57
59
|
response: responsePlan,
|
|
60
|
+
delivery,
|
|
58
61
|
metadata,
|
|
59
62
|
// Convenience fields for legacy/simple consumers
|
|
60
63
|
reply: speech,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BrainOrgan, Message, ResponsePlan, MemoryScope } from './index';
|
|
2
|
+
export interface CognitionPlanningParams {
|
|
3
|
+
companionName: string;
|
|
4
|
+
brain?: BrainOrgan;
|
|
5
|
+
systemPrompt: string;
|
|
6
|
+
contextPrompt: string;
|
|
7
|
+
recentMessages: Message[];
|
|
8
|
+
recipient?: MemoryScope;
|
|
9
|
+
perceivedText: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Invokes BrainOrgan to generate a structured ResponsePlan, or provides
|
|
13
|
+
* a graceful baseline response if Brain is absent or in headless passive mode.
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateCognitionPlan(params: CognitionPlanningParams): Promise<ResponsePlan>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateCognitionPlan = generateCognitionPlan;
|
|
4
|
+
/**
|
|
5
|
+
* Invokes BrainOrgan to generate a structured ResponsePlan, or provides
|
|
6
|
+
* a graceful baseline response if Brain is absent or in headless passive mode.
|
|
7
|
+
*/
|
|
8
|
+
async function generateCognitionPlan(params) {
|
|
9
|
+
const { companionName, brain, systemPrompt, contextPrompt, recentMessages, recipient, perceivedText, } = params;
|
|
10
|
+
if (brain && typeof brain.generatePlan === 'function') {
|
|
11
|
+
return brain.generatePlan({
|
|
12
|
+
systemPrompt,
|
|
13
|
+
contextPrompt,
|
|
14
|
+
recentMessages,
|
|
15
|
+
recipient,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// Graceful baseline response when Brain is not configured or in headless passive mode
|
|
19
|
+
return {
|
|
20
|
+
speech: `[Siduri ${companionName}] Acknowledged: ${perceivedText}`,
|
|
21
|
+
language: 'en',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { KnowledgeOrgan, MemoryOrgan, KnowledgeItem, Claim, BehaviorDirective, EvidenceRecord, ResponseCitation, RequestContext } from './index';
|
|
2
|
+
export interface ContextRetrievalParams {
|
|
3
|
+
companionId: string;
|
|
4
|
+
perceivedText: string;
|
|
5
|
+
requestContext: RequestContext;
|
|
6
|
+
role: 'OWNER' | 'VIEWER' | 'OPERATOR';
|
|
7
|
+
isContextObject: boolean;
|
|
8
|
+
shouldQueryKnowledge: boolean;
|
|
9
|
+
knowledge?: KnowledgeOrgan;
|
|
10
|
+
memory?: MemoryOrgan;
|
|
11
|
+
}
|
|
12
|
+
export interface RetrievedContext {
|
|
13
|
+
knowledgeData: KnowledgeItem[];
|
|
14
|
+
memoryData: Claim[];
|
|
15
|
+
activeDirectives: BehaviorDirective[];
|
|
16
|
+
subsystemDiagnostics: Record<string, string>;
|
|
17
|
+
collectedEvidence: EvidenceRecord[];
|
|
18
|
+
citations: ResponseCitation[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Concurrently queries Knowledge and Memory organs with graceful degradation,
|
|
22
|
+
* collecting diagnostics and synthesizing evidence records and citations.
|
|
23
|
+
*/
|
|
24
|
+
export declare function retrieveRuntimeContext(params: ContextRetrievalParams): Promise<RetrievedContext>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retrieveRuntimeContext = retrieveRuntimeContext;
|
|
4
|
+
/**
|
|
5
|
+
* Concurrently queries Knowledge and Memory organs with graceful degradation,
|
|
6
|
+
* collecting diagnostics and synthesizing evidence records and citations.
|
|
7
|
+
*/
|
|
8
|
+
async function retrieveRuntimeContext(params) {
|
|
9
|
+
const { companionId, perceivedText, requestContext, role, isContextObject, shouldQueryKnowledge, knowledge, memory, } = params;
|
|
10
|
+
const queryOptions = isContextObject
|
|
11
|
+
? {
|
|
12
|
+
channel: requestContext.conversation.channel,
|
|
13
|
+
audienceId: requestContext.conversation.audienceId,
|
|
14
|
+
limit: 5,
|
|
15
|
+
}
|
|
16
|
+
: role;
|
|
17
|
+
const subsystemDiagnostics = {};
|
|
18
|
+
const [knowledgeData, memoryData, activeDirectives] = await Promise.all([
|
|
19
|
+
knowledge && shouldQueryKnowledge && typeof knowledge.search === 'function'
|
|
20
|
+
? knowledge.search(perceivedText).catch((e) => {
|
|
21
|
+
console.error('[SiduriRuntime] Knowledge search failed:', e.message);
|
|
22
|
+
subsystemDiagnostics['knowledge'] = `UNAVAILABLE: ${e.message}`;
|
|
23
|
+
return [];
|
|
24
|
+
})
|
|
25
|
+
: Promise.resolve([]),
|
|
26
|
+
memory && typeof memory.searchClaims === 'function'
|
|
27
|
+
? memory.searchClaims(perceivedText, queryOptions, 5).catch((e) => {
|
|
28
|
+
console.error('[SiduriRuntime] Memory search failed:', e.message);
|
|
29
|
+
subsystemDiagnostics['memory_claims'] = `UNAVAILABLE: ${e.message}`;
|
|
30
|
+
return [];
|
|
31
|
+
})
|
|
32
|
+
: Promise.resolve([]),
|
|
33
|
+
memory && typeof memory.getDirectives === 'function'
|
|
34
|
+
? memory.getDirectives().catch((e) => {
|
|
35
|
+
console.error('[SiduriRuntime] Memory directives failed:', e.message);
|
|
36
|
+
subsystemDiagnostics['memory_directives'] = `UNAVAILABLE: ${e.message}`;
|
|
37
|
+
return [];
|
|
38
|
+
})
|
|
39
|
+
: Promise.resolve([]),
|
|
40
|
+
]);
|
|
41
|
+
// Build evidence records from retrieved knowledge context
|
|
42
|
+
const collectedEvidence = [];
|
|
43
|
+
const citations = [];
|
|
44
|
+
if (knowledgeData.length > 0) {
|
|
45
|
+
for (const k of knowledgeData) {
|
|
46
|
+
if (k.evidenceRecord) {
|
|
47
|
+
const nativeRecord = {
|
|
48
|
+
...k.evidenceRecord,
|
|
49
|
+
};
|
|
50
|
+
collectedEvidence.push(nativeRecord);
|
|
51
|
+
citations.push({
|
|
52
|
+
sourceId: nativeRecord.sourceId,
|
|
53
|
+
revision: nativeRecord.revision,
|
|
54
|
+
documentId: nativeRecord.documentId || k.citations?.[0]?.documentId,
|
|
55
|
+
chunkId: nativeRecord.chunkId || k.citations?.[0]?.chunkId,
|
|
56
|
+
locator: nativeRecord.locator || k.citations?.[0]?.locator,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Synthesize fallback evidence record with provenance
|
|
61
|
+
const evId = `ev-know-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
62
|
+
const sourceId = k.provenance || 'configured-knowledge';
|
|
63
|
+
collectedEvidence.push({
|
|
64
|
+
evidenceId: evId,
|
|
65
|
+
sourceId,
|
|
66
|
+
revision: k.revision,
|
|
67
|
+
origin: 'knowledge',
|
|
68
|
+
trust: 'configured',
|
|
69
|
+
sensitivity: 'public',
|
|
70
|
+
companionId,
|
|
71
|
+
correlationId: requestContext.conversation.correlationId,
|
|
72
|
+
createdAt: new Date().toISOString(),
|
|
73
|
+
});
|
|
74
|
+
citations.push({
|
|
75
|
+
sourceId,
|
|
76
|
+
revision: k.revision,
|
|
77
|
+
documentId: k.citations?.[0]?.documentId,
|
|
78
|
+
chunkId: k.citations?.[0]?.chunkId,
|
|
79
|
+
locator: k.citations?.[0]?.locator,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
knowledgeData,
|
|
86
|
+
memoryData,
|
|
87
|
+
activeDirectives,
|
|
88
|
+
subsystemDiagnostics,
|
|
89
|
+
collectedEvidence,
|
|
90
|
+
citations,
|
|
91
|
+
};
|
|
92
|
+
}
|
package/dist/context.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
export type AuthorizationRole = 'viewer' | 'operator' | 'administrator';
|
|
2
|
-
export type Channel = 'public' | 'direct' | 'private' | 'operator';
|
|
3
1
|
export interface ActorContext {
|
|
4
2
|
actorId: string;
|
|
5
3
|
sessionId: string;
|
|
6
|
-
|
|
7
|
-
capabilities
|
|
8
|
-
|
|
4
|
+
authenticated?: boolean;
|
|
5
|
+
capabilities?: string[];
|
|
6
|
+
[key: string]: unknown;
|
|
9
7
|
}
|
|
10
8
|
export interface ConversationContext {
|
|
11
|
-
channel: Channel;
|
|
12
|
-
audienceId: string;
|
|
13
|
-
isLive?: boolean;
|
|
14
9
|
correlationId: string;
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
channel?: string;
|
|
12
|
+
audienceId?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
15
14
|
}
|
|
16
15
|
export type SubjectKind = 'actor' | 'companion' | 'configured';
|
|
17
16
|
export interface SubjectRef {
|
|
@@ -23,16 +22,17 @@ export interface RequestContext {
|
|
|
23
22
|
companionId: string;
|
|
24
23
|
actor: ActorContext;
|
|
25
24
|
conversation: ConversationContext;
|
|
25
|
+
source?: 'local' | 'external' | string;
|
|
26
26
|
subject?: SubjectRef;
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
27
28
|
}
|
|
28
|
-
export type DiagnosticCode = '
|
|
29
|
-
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | '
|
|
29
|
+
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped';
|
|
30
|
+
export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'FORBIDDEN_CONTEXT' | 'LEGACY_PERSONAL_AUDIENCE' | 'AMBIGUOUS_CONTEXT' | 'UNAUTHORIZED_CAPABILITY';
|
|
30
31
|
export interface ContextError {
|
|
31
32
|
code: ContextErrorCode;
|
|
32
33
|
message?: string;
|
|
33
34
|
fields?: string[];
|
|
34
35
|
field?: string;
|
|
35
|
-
conflicts?: string[];
|
|
36
36
|
correlationId?: string;
|
|
37
37
|
}
|
|
38
38
|
export interface RequestContextValidationResult {
|
|
@@ -41,7 +41,5 @@ export interface RequestContextValidationResult {
|
|
|
41
41
|
diagnostics?: DiagnosticCode[];
|
|
42
42
|
error?: ContextError;
|
|
43
43
|
}
|
|
44
|
-
export declare function isValidAuthorizationRole(role: unknown): role is AuthorizationRole;
|
|
45
|
-
export declare function isValidChannel(channel: unknown): channel is Channel;
|
|
46
44
|
export declare function isValidSubjectKind(kind: unknown): kind is SubjectKind;
|
|
47
45
|
export declare function validateRequestContext(context: unknown): RequestContextValidationResult;
|
package/dist/context.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// Single-owner, single-machine context model
|
|
3
|
+
// Security perimeter is the local machine boundary (external vs internal).
|
|
4
|
+
// No internal audience, viewer, or owner role hierarchies.
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isValidAuthorizationRole = isValidAuthorizationRole;
|
|
4
|
-
exports.isValidChannel = isValidChannel;
|
|
5
6
|
exports.isValidSubjectKind = isValidSubjectKind;
|
|
6
7
|
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
8
|
function isValidSubjectKind(kind) {
|
|
14
9
|
return kind === 'actor' || kind === 'companion' || kind === 'configured';
|
|
15
10
|
}
|
|
@@ -38,26 +33,11 @@ function validateRequestContext(context) {
|
|
|
38
33
|
if (!ctx.actor.sessionId || typeof ctx.actor.sessionId !== 'string' || ctx.actor.sessionId.trim() === '') {
|
|
39
34
|
missingFields.push('actor.sessionId');
|
|
40
35
|
}
|
|
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
36
|
}
|
|
51
37
|
if (!ctx.conversation || typeof ctx.conversation !== 'object') {
|
|
52
38
|
missingFields.push('conversation');
|
|
53
39
|
}
|
|
54
40
|
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
41
|
if (!ctx.conversation.correlationId || typeof ctx.conversation.correlationId !== 'string' || ctx.conversation.correlationId.trim() === '') {
|
|
62
42
|
missingFields.push('conversation.correlationId');
|
|
63
43
|
}
|
package/dist/context.test.js
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const context_1 = require("./context");
|
|
4
|
-
describe('Core Context Contract (
|
|
4
|
+
describe('Core Context Contract (Single-Owner Single-Machine)', () => {
|
|
5
5
|
const validContext = {
|
|
6
6
|
companionId: 'companion-a',
|
|
7
7
|
actor: {
|
|
8
|
-
actorId: '
|
|
8
|
+
actorId: 'local-user',
|
|
9
9
|
sessionId: 'session-a',
|
|
10
|
-
|
|
11
|
-
capabilities: ['chat:
|
|
12
|
-
authenticated: false,
|
|
10
|
+
authenticated: true,
|
|
11
|
+
capabilities: ['chat:interact'],
|
|
13
12
|
},
|
|
14
13
|
conversation: {
|
|
15
|
-
channel: 'public',
|
|
16
|
-
audienceId: 'audience-public',
|
|
17
14
|
correlationId: 'corr-a',
|
|
18
15
|
},
|
|
19
16
|
subject: {
|
|
20
|
-
subjectId: 'actor:
|
|
17
|
+
subjectId: 'actor:local-user',
|
|
21
18
|
kind: 'actor',
|
|
22
|
-
ownerActorId: '
|
|
19
|
+
ownerActorId: 'local-user',
|
|
23
20
|
},
|
|
24
21
|
};
|
|
25
|
-
test('validates a correct
|
|
22
|
+
test('validates a correct RequestContext', () => {
|
|
26
23
|
const result = (0, context_1.validateRequestContext)(validContext);
|
|
27
24
|
expect(result.accepted).toBe(true);
|
|
28
25
|
expect(result.context).toEqual(validContext);
|
|
@@ -46,36 +43,6 @@ describe('Core Context Contract (P1)', () => {
|
|
|
46
43
|
expect(result.error?.code).toBe('MISSING_CONTEXT');
|
|
47
44
|
expect(result.error?.fields).toEqual(expect.arrayContaining(['companionId', 'actor', 'conversation']));
|
|
48
45
|
});
|
|
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
46
|
test('validates subject kinds and constraints', () => {
|
|
80
47
|
expect((0, context_1.isValidSubjectKind)('actor')).toBe(true);
|
|
81
48
|
expect((0, context_1.isValidSubjectKind)('companion')).toBe(true);
|