@siduri-x/core 2.0.1 → 2.0.2

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/runtime.js CHANGED
@@ -1,431 +1,86 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SiduriRuntime = void 0;
4
- const index_1 = require("./index");
5
- const input_normalizer_1 = require("./input-normalizer");
6
- const intent_classifier_1 = require("./intent-classifier");
7
- const context_retriever_1 = require("./context-retriever");
8
- const prompt_compiler_1 = require("./prompt-compiler");
9
- const cognition_planner_1 = require("./cognition-planner");
10
- const memory_settler_1 = require("./memory-settler");
11
- const action_executor_1 = require("./action-executor");
12
- const experience_emitter_1 = require("./experience-emitter");
13
- const response_envelope_1 = require("./response-envelope");
14
- const session_history_1 = require("./session-history");
4
+ const perception_pipeline_1 = require("./perception-pipeline");
5
+ const container_1 = require("./container");
15
6
  /**
16
- * SiduriRuntime coordinates companion lifecycle, sensory perception,
17
- * context retrieval, cognition planning, safety gating, action execution,
18
- * and experience emission across decoupled organs.
7
+ * SiduriRuntime coordinates companion perception and cognition execution.
8
+ * Lifecycle management and organ storage are handled by CompanionContainer.
9
+ * The 12-stage perception cycle is executed via PerceptionPipeline.
19
10
  */
