@siduri-x/core 1.0.5 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +50 -0
  3. package/dist/action-policy.d.ts +45 -0
  4. package/dist/action-policy.js +219 -0
  5. package/dist/action-policy.test.d.ts +1 -0
  6. package/dist/action-policy.test.js +193 -0
  7. package/dist/action.d.ts +72 -0
  8. package/dist/action.js +2 -0
  9. package/dist/adversarial.test.d.ts +1 -0
  10. package/dist/adversarial.test.js +489 -0
  11. package/dist/architecture-boundary.test.d.ts +1 -0
  12. package/dist/architecture-boundary.test.js +117 -0
  13. package/dist/capability.d.ts +65 -0
  14. package/dist/capability.js +157 -0
  15. package/dist/capability.test.d.ts +1 -0
  16. package/dist/capability.test.js +268 -0
  17. package/dist/chat-contract.d.ts +81 -0
  18. package/dist/chat-contract.js +68 -0
  19. package/dist/cognition-planner.d.ts +15 -0
  20. package/dist/cognition-planner.js +23 -0
  21. package/dist/context-retriever.d.ts +24 -0
  22. package/dist/context-retriever.js +90 -0
  23. package/dist/context.d.ts +44 -0
  24. package/dist/context.js +72 -0
  25. package/dist/context.test.d.ts +1 -0
  26. package/dist/context.test.js +76 -0
  27. package/dist/dispatcher.d.ts +14 -0
  28. package/dist/dispatcher.js +40 -0
  29. package/dist/dispatcher.test.d.ts +1 -0
  30. package/dist/dispatcher.test.js +58 -0
  31. package/dist/ear-types.d.ts +33 -0
  32. package/dist/ear-types.js +2 -0
  33. package/dist/evidence.d.ts +73 -0
  34. package/dist/evidence.js +27 -0
  35. package/dist/evidence.test.d.ts +1 -0
  36. package/dist/evidence.test.js +75 -0
  37. package/dist/experience-emitter.d.ts +21 -0
  38. package/dist/experience-emitter.js +47 -0
  39. package/dist/experience.d.ts +55 -0
  40. package/dist/experience.js +74 -0
  41. package/dist/experience.test.d.ts +1 -0
  42. package/dist/experience.test.js +55 -0
  43. package/dist/gating.d.ts +45 -0
  44. package/dist/gating.js +183 -0
  45. package/dist/gating.test.d.ts +1 -0
  46. package/dist/gating.test.js +186 -0
  47. package/dist/index.d.ts +250 -0
  48. package/dist/index.js +43 -0
  49. package/dist/input-normalizer.d.ts +14 -0
  50. package/dist/input-normalizer.js +58 -0
  51. package/dist/input-normalizer.test.d.ts +1 -0
  52. package/dist/input-normalizer.test.js +39 -0
  53. package/dist/intent-classifier.d.ts +24 -0
  54. package/dist/intent-classifier.js +53 -0
  55. package/dist/intent-classifier.test.d.ts +1 -0
  56. package/dist/intent-classifier.test.js +67 -0
  57. package/dist/memory-settler.d.ts +27 -0
  58. package/dist/memory-settler.js +92 -0
  59. package/dist/mouth-types.d.ts +85 -0
  60. package/dist/mouth-types.js +2 -0
  61. package/dist/perception-cycle.test.d.ts +1 -0
  62. package/dist/perception-cycle.test.js +153 -0
  63. package/dist/prompt-compiler.d.ts +20 -0
  64. package/dist/prompt-compiler.js +54 -0
  65. package/dist/prompt-compiler.test.d.ts +1 -0
  66. package/dist/prompt-compiler.test.js +75 -0
  67. package/dist/proposals.d.ts +29 -0
  68. package/dist/proposals.js +2 -0
  69. package/dist/response-envelope.d.ts +25 -0
  70. package/dist/response-envelope.js +64 -0
  71. package/dist/runtime-facades.test.d.ts +1 -0
  72. package/dist/runtime-facades.test.js +69 -0
  73. package/dist/runtime.d.ts +114 -0
  74. package/dist/runtime.js +412 -0
  75. package/dist/session-history.d.ts +20 -0
  76. package/dist/session-history.js +55 -0
  77. package/dist/session-history.test.d.ts +1 -0
  78. package/dist/session-history.test.js +38 -0
  79. package/dist/sqlite-action-store.d.ts +20 -0
  80. package/dist/sqlite-action-store.js +225 -0
  81. package/dist/sqlite-action-store.test.d.ts +1 -0
  82. package/dist/sqlite-action-store.test.js +251 -0
  83. package/dist/teaching.d.ts +15 -0
  84. package/dist/teaching.js +132 -0
  85. package/package.json +1 -1
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const session_history_1 = require("./session-history");
4
+ describe('SessionHistoryManager', () => {
5
+ test('isolates history across distinct sessions', () => {
6
+ const manager = new session_history_1.SessionHistoryManager();
7
+ manager.append('session-alice', { role: 'user', content: 'Alice message' });
8
+ manager.append('session-bob', { role: 'user', content: 'Bob message' });
9
+ expect(manager.getHistory('session-alice')).toEqual([
10
+ { role: 'user', content: 'Alice message' },
11
+ ]);
12
+ expect(manager.getHistory('session-bob')).toEqual([
13
+ { role: 'user', content: 'Bob message' },
14
+ ]);
15
+ });
16
+ test('bounds messages per session to configured limit and strips null bytes', () => {
17
+ const manager = new session_history_1.SessionHistoryManager({ maxMessagesPerSession: 3 });
18
+ manager.append('sess-1', { role: 'user', content: 'm1\0' });
19
+ manager.append('sess-1', { role: 'assistant', content: 'm2' });
20
+ manager.append('sess-1', { role: 'user', content: 'm3' });
21
+ manager.append('sess-1', { role: 'assistant', content: 'm4' });
22
+ const history = manager.getHistory('sess-1');
23
+ expect(history).toHaveLength(3);
24
+ expect(history[0].content).toBe('m2');
25
+ expect(history[2].content).toBe('m4');
26
+ });
27
+ test('bounds maximum active sessions with LRU eviction', () => {
28
+ const manager = new session_history_1.SessionHistoryManager({ maxSessions: 2 });
29
+ manager.append('sess-1', { role: 'user', content: 'm1' });
30
+ manager.append('sess-2', { role: 'user', content: 'm2' });
31
+ expect(manager.sessionCount()).toBe(2);
32
+ manager.append('sess-3', { role: 'user', content: 'm3' });
33
+ expect(manager.sessionCount()).toBe(2);
34
+ expect(manager.getHistory('sess-1')).toEqual([]);
35
+ expect(manager.getHistory('sess-2')).toHaveLength(1);
36
+ expect(manager.getHistory('sess-3')).toHaveLength(1);
37
+ });
38
+ });
@@ -0,0 +1,20 @@
1
+ import { ActionAuditEvent } from './action';
2
+ import { ActionStore, PersistentExecutionRecord } from './capability';
3
+ export interface SqliteActionStoreOptions {
4
+ dbPath?: string;
5
+ }
6
+ export declare class SqliteActionStore implements ActionStore {
7
+ private db;
8
+ private lastAuditHash;
9
+ constructor(options?: SqliteActionStoreOptions);
10
+ private initSchema;
11
+ private initLastAuditHash;
12
+ reserveExecution(record: PersistentExecutionRecord): Promise<boolean>;
13
+ updateExecution(record: PersistentExecutionRecord): Promise<void>;
14
+ getExecution(executionId: string): Promise<PersistentExecutionRecord | undefined>;
15
+ saveApproval(executionId: string, approverActorId: string, reason?: string): Promise<void>;
16
+ isActionApproved(executionId: string): Promise<boolean>;
17
+ appendAudit(event: ActionAuditEvent): Promise<void>;
18
+ getAuditLog(executionId?: string): Promise<ActionAuditEvent[]>;
19
+ close(): void;
20
+ }
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SqliteActionStore = void 0;
4
+ const capability_1 = require("./capability");
5
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
6
+ const crypto = require('crypto');
7
+ class SqliteActionStore {
8
+ db;
9
+ lastAuditHash = '0000000000000000000000000000000000000000000000000000000000000000';
10
+ constructor(options = {}) {
11
+ const dbPath = options.dbPath ?? ':memory:';
12
+ // Dynamically require node:sqlite (supported natively in Node.js >= 22)
13
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
14
+ const { DatabaseSync } = require('node:sqlite');
15
+ this.db = new DatabaseSync(dbPath);
16
+ this.initSchema();
17
+ this.initLastAuditHash();
18
+ }
19
+ initSchema() {
20
+ this.db.exec(`
21
+ CREATE TABLE IF NOT EXISTS action_executions (
22
+ execution_id TEXT PRIMARY KEY,
23
+ action_id TEXT NOT NULL,
24
+ tool_name TEXT NOT NULL,
25
+ provider_id TEXT NOT NULL,
26
+ parameters_hash TEXT NOT NULL,
27
+ lifecycle TEXT NOT NULL,
28
+ decision_json TEXT,
29
+ result_json TEXT,
30
+ error TEXT,
31
+ created_at TEXT NOT NULL,
32
+ updated_at TEXT NOT NULL
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS action_approvals (
36
+ execution_id TEXT PRIMARY KEY,
37
+ approver_actor_id TEXT NOT NULL,
38
+ reason TEXT,
39
+ approved_at TEXT NOT NULL
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS action_audit_log (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ execution_id TEXT NOT NULL,
45
+ action_id TEXT NOT NULL,
46
+ tool_name TEXT NOT NULL,
47
+ provider_id TEXT,
48
+ companion_id TEXT NOT NULL,
49
+ actor_id TEXT,
50
+ session_id TEXT,
51
+ channel TEXT,
52
+ correlation_id TEXT,
53
+ risk_level TEXT NOT NULL,
54
+ lifecycle TEXT NOT NULL,
55
+ decision_json TEXT,
56
+ parameters_hash TEXT,
57
+ result_hash TEXT,
58
+ event_hash TEXT NOT NULL,
59
+ previous_event_hash TEXT NOT NULL,
60
+ duration_ms REAL,
61
+ error TEXT,
62
+ timestamp TEXT NOT NULL
63
+ );
64
+ `);
65
+ }
66
+ initLastAuditHash() {
67
+ const row = this.db.prepare('SELECT event_hash FROM action_audit_log ORDER BY id DESC LIMIT 1').get();
68
+ if (row && typeof row.event_hash === 'string') {
69
+ this.lastAuditHash = row.event_hash;
70
+ }
71
+ }
72
+ async reserveExecution(record) {
73
+ try {
74
+ const stmt = this.db.prepare(`
75
+ INSERT INTO action_executions (
76
+ execution_id, action_id, tool_name, provider_id, parameters_hash,
77
+ lifecycle, decision_json, result_json, error, created_at, updated_at
78
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79
+ `);
80
+ stmt.run(record.executionId, record.actionId, record.toolName, record.providerId, record.parametersHash, record.lifecycle, record.decision ? JSON.stringify(record.decision) : null, record.result !== undefined ? JSON.stringify(record.result) : null, record.error ?? null, record.createdAt, record.updatedAt);
81
+ return true;
82
+ }
83
+ catch (err) {
84
+ // Primary key constraint violation on execution_id
85
+ if (err.message && (err.message.includes('UNIQUE constraint failed') || err.message.includes('constraint failed'))) {
86
+ return false;
87
+ }
88
+ throw err;
89
+ }
90
+ }
91
+ async updateExecution(record) {
92
+ const stmt = this.db.prepare(`
93
+ INSERT INTO action_executions (
94
+ execution_id, action_id, tool_name, provider_id, parameters_hash,
95
+ lifecycle, decision_json, result_json, error, created_at, updated_at
96
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
97
+ ON CONFLICT(execution_id) DO UPDATE SET
98
+ action_id = excluded.action_id,
99
+ tool_name = excluded.tool_name,
100
+ provider_id = excluded.provider_id,
101
+ parameters_hash = excluded.parameters_hash,
102
+ lifecycle = excluded.lifecycle,
103
+ decision_json = excluded.decision_json,
104
+ result_json = excluded.result_json,
105
+ error = excluded.error,
106
+ updated_at = excluded.updated_at
107
+ `);
108
+ stmt.run(record.executionId, record.actionId, record.toolName, record.providerId, record.parametersHash, record.lifecycle, record.decision ? JSON.stringify(record.decision) : null, record.result !== undefined ? JSON.stringify(record.result) : null, record.error ?? null, record.createdAt, record.updatedAt);
109
+ }
110
+ async getExecution(executionId) {
111
+ const stmt = this.db.prepare('SELECT * FROM action_executions WHERE execution_id = ?');
112
+ const row = stmt.get(executionId);
113
+ if (!row) {
114
+ return undefined;
115
+ }
116
+ return {
117
+ executionId: row.execution_id,
118
+ actionId: row.action_id,
119
+ toolName: row.tool_name,
120
+ providerId: row.provider_id,
121
+ parametersHash: row.parameters_hash,
122
+ lifecycle: row.lifecycle,
123
+ decision: row.decision_json ? JSON.parse(row.decision_json) : undefined,
124
+ result: row.result_json ? JSON.parse(row.result_json) : undefined,
125
+ error: row.error ?? undefined,
126
+ createdAt: row.created_at,
127
+ updatedAt: row.updated_at,
128
+ };
129
+ }
130
+ async saveApproval(executionId, approverActorId, reason) {
131
+ const stmt = this.db.prepare(`
132
+ INSERT INTO action_approvals (execution_id, approver_actor_id, reason, approved_at)
133
+ VALUES (?, ?, ?, ?)
134
+ ON CONFLICT(execution_id) DO UPDATE SET
135
+ approver_actor_id = excluded.approver_actor_id,
136
+ reason = excluded.reason,
137
+ approved_at = excluded.approved_at
138
+ `);
139
+ stmt.run(executionId, approverActorId, reason ?? null, new Date().toISOString());
140
+ }
141
+ async isActionApproved(executionId) {
142
+ const stmt = this.db.prepare('SELECT 1 FROM action_approvals WHERE execution_id = ?');
143
+ const row = stmt.get(executionId);
144
+ return !!row;
145
+ }
146
+ async appendAudit(event) {
147
+ const prevHash = this.lastAuditHash;
148
+ const eventPayload = {
149
+ executionId: event.executionId,
150
+ actionId: event.actionId,
151
+ toolName: event.toolName,
152
+ providerId: event.providerId || null,
153
+ companionId: event.companionId,
154
+ actorId: event.actorId || null,
155
+ sessionId: event.sessionId || null,
156
+ channel: event.channel || null,
157
+ correlationId: event.correlationId || null,
158
+ riskLevel: event.riskLevel,
159
+ lifecycle: event.lifecycle,
160
+ decision: event.decision ? {
161
+ allowed: event.decision.allowed,
162
+ reason: event.decision.reason,
163
+ riskLevel: event.decision.riskLevel,
164
+ decisionCode: event.decision.decisionCode,
165
+ } : null,
166
+ parametersHash: event.parametersHash || null,
167
+ error: event.error || null,
168
+ timestamp: event.timestamp,
169
+ };
170
+ const canonical = (0, capability_1.canonicalizeJson)(eventPayload);
171
+ const eventHash = crypto
172
+ .createHash('sha256')
173
+ .update(`${prevHash}:${canonical}`, 'utf8')
174
+ .digest('hex');
175
+ this.lastAuditHash = eventHash;
176
+ const resultHash = event.resultHash || eventHash;
177
+ const stmt = this.db.prepare(`
178
+ INSERT INTO action_audit_log (
179
+ execution_id, action_id, tool_name, provider_id, companion_id,
180
+ actor_id, session_id, channel, correlation_id, risk_level,
181
+ lifecycle, decision_json, parameters_hash, result_hash, event_hash,
182
+ previous_event_hash, duration_ms, error, timestamp
183
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
184
+ `);
185
+ stmt.run(event.executionId, event.actionId, event.toolName, event.providerId ?? null, event.companionId, event.actorId ?? null, event.sessionId ?? null, event.channel ?? null, event.correlationId ?? null, event.riskLevel, event.lifecycle, event.decision ? JSON.stringify(event.decision) : null, event.parametersHash ?? null, resultHash, eventHash, prevHash, event.durationMs ?? null, event.error ?? null, event.timestamp);
186
+ }
187
+ async getAuditLog(executionId) {
188
+ let rows;
189
+ if (executionId) {
190
+ const stmt = this.db.prepare('SELECT * FROM action_audit_log WHERE execution_id = ? ORDER BY id ASC');
191
+ rows = stmt.all(executionId);
192
+ }
193
+ else {
194
+ const stmt = this.db.prepare('SELECT * FROM action_audit_log ORDER BY id ASC');
195
+ rows = stmt.all();
196
+ }
197
+ return rows.map((row) => ({
198
+ executionId: row.execution_id,
199
+ actionId: row.action_id,
200
+ toolName: row.tool_name,
201
+ providerId: row.provider_id ?? undefined,
202
+ companionId: row.companion_id,
203
+ actorId: row.actor_id ?? undefined,
204
+ sessionId: row.session_id ?? undefined,
205
+ channel: row.channel ?? undefined,
206
+ correlationId: row.correlation_id ?? undefined,
207
+ riskLevel: row.risk_level,
208
+ lifecycle: row.lifecycle,
209
+ decision: row.decision_json ? JSON.parse(row.decision_json) : undefined,
210
+ parametersHash: row.parameters_hash ?? undefined,
211
+ resultHash: row.result_hash ?? undefined,
212
+ eventHash: row.event_hash,
213
+ previousEventHash: row.previous_event_hash,
214
+ durationMs: row.duration_ms ?? undefined,
215
+ error: row.error ?? undefined,
216
+ timestamp: row.timestamp,
217
+ }));
218
+ }
219
+ close() {
220
+ if (this.db) {
221
+ this.db.close();
222
+ }
223
+ }
224
+ }
225
+ exports.SqliteActionStore = SqliteActionStore;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,251 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
37
+ const crypto = require('crypto');
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ const sqlite_action_store_1 = require("./sqlite-action-store");
42
+ const action_policy_1 = require("./action-policy");
43
+ const capability_1 = require("./capability");
44
+ describe('SqliteActionStore Implementation & Durability', () => {
45
+ let tmpDir;
46
+ let dbPath;
47
+ beforeEach(() => {
48
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'siduri-sqlite-test-'));
49
+ dbPath = path.join(tmpDir, 'actions.db');
50
+ });
51
+ afterEach(() => {
52
+ try {
53
+ fs.rmSync(tmpDir, { recursive: true, force: true });
54
+ }
55
+ catch {
56
+ // ignore
57
+ }
58
+ });
59
+ it('reserves execution atomically and prevents duplicate executionId', async () => {
60
+ const store = new sqlite_action_store_1.SqliteActionStore({ dbPath });
61
+ const record = {
62
+ executionId: 'exec-100',
63
+ actionId: 'act-100',
64
+ toolName: 'test_tool',
65
+ providerId: 'builtin',
66
+ parametersHash: 'hash-100',
67
+ lifecycle: 'EXECUTING',
68
+ createdAt: new Date().toISOString(),
69
+ updatedAt: new Date().toISOString(),
70
+ };
71
+ const first = await store.reserveExecution(record);
72
+ expect(first).toBe(true);
73
+ // Second reservation with same executionId fails
74
+ const second = await store.reserveExecution({
75
+ ...record,
76
+ actionId: 'act-duplicate',
77
+ });
78
+ expect(second).toBe(false);
79
+ // Fetch and verify contents
80
+ const fetched = await store.getExecution('exec-100');
81
+ expect(fetched).toBeDefined();
82
+ expect(fetched?.executionId).toBe('exec-100');
83
+ expect(fetched?.toolName).toBe('test_tool');
84
+ // Update execution
85
+ await store.updateExecution({
86
+ ...record,
87
+ lifecycle: 'COMPLETED',
88
+ result: { success: true, count: 42 },
89
+ });
90
+ const updated = await store.getExecution('exec-100');
91
+ expect(updated?.lifecycle).toBe('COMPLETED');
92
+ expect(updated?.result).toEqual({ success: true, count: 42 });
93
+ store.close();
94
+ });
95
+ it('persists approvals and survives process restart with a fresh instance', async () => {
96
+ // Process 1: save approval in instance 1
97
+ const store1 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
98
+ await store1.saveApproval('exec-restart-test', 'operator-alice', 'Routine maintenance');
99
+ expect(await store1.isActionApproved('exec-restart-test')).toBe(true);
100
+ expect(await store1.isActionApproved('non-existent')).toBe(false);
101
+ store1.close();
102
+ // Process 2: open new instance on the same file (simulating engine reboot)
103
+ const store2 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
104
+ const isApprovedAfterReboot = await store2.isActionApproved('exec-restart-test');
105
+ expect(isApprovedAfterReboot).toBe(true);
106
+ expect(await store2.isActionApproved('non-existent')).toBe(false);
107
+ store2.close();
108
+ });
109
+ it('maintains tamper-evident SHA-256 hash chaining in SQLite audit log', async () => {
110
+ const store = new sqlite_action_store_1.SqliteActionStore({ dbPath });
111
+ const event1 = {
112
+ executionId: 'exec-audit-1',
113
+ actionId: 'act-audit-1',
114
+ toolName: 'system/disk_clean',
115
+ providerId: 'system',
116
+ companionId: 'comp-1',
117
+ actorId: 'user-1',
118
+ riskLevel: 'LOW',
119
+ lifecycle: 'POLICY_CHECKED',
120
+ timestamp: '2026-09-09T10:00:00.000Z',
121
+ };
122
+ const event2 = {
123
+ executionId: 'exec-audit-2',
124
+ actionId: 'act-audit-2',
125
+ toolName: 'system/disk_clean',
126
+ providerId: 'system',
127
+ companionId: 'comp-1',
128
+ actorId: 'user-1',
129
+ riskLevel: 'LOW',
130
+ lifecycle: 'COMPLETED',
131
+ timestamp: '2026-09-09T10:00:05.000Z',
132
+ };
133
+ await store.appendAudit(event1);
134
+ await store.appendAudit(event2);
135
+ const log = await store.getAuditLog();
136
+ expect(log).toHaveLength(2);
137
+ const initialPrevHash = '0000000000000000000000000000000000000000000000000000000000000000';
138
+ expect(log[0].previousEventHash).toBe(initialPrevHash);
139
+ expect(log[1].previousEventHash).toBe(log[0].eventHash);
140
+ // Verify hash computation on event 1
141
+ const canonical1 = (0, capability_1.canonicalizeJson)({
142
+ executionId: event1.executionId,
143
+ actionId: event1.actionId,
144
+ toolName: event1.toolName,
145
+ providerId: event1.providerId,
146
+ companionId: event1.companionId,
147
+ actorId: event1.actorId,
148
+ sessionId: null,
149
+ channel: null,
150
+ correlationId: null,
151
+ riskLevel: event1.riskLevel,
152
+ lifecycle: event1.lifecycle,
153
+ decision: null,
154
+ parametersHash: null,
155
+ error: null,
156
+ timestamp: event1.timestamp,
157
+ });
158
+ const expectedHash1 = crypto.createHash('sha256').update(`${initialPrevHash}:${canonical1}`, 'utf8').digest('hex');
159
+ expect(log[0].eventHash).toBe(expectedHash1);
160
+ store.close();
161
+ // Reopen in a new instance and verify audit chain resumes seamlessly
162
+ const storeReopened = new sqlite_action_store_1.SqliteActionStore({ dbPath });
163
+ const event3 = {
164
+ executionId: 'exec-audit-3',
165
+ actionId: 'act-audit-3',
166
+ toolName: 'system/disk_clean',
167
+ providerId: 'system',
168
+ companionId: 'comp-1',
169
+ actorId: 'user-1',
170
+ riskLevel: 'LOW',
171
+ lifecycle: 'COMPLETED',
172
+ timestamp: '2026-09-09T10:00:10.000Z',
173
+ };
174
+ await storeReopened.appendAudit(event3);
175
+ const logReopened = await storeReopened.getAuditLog();
176
+ expect(logReopened).toHaveLength(3);
177
+ expect(logReopened[2].previousEventHash).toBe(logReopened[1].eventHash);
178
+ // Query specific executionId
179
+ const filtered = await storeReopened.getAuditLog('exec-audit-2');
180
+ expect(filtered).toHaveLength(1);
181
+ expect(filtered[0].executionId).toBe('exec-audit-2');
182
+ storeReopened.close();
183
+ });
184
+ it('works seamlessly with ActionPolicyEngine across simulated restarts', async () => {
185
+ const secretKey = 'test_sqlite_policy_key';
186
+ const store1 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
187
+ const engine1 = new action_policy_1.ActionPolicyEngine({
188
+ store: store1,
189
+ secretKey,
190
+ defaultRiskLevel: 'HIGH',
191
+ defaultRequireApprovalForHighRisk: true,
192
+ });
193
+ const dangerousTool = {
194
+ name: 'cleanup_disk',
195
+ providerId: 'system',
196
+ description: 'Clean up disk',
197
+ inputSchema: {},
198
+ riskLevel: 'HIGH',
199
+ requiresApproval: true,
200
+ };
201
+ engine1.registerToolDefinition(dangerousTool);
202
+ const context = {
203
+ companionId: 'comp-local',
204
+ actor: {
205
+ actorId: 'local-owner',
206
+ sessionId: 'sess-1',
207
+ authorizationRole: 'operator',
208
+ capabilities: ['system:manage'],
209
+ authenticated: true,
210
+ },
211
+ conversation: {
212
+ channel: 'direct',
213
+ correlationId: 'corr-1',
214
+ },
215
+ };
216
+ const action = {
217
+ actionId: 'act-danger-1',
218
+ executionId: 'exec-danger-1',
219
+ toolName: 'system/cleanup_disk',
220
+ parameters: { force: true },
221
+ context,
222
+ };
223
+ // 1. Initial attempt without approval -> rejected
224
+ const eval1 = await engine1.evaluateAction(action);
225
+ expect(eval1.decision.allowed).toBe(false);
226
+ expect(eval1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
227
+ // 2. Owner approves
228
+ await engine1.approveAction({
229
+ executionId: 'exec-danger-1',
230
+ approverActorId: 'local-owner',
231
+ reason: 'Owner confirmed cleanup',
232
+ });
233
+ store1.close();
234
+ // 3. Process restart: engine2 with fresh SqliteActionStore connecting to same db
235
+ const store2 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
236
+ const engine2 = new action_policy_1.ActionPolicyEngine({
237
+ store: store2,
238
+ secretKey,
239
+ defaultRiskLevel: 'HIGH',
240
+ defaultRequireApprovalForHighRisk: true,
241
+ });
242
+ engine2.registerToolDefinition(dangerousTool);
243
+ // 4. Evaluate after restart -> approval is retrieved from SQLite and capability granted
244
+ const eval2 = await engine2.evaluateAction(action);
245
+ expect(eval2.decision.allowed).toBe(true);
246
+ expect(eval2.decision.decisionCode).toBe('ALLOWED_POLICY');
247
+ expect(eval2.capability).toBeDefined();
248
+ expect((0, capability_1.verifyCapabilitySignature)(eval2.capability, secretKey)).toBe(true);
249
+ store2.close();
250
+ });
251
+ });
@@ -0,0 +1,15 @@
1
+ import { RequestContext, MemoryProposal, BehaviorProposal } from './index';
2
+ export interface ExtractedTeaching {
3
+ claims: MemoryProposal[];
4
+ behaviorProposals: BehaviorProposal[];
5
+ }
6
+ /**
7
+ * Deterministically extracts teaching candidates from user messages according to single-owner model.
8
+ *
9
+ * Rules:
10
+ * - Scoped to the requesting actor context (subject: `actor:${actorId}`), NEVER `primary_user`.
11
+ * - Candidates are pending proposals only, never active/approved.
12
+ * - In a single-owner companion, preferences apply across the companion instance without audience partitioning.
13
+ * - Companion identity is isolated.
14
+ */
15
+ export declare function extractDeterministicTeaching(message: string, context?: RequestContext, sourceEventId?: string): ExtractedTeaching;