@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.
Files changed (61) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +50 -0
  3. package/dist/action-policy.js +4 -9
  4. package/dist/architecture-boundary.test.js +1 -0
  5. package/dist/capability.d.ts +8 -0
  6. package/dist/capability.js +31 -4
  7. package/dist/chat-contract.d.ts +5 -1
  8. package/dist/chat-contract.js +26 -23
  9. package/dist/cognition-planner.d.ts +15 -0
  10. package/dist/cognition-planner.js +23 -0
  11. package/dist/context-retriever.d.ts +24 -0
  12. package/dist/context-retriever.js +92 -0
  13. package/dist/context.d.ts +11 -13
  14. package/dist/context.js +3 -23
  15. package/dist/context.test.js +7 -40
  16. package/dist/evidence.d.ts +12 -8
  17. package/dist/evidence.js +6 -4
  18. package/dist/experience-emitter.d.ts +21 -0
  19. package/dist/experience-emitter.js +48 -0
  20. package/dist/experience.d.ts +6 -5
  21. package/dist/experience.js +3 -2
  22. package/dist/experience.test.js +5 -4
  23. package/dist/gating.d.ts +3 -2
  24. package/dist/gating.js +4 -8
  25. package/dist/index.d.ts +27 -43
  26. package/dist/index.js +14 -0
  27. package/dist/input-normalizer.d.ts +14 -0
  28. package/dist/input-normalizer.js +58 -0
  29. package/dist/input-normalizer.test.d.ts +1 -0
  30. package/dist/input-normalizer.test.js +39 -0
  31. package/dist/intent-classifier.d.ts +24 -0
  32. package/dist/intent-classifier.js +53 -0
  33. package/dist/intent-classifier.test.d.ts +1 -0
  34. package/dist/intent-classifier.test.js +68 -0
  35. package/dist/memory-settler.d.ts +27 -0
  36. package/dist/memory-settler.js +95 -0
  37. package/dist/mouth-types.d.ts +85 -0
  38. package/dist/mouth-types.js +2 -0
  39. package/dist/perception-cycle.test.d.ts +1 -0
  40. package/dist/perception-cycle.test.js +155 -0
  41. package/dist/prompt-compiler.d.ts +20 -0
  42. package/dist/prompt-compiler.js +57 -0
  43. package/dist/prompt-compiler.test.d.ts +1 -0
  44. package/dist/prompt-compiler.test.js +76 -0
  45. package/dist/proposals.d.ts +30 -0
  46. package/dist/proposals.js +2 -0
  47. package/dist/response-envelope.d.ts +25 -0
  48. package/dist/response-envelope.js +64 -0
  49. package/dist/runtime-facades.test.d.ts +1 -0
  50. package/dist/runtime-facades.test.js +69 -0
  51. package/dist/runtime.d.ts +79 -15
  52. package/dist/runtime.js +327 -326
  53. package/dist/session-history.d.ts +20 -0
  54. package/dist/session-history.js +55 -0
  55. package/dist/session-history.test.d.ts +1 -0
  56. package/dist/session-history.test.js +38 -0
  57. package/dist/sqlite-action-store.d.ts +20 -0
  58. package/dist/sqlite-action-store.js +225 -0
  59. package/dist/sqlite-action-store.test.d.ts +1 -0
  60. package/dist/sqlite-action-store.test.js +252 -0
  61. 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
+ }
@@ -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
- const envSecret = typeof process !== 'undefined' && process.env ? process.env.ACTION_POLICY_SECRET : undefined;
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'));
@@ -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;
@@ -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 = 'siduri_y_action_policy_secret') {
128
+ function signCapabilityPayload(payload, secretKey) {
129
+ const resolvedKey = getOrGenerateLocalActionPolicySecret(secretKey);
104
130
  const canonicalStr = canonicalizeJson(payload);
105
- return crypto.createHmac('sha256', secretKey).update(canonicalStr, 'utf8').digest('hex');
131
+ return crypto.createHmac('sha256', resolvedKey).update(canonicalStr, 'utf8').digest('hex');
106
132
  }
107
- function verifyCapabilitySignature(capability, secretKey = 'siduri_y_action_policy_secret') {
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', secretKey).update(canonicalStr, 'utf8').digest('hex');
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
@@ -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;
@@ -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.role) {
13
- roleOrContext = payload.role;
12
+ if (payload.context) {
13
+ roleOrContext = payload.context;
14
14
  }
15
- else if (payload.context) {
16
- // Map authorization role to legacy memory scope for backwards-compatible runtime calls
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 = await runtime.handleUserMessage(userMessage, roleOrContext, history);
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 = runtimeResult?.response?.subtitle_ja || runtimeResult?.response?.subtitle_en || '';
31
- const audioUrl = runtimeResult?.response?.audio_url;
32
- // Extract avatar expression if any event was generated
33
- let expression = 'neutral';
34
- const events = runtimeResult?.metadata?.events || [];
35
- const avatarEvent = events.find((e) => e.kind === 'avatar' || e.kind === 'body');
36
- if (avatarEvent && avatarEvent.expression) {
37
- expression = avatarEvent.expression;
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
- authorizationRole: AuthorizationRole;
7
- capabilities: string[];
8
- authenticated: boolean;
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 = 'audience_defaulted_by_public_policy' | 'legacy_role_mapped_to_authorization' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped' | 'legacy_primary_user_quarantined';
29
- export type ContextErrorCode = 'MISSING_CONTEXT' | 'INVALID_CONTEXT' | 'AMBIGUOUS_CONTEXT' | 'FORBIDDEN_CONTEXT' | 'LEGACY_PERSONAL_AUDIENCE' | 'UNAUTHORIZED_CHANNEL_OR_CAPABILITY';
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
  }
@@ -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 (P1)', () => {
4
+ describe('Core Context Contract (Single-Owner Single-Machine)', () => {
5
5
  const validContext = {
6
6
  companionId: 'companion-a',
7
7
  actor: {
8
- actorId: 'actor-a',
8
+ actorId: 'local-user',
9
9
  sessionId: 'session-a',
10
- authorizationRole: 'viewer',
11
- capabilities: ['chat:public'],
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:actor-a',
17
+ subjectId: 'actor:local-user',
21
18
  kind: 'actor',
22
- ownerActorId: 'actor-a',
19
+ ownerActorId: 'local-user',
23
20
  },
24
21
  };
25
- test('validates a correct neutral RequestContext', () => {
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);