20
11
  class SiduriRuntime {
21
12
  id;
22
13
  config;
23
- brain;
24
- memory;
25
- voice;
26
- knowledge;
27
- vision;
28
- behavior;
29
- body;
30
- hands;
31
- ear;
32
- observation;
33
- mouth;
34
- self;
35
- externalKnowledge;
36
- gating;
37
- actionPolicy;
38
- dispatcher;
39
- sessionHistory = new session_history_1.SessionHistoryManager();
40
- // Backward-compatible getter/setter for conversation history
14
+ container;
15
+ pipeline;
16
+ constructor(id, config, containerOrOrgans = {}, pipeline) {
17
+ this.id = id;
18
+ this.config = config;
19
+ if (containerOrOrgans instanceof container_1.CompanionContainer) {
20
+ this.container = containerOrOrgans;
21
+ }
22
+ else {
23
+ this.container = new container_1.CompanionContainer(id, config, containerOrOrgans);
24
+ }
25
+ this.pipeline = pipeline || (0, perception_pipeline_1.createDefaultPerceptionPipeline)();
26
+ }
27
+ // Direct organ accessors through container
28
+ get organs() { return this.container.organs; }
29
+ get brain() { return this.container.brain; }
30
+ get memory() { return this.container.memory; }
31
+ get voice() { return this.container.voice; }
32
+ get knowledge() { return this.container.knowledge; }
33
+ get vision() { return this.container.vision; }
34
+ get behavior() { return this.container.behavior; }
35
+ get body() { return this.container.body; }
36
+ get hands() { return this.container.hands; }
37
+ get ear() { return this.container.ear; }
38
+ get observation() { return this.container.observation; }
39
+ set observation(org) { this.container.observation = org; }
40
+ get mouth() { return this.container.mouth; }
41
+ get self() { return this.container.self; }
42
+ get externalKnowledge() { return this.container.externalKnowledge; }
43
+ get gating() { return this.container.gating; }
44
+ get actionPolicy() { return this.container.actionPolicy; }
45
+ get dispatcher() { return this.container.dispatcher; }
46
+ get sessionHistory() { return this.container.sessionHistory; }
47
+ // Conversation history accessors
41
48
  get conversationHistory() {
42
- return this.sessionHistory.getHistory('default');
49
+ return this.container.sessionHistory.getHistory('default');
43
50
  }
44
51
  set conversationHistory(messages) {
45
- this.sessionHistory.setHistory('default', messages);
46
- }
47
- constructor(id, config, organs = {}) {
48
- this.id = id;
49
- this.config = config;
50
- this.brain = organs.brain;
51
- this.memory = organs.memory;
52
- this.voice = organs.voice;
53
- this.knowledge = organs.knowledge;
54
- this.vision = organs.vision;
55
- this.behavior = organs.behavior;
56
- this.body = organs.body;
57
- this.hands = organs.hands;
58
- this.ear = organs.ear;
59
- this.observation = organs.observation;
60
- this.mouth = organs.mouth;
61
- this.self = organs.self;
62
- this.externalKnowledge = organs.externalKnowledge;
63
- this.gating = new index_1.ResponseGatingEngine();
64
- let actionStore = organs.actionStore;
65
- if (!actionStore) {
66
- const storeOpt = config.actionStore;
67
- const storePath = config.actionStorePath || (typeof storeOpt === 'object' ? storeOpt.dbPath : undefined);
68
- if (storeOpt === 'sqlite' || (typeof storeOpt === 'object' && storeOpt.type === 'sqlite') || storePath) {
69
- actionStore = new index_1.SqliteActionStore({ dbPath: storePath });
70
- }
71
- }
72
- this.actionPolicy = organs.actionPolicy || new index_1.ActionPolicyEngine({
73
- store: actionStore,
74
- });
75
- this.dispatcher = new index_1.ExperienceDispatcher();
76
- if (this.voice && typeof this.voice.handleEvent === 'function') {
77
- this.dispatcher.registerAdapter(this.voice);
78
- }
79
- if (this.body && typeof this.body.handleEvent === 'function') {
80
- this.dispatcher.registerAdapter(this.body);
81
- }
82
- if (this.mouth && typeof this.mouth.handleEvent === 'function') {
83
- this.dispatcher.registerAdapter(this.mouth);
84
- }
52
+ this.container.sessionHistory.setHistory('default', messages);
85
53
  }
86
54
  async initialize() {
87
- if (this.memory && typeof this.memory.initialize === 'function') {
88
- await this.memory.initialize(this.id);
89
- }
90
- if (this.hands && typeof this.hands.listTools === 'function') {
91
- const tools = await this.hands.listTools();
92
- for (const tool of tools) {
93
- this.actionPolicy.registerToolDefinition(tool);
94
- }
95
- }
55
+ return this.container.initialize();
96
56
  }
97
- // --- Session History Accessors ---
98
57
  getSessionHistory(sessionKey) {
99
- return this.sessionHistory.getHistory(sessionKey);
58
+ return this.container.sessionHistory.getHistory(sessionKey);
100
59
  }
101
60
  clearHistory(sessionKey) {
102
- this.sessionHistory.clear(sessionKey);
103
- }
104
- // --- Observation & Vision Facades ---
105
- async analyzeVision(imageUrl, prompt) {
106
- if (!this.vision || typeof this.vision.analyze !== 'function') {
107
- throw new Error('Vision organ is not configured on this runtime');
108
- }
109
- return this.vision.analyze(imageUrl, prompt);
110
- }
111
- async ingestObservation(frame, sourceName, providerId) {
112
- if (!this.observation || typeof this.observation.ingest !== 'function') {
113
- return { duplicate: false, reason: 'provider_failure' };
114
- }
115
- return this.observation.ingest(frame, sourceName, providerId);
116
- }
117
- getCurrentObservations(now) {
118
- if (!this.observation || typeof this.observation.current !== 'function') {
119
- return [];
120
- }
121
- return this.observation.current(now);
122
- }
123
- clearExpiredObservations(now) {
124
- if (!this.observation || typeof this.observation.clearExpired !== 'function') {
125
- return 0;
126
- }
127
- return this.observation.clearExpired(now);
61
+ this.container.sessionHistory.clear(sessionKey);
128
62
  }
129
- // --- Memory Facades ---
130
- async getClaims(limit) {
131
- if (!this.memory || typeof this.memory.getClaims !== 'function') {
132
- return [];
133
- }
134
- return this.memory.getClaims(limit);
135
- }
136
- async getPendingClaims(limit) {
137
- if (!this.memory || typeof this.memory.getPendingClaims !== 'function') {
138
- return [];
139
- }
140
- return this.memory.getPendingClaims(limit);
141
- }
142
- async getDirectives() {
143
- if (!this.memory || typeof this.memory.getDirectives !== 'function') {
144
- return [];
145
- }
146
- return this.memory.getDirectives();
147
- }
148
- async approveClaim(id) {
149
- if (!this.memory || typeof this.memory.approveClaim !== 'function') {
150
- throw new Error('Memory organ not configured');
151
- }
152
- return this.memory.approveClaim(id);
153
- }
154
- async rejectClaim(id) {
155
- if (!this.memory || typeof this.memory.rejectClaim !== 'function') {
156
- throw new Error('Memory organ not configured');
157
- }
158
- return this.memory.rejectClaim(id);
159
- }
160
- async updateClaim(id, updates) {
161
- if (!this.memory || typeof this.memory.updateClaim !== 'function') {
162
- throw new Error('Memory organ updateClaim not supported');
163
- }
164
- return this.memory.updateClaim(id, updates);
165
- }
166
- async approveDirective(id, companionId) {
167
- if (!this.memory || typeof this.memory.approveDirective !== 'function') {
168
- throw new Error('Memory organ not configured');
169
- }
170
- return companionId !== undefined
171
- ? this.memory.approveDirective(id, companionId)
172
- : this.memory.approveDirective(id);
173
- }
174
- async rejectDirective(id, companionId) {
175
- if (!this.memory || typeof this.memory.rejectDirective !== 'function') {
176
- throw new Error('Memory organ not configured');
177
- }
178
- return companionId !== undefined
179
- ? this.memory.rejectDirective(id, companionId)
180
- : this.memory.rejectDirective(id);
181
- }
182
- async revokeDirective(id, companionId) {
183
- if (!this.memory || typeof this.memory.revokeDirective !== 'function') {
184
- throw new Error('Memory organ not configured');
185
- }
186
- return companionId !== undefined
187
- ? this.memory.revokeDirective(id, companionId)
188
- : this.memory.revokeDirective(id);
189
- }
190
- async disableDirective(id, companionId) {
191
- if (!this.memory || typeof this.memory.disableDirective !== 'function') {
192
- throw new Error('Memory organ not configured');
193
- }
194
- return companionId !== undefined
195
- ? this.memory.disableDirective(id, companionId)
196
- : this.memory.disableDirective(id);
197
- }
198
- async resetMemory() {
199
- if (!this.memory || typeof this.memory.resetMemory !== 'function') {
200
- throw new Error('Memory organ does not support reset');
201
- }
202
- return this.memory.resetMemory();
203
- }
204
- // --- Response Gating Facades ---
205
- stageResponse(options) {
206
- return this.gating.stageResponse(options);
207
- }
208
- evaluateGate(plan, evidenceRecords) {
209
- return this.gating.evaluateGate(plan, evidenceRecords);
210
- }
211
- approveResponse(options) {
212
- return this.gating.approveResponse(options);
213
- }
214
- rejectResponse(options) {
215
- return this.gating.rejectResponse(options);
216
- }
217
- async approveAction(options) {
218
- return this.actionPolicy.approveAction(options);
219
- }
220
- getStagedPlan(responseId) {
221
- return this.gating.getStagedPlan(responseId);
222
- }
223
- findStagedPlanByCorrelation(companionId, correlationId) {
224
- return this.gating.findStagedPlanByCorrelation(companionId, correlationId);
225
- }
226
- // --- Universal Perception & Cognition Cycle ---
227
63
  /**
228
64
  * Processes an incoming perception (sensory audio, text, platform event, or observation alert)
229
- * through the perception -> retrieval -> cognition -> gating -> action -> experience cycle.
65
+ * through the decoupled PerceptionPipeline.
230
66
  */
231
67
  async processPerception(perception) {
232
- let rawText = perception.text || '';
233
- // If audio buffer is provided and Ear supports transcription, transcribe it
234
- if (!rawText && perception.audioBuffer && this.ear && typeof this.ear.transcribeAudio === 'function') {
235
- rawText = await this.ear.transcribeAudio(perception.audioBuffer);
236
- }
237
- const roleOrContext = perception.context || perception.roleOrContext || 'OWNER';
238
- const history = perception.history || [];
239
- // 1. Input validation, RequestContext synthesis, and Ear perception routing
240
- const input = await (0, input_normalizer_1.normalizeUserInput)(rawText, roleOrContext, history, this.id, this.ear);
241
- const sessionKey = input.requestContext.actor.sessionId ||
242
- input.requestContext.conversation.correlationId ||
243
- 'default';
244
- const currentMessage = { role: 'user', content: input.perceivedText };
245
- const boundedSessionHistory = [...input.boundedHistory, currentMessage].slice(-20);
246
- this.sessionHistory.setHistory(sessionKey, boundedSessionHistory);
247
- this.sessionHistory.setHistory('default', boundedSessionHistory);
248
- // 2. Intent classification (delegating to Ear if available, else heuristics)
249
- const intent = await (0, intent_classifier_1.classifyInputIntentAsync)(input.perceivedText, input.requestContext, this.ear?.classifyIntent
250
- ? (t, c) => this.ear.classifyIntent(t, c)
251
- : undefined);
252
- // 3. Concurrent Knowledge & Memory retrieval with diagnostics & evidence handling
253
- const contextRetrieval = await (0, context_retriever_1.retrieveRuntimeContext)({
68
+ const context = {
254
69
  companionId: this.id,
255
- perceivedText: input.perceivedText,
256
- requestContext: input.requestContext,
257
- role: input.role,
258
- isContextObject: input.isContextObject,
259
- shouldQueryKnowledge: intent.shouldQueryKnowledge,
260
- knowledge: this.knowledge,
261
- memory: this.memory,
262
- self: this.self,
263
- externalKnowledge: this.externalKnowledge,
264
- });
265
- // 4. Neutral system prompt and contextual prompt compilation
266
- const prompts = await (0, prompt_compiler_1.compilePrompts)({
267
- companionName: this.config.name,
268
- companionId: this.id,
269
- role: input.role,
270
- requestContext: input.requestContext,
271
- behavior: this.behavior,
272
- activeDirectives: contextRetrieval.activeDirectives,
273
- subsystemDiagnostics: contextRetrieval.subsystemDiagnostics,
274
- knowledgeData: contextRetrieval.knowledgeData,
275
- memoryData: contextRetrieval.memoryData,
276
- lifeContext: contextRetrieval.lifeContext,
277
- });
278
- // 5. Cognition planning via BrainOrgan
279
- const plan = await (0, cognition_planner_1.generateCognitionPlan)({
280
70
  companionName: this.config.name,
281
- brain: this.brain,
282
- systemPrompt: prompts.systemPrompt,
283
- contextPrompt: prompts.contextPrompt,
284
- recentMessages: this.sessionHistory.getHistory(sessionKey).slice(-10),
285
- recipient: input.role,
286
- perceivedText: input.perceivedText,
287
- });
288
- // 6. Stage response and evaluate safety gating boundary
289
- const stagedPlan = this.gating.stageResponse({
290
- requestContext: input.requestContext,
291
- candidateSpeech: plan.speech,
292
- candidateLanguage: plan.language || 'ja',
293
- internalMonologue: plan.internalMonologue,
294
- memoryProposals: plan.memoryProposals,
295
- behaviorProposals: plan.behaviorProposals,
296
- evidenceRecords: contextRetrieval.collectedEvidence,
297
- citations: contextRetrieval.citations,
298
- });
299
- const gateEval = this.gating.evaluateGate(stagedPlan, contextRetrieval.collectedEvidence);
300
- if (!gateEval.admissible) {
301
- return (0, response_envelope_1.createGateRejectionEnvelope)(stagedPlan, gateEval);
302
- }
303
- this.sessionHistory.append(sessionKey, { role: 'assistant', content: plan.speech });
304
- this.sessionHistory.append('default', { role: 'assistant', content: plan.speech });
305
- // 7. Settle memory proposals and persist source events
306
- const memorySettlement = await (0, memory_settler_1.settleMemoryProposals)({
307
- companionId: this.id,
308
- perceivedText: input.perceivedText,
309
- role: input.role,
310
- requestContext: input.requestContext,
311
- memory: this.memory,
312
- explicitTeaching: intent.explicitTeaching,
313
- plan,
314
- });
315
- // 8. Authorize and execute action intents under Primary Security Invariant
316
- const actionResults = await (0, action_executor_1.executeActionIntents)({
317
- actionIntents: plan.actionIntents,
318
- requestContext: input.requestContext,
319
- actionPolicy: this.actionPolicy,
320
- hands: this.hands,
321
- });
322
- // 9. Dispatch ExperienceEvents to registered adapters
323
- const experienceEmission = await (0, experience_emitter_1.emitExperienceEvents)({
324
- companionId: this.id,
325
- requestContext: input.requestContext,
326
- stagedPlan,
327
- gateEval,
328
- speech: plan.speech,
329
- language: plan.language || 'ja',
330
- dispatcher: this.dispatcher,
331
- voice: this.voice,
332
- body: this.body,
333
- });
334
- // 10. Deliver utterance via Mouth organ (UI / Output Channel Decoupling)
335
- let mouthDelivery;
336
- if (this.mouth && typeof this.mouth.speak === 'function') {
337
- try {
338
- const avatarEvent = experienceEmission.experienceEvents.find((e) => e.kind === 'avatar');
339
- mouthDelivery = await this.mouth.speak({
340
- utteranceId: stagedPlan.responseId,
341
- companionId: this.id,
342
- responseId: stagedPlan.responseId,
343
- correlationId: stagedPlan.correlationId,
344
- text: plan.speech,
345
- language: plan.language || 'ja',
346
- subtitleJa: plan.speech,
347
- subtitleEn: plan.speech,
348
- spokenJa: plan.speech,
349
- expression: avatarEvent?.expression,
350
- medium: perception.medium,
351
- signal: perception.signal,
352
- audioUrl: experienceEmission.speechId
353
- ? `/voice/stream?id=${experienceEmission.speechId}`
354
- : undefined,
355
- metadata: {
356
- subsystemDiagnostics: contextRetrieval.subsystemDiagnostics,
357
- internalMonologue: plan.internalMonologue,
358
- },
359
- citations: gateEval.filteredCitations,
360
- evidenceIds: gateEval.filteredEvidenceIds,
361
- });
362
- }
363
- catch (e) {
364
- console.error('[SiduriRuntime] Mouth delivery failed:', e?.message || e);
365
- }
366
- }
367
- // 11. Assemble and return response envelope
368
- return (0, response_envelope_1.assembleResponseEnvelope)({
369
- stagedPlan,
370
- speech: plan.speech,
371
- language: plan.language,
372
- speechId: experienceEmission.speechId,
373
- createdMemoryProposals: memorySettlement.createdMemoryProposals,
374
- memoryProposalReceipts: memorySettlement.memoryProposalReceipts,
375
- actionResults,
376
- filteredEvidenceIds: gateEval.filteredEvidenceIds,
377
- filteredCitations: gateEval.filteredCitations,
378
- subsystemDiagnostics: contextRetrieval.subsystemDiagnostics,
379
- experienceEvents: experienceEmission.experienceEvents,
380
- mouthDelivery,
381
- });
382
- }
383
- // --- Mouth Facades ---
384
- async speakMouth(utterance) {
385
- if (!this.mouth || typeof this.mouth.speak !== 'function')
386
- return undefined;
387
- return this.mouth.speak(utterance);
388
- }
389
- formatMouth(utterance, medium) {
390
- if (!this.mouth || typeof this.mouth.format !== 'function')
391
- return undefined;
392
- return this.mouth.format(utterance, medium);
393
- }
394
- registerMouthChannel(channel) {
395
- if (this.mouth && typeof this.mouth.registerChannel === 'function') {
396
- this.mouth.registerChannel(channel);
397
- }
71
+ perception,
72
+ organs: this.container.organs,
73
+ gating: this.container.gating,
74
+ actionPolicy: this.container.actionPolicy,
75
+ dispatcher: this.container.dispatcher,
76
+ sessionHistory: this.container.sessionHistory,
77
+ rawText: perception.text || '',
78
+ };
79
+ return this.pipeline.execute(context);
398
80
  }
399
- unregisterMouthChannel(channelId) {
400
- if (this.mouth && typeof this.mouth.unregisterChannel === 'function') {
401
- this.mouth.unregisterChannel(channelId);
402
- }
403
- }
404
- async broadcastMouth(utterance) {
405
- if (!this.mouth || typeof this.mouth.broadcast !== 'function')
406
- return [];
407
- return this.mouth.broadcast(utterance);
408
- }
409
- interruptMouth(reason) {
410
- if (this.mouth && typeof this.mouth.interrupt === 'function') {
411
- this.mouth.interrupt(reason);
412
- }
413
- }
414
- streamMouth(utterance) {
415
- if (!this.mouth || typeof this.mouth.stream !== 'function') {
416
- return (async function* () {
417
- yield {
418
- utteranceId: utterance.utteranceId,
419
- index: 0,
420
- deltaText: utterance.text,
421
- isComplete: true,
422
- medium: 'web',
423
- };
424
- })();
425
- }
426
- return this.mouth.stream(utterance);
427
- }
428
- // --- Backward-Compatible Chat Adapter ---
81
+ /**
82
+ * Primary entrypoint for text chat messages.
83
+ */
429
84
  async handleUserMessage(message, roleOrContext = 'OWNER', history = [], medium, signal) {
430
85
  return this.processPerception({
431
86
  source: 'text_chat',
@@ -72,14 +72,15 @@ describe('SiduriDatabase', () => {
72
72
  memDb.close();
73
73
  }).not.toThrow();
74
74
  });
75
- it('initializes schema and WAL mode within the startup latency budget (<100ms in CI, typical <20ms locally)', () => {
75
+ it('initializes schema and WAL mode within the startup latency budget (<1000ms in CI, typical <20ms locally)', () => {
76
76
  const start = performance.now();
77
77
  const benchDb = new siduri_db_1.SiduriDatabase({ dbPath });
78
78
  const duration = performance.now() - start;
79
79
  benchDb.close();
80
80
  // In bare-metal local development, SQLite cold init is ~2-5ms.
81
- // Under virtualized CI runners with concurrent Turbo tasks, allow a safe 100ms budget.
82
- expect(duration).toBeLessThan(100);
81
+ // Under virtualized CI runners with concurrent Turbo tasks and shared I/O, allow up to 1000ms.
82
+ const budgetMs = process.env.CI ? 1000 : 250;
83
+ expect(duration).toBeLessThan(budgetMs);
83
84
  });
84
85
  it('stores and retrieves companion identity', () => {
85
86
  db = new siduri_db_1.SiduriDatabase({ dbPath });
@@ -12,7 +12,7 @@ export declare class SqliteActionStore implements ActionStore {
12
12
  reserveExecution(record: PersistentExecutionRecord): Promise<boolean>;
13
13
  updateExecution(record: PersistentExecutionRecord): Promise<void>;
14
14
  getExecution(executionId: string): Promise<PersistentExecutionRecord | undefined>;
15
- saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string): Promise<void>;
15
+ saveApproval(executionId: string, approverActorId: string, reason?: string, approverRole?: string, toolName?: string, parametersHash?: string, companionId?: string, actorId?: string): Promise<void>;
16
16
  isActionApproved(executionId: string): Promise<boolean>;
17
17
  getApproval(executionId: string): Promise<ActionApprovalRecord | undefined>;
18
18
  appendAudit(event: ActionAuditEvent): Promise<void>;
@@ -37,7 +37,11 @@ class SqliteActionStore {
37
37
  approver_actor_id TEXT NOT NULL,
38
38
  reason TEXT,
39
39
  approver_role TEXT,
40
- approved_at TEXT NOT NULL
40
+ approved_at TEXT NOT NULL,
41
+ tool_name TEXT,
42
+ parameters_hash TEXT,
43
+ companion_id TEXT,
44
+ actor_id TEXT
41
45
  );
42
46
 
43
47
  CREATE TABLE IF NOT EXISTS action_audit_log (
@@ -69,6 +73,22 @@ class SqliteActionStore {
69
73
  catch {
70
74
  // Column already exists or table freshly created
71
75
  }
76
+ try {
77
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN tool_name TEXT;');
78
+ }
79
+ catch { }
80
+ try {
81
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN parameters_hash TEXT;');
82
+ }
83
+ catch { }
84
+ try {
85
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN companion_id TEXT;');
86
+ }
87
+ catch { }
88
+ try {
89
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN actor_id TEXT;');
90
+ }
91
+ catch { }
72
92
  }
73
93
  initLastAuditHash() {
74
94
  const row = this.db.prepare('SELECT event_hash FROM action_audit_log ORDER BY id DESC LIMIT 1').get();
@@ -134,17 +154,24 @@ class SqliteActionStore {
134
154
  updatedAt: row.updated_at,
135
155
  };
136
156
  }
137
- async saveApproval(executionId, approverActorId, reason, approverRole) {
157
+ async saveApproval(executionId, approverActorId, reason, approverRole, toolName, parametersHash, companionId, actorId) {
138
158
  const stmt = this.db.prepare(`
139
- INSERT INTO action_approvals (execution_id, approver_actor_id, reason, approver_role, approved_at)
140
- VALUES (?, ?, ?, ?, ?)
159
+ INSERT INTO action_approvals (
160
+ execution_id, approver_actor_id, reason, approver_role, approved_at,
161
+ tool_name, parameters_hash, companion_id, actor_id
162
+ )
163
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
141
164
  ON CONFLICT(execution_id) DO UPDATE SET
142
165
  approver_actor_id = excluded.approver_actor_id,
143
166
  reason = excluded.reason,
144
167
  approver_role = excluded.approver_role,
145
- approved_at = excluded.approved_at
168
+ approved_at = excluded.approved_at,
169
+ tool_name = excluded.tool_name,
170
+ parameters_hash = excluded.parameters_hash,
171
+ companion_id = excluded.companion_id,
172
+ actor_id = excluded.actor_id
146
173
  `);
147
- stmt.run(executionId, approverActorId, reason ?? null, approverRole ?? null, new Date().toISOString());
174
+ stmt.run(executionId, approverActorId, reason ?? null, approverRole ?? null, new Date().toISOString(), toolName ?? null, parametersHash ?? null, companionId ?? null, actorId ?? null);
148
175
  }
149
176
  async isActionApproved(executionId) {
150
177
  const stmt = this.db.prepare('SELECT 1 FROM action_approvals WHERE execution_id = ?');
@@ -163,6 +190,10 @@ class SqliteActionStore {
163
190
  reason: row.reason ?? undefined,
164
191
  approverRole: row.approver_role ?? undefined,
165
192
  approvedAt: row.approved_at,
193
+ toolName: row.tool_name ?? undefined,
194
+ parametersHash: row.parameters_hash ?? undefined,
195
+ companionId: row.companion_id ?? undefined,
196
+ actorId: row.actor_id ?? undefined,
166
197
  };
167
198
  }
168
199
  async appendAudit(event) {
@@ -229,6 +229,7 @@ describe('SqliteActionStore Implementation & Durability', () => {
229
229
  await engine1.approveAction({
230
230
  executionId: 'exec-danger-1',
231
231
  approverActorId: 'local-owner',
232
+ approverRole: 'owner',
232
233
  reason: 'Owner confirmed cleanup',
233
234
  });
234
235
  store1.close();
@@ -247,6 +248,14 @@ describe('SqliteActionStore Implementation & Durability', () => {
247
248
  expect(eval2.decision.decisionCode).toBe('ALLOWED_POLICY');
248
249
  expect(eval2.capability).toBeDefined();
249
250
  expect((0, capability_1.verifyCapabilitySignature)(eval2.capability, secretKey)).toBe(true);
251
+ // 5. Tampered action after restart -> rejected due to approval parameter mismatch
252
+ const tamperedAction = {
253
+ ...action,
254
+ parameters: { force: true, dropDatabase: true },
255
+ };
256
+ const evalTampered = await engine2.evaluateAction(tamperedAction);
257
+ expect(evalTampered.decision.allowed).toBe(false);
258
+ expect(evalTampered.decision.decisionCode).toBe('REJECTED_APPROVAL_MISMATCH');
250
259
  store2.close();
251
260
  });
252
261
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/core",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {