@sidurijs/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +54 -0
  3. package/dist/action-policy.d.ts +79 -0
  4. package/dist/action-policy.js +439 -0
  5. package/dist/action-policy.test.d.ts +1 -0
  6. package/dist/action-policy.test.js +363 -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 +491 -0
  11. package/dist/architecture-boundary.test.d.ts +1 -0
  12. package/dist/architecture-boundary.test.js +108 -0
  13. package/dist/capability.d.ts +78 -0
  14. package/dist/capability.js +168 -0
  15. package/dist/capability.test.d.ts +1 -0
  16. package/dist/capability.test.js +311 -0
  17. package/dist/chat-contract.d.ts +94 -0
  18. package/dist/chat-contract.js +104 -0
  19. package/dist/cognition-planner.d.ts +15 -0
  20. package/dist/cognition-planner.js +23 -0
  21. package/dist/container.d.ts +74 -0
  22. package/dist/container.js +81 -0
  23. package/dist/context-retriever.d.ts +33 -0
  24. package/dist/context-retriever.js +260 -0
  25. package/dist/context.d.ts +46 -0
  26. package/dist/context.js +85 -0
  27. package/dist/context.test.d.ts +1 -0
  28. package/dist/context.test.js +76 -0
  29. package/dist/conversational-teach.test.d.ts +1 -0
  30. package/dist/conversational-teach.test.js +941 -0
  31. package/dist/database.d.ts +1 -0
  32. package/dist/database.js +17 -0
  33. package/dist/dispatcher.d.ts +14 -0
  34. package/dist/dispatcher.js +40 -0
  35. package/dist/dispatcher.test.d.ts +1 -0
  36. package/dist/dispatcher.test.js +58 -0
  37. package/dist/ear-types.d.ts +33 -0
  38. package/dist/ear-types.js +2 -0
  39. package/dist/evidence.d.ts +76 -0
  40. package/dist/evidence.js +27 -0
  41. package/dist/evidence.test.d.ts +1 -0
  42. package/dist/evidence.test.js +75 -0
  43. package/dist/experience-emitter.d.ts +21 -0
  44. package/dist/experience-emitter.js +47 -0
  45. package/dist/experience.d.ts +55 -0
  46. package/dist/experience.js +74 -0
  47. package/dist/experience.test.d.ts +1 -0
  48. package/dist/experience.test.js +55 -0
  49. package/dist/gating.d.ts +45 -0
  50. package/dist/gating.js +185 -0
  51. package/dist/gating.test.d.ts +1 -0
  52. package/dist/gating.test.js +186 -0
  53. package/dist/index.d.ts +367 -0
  54. package/dist/index.js +56 -0
  55. package/dist/input-normalizer.d.ts +14 -0
  56. package/dist/input-normalizer.js +76 -0
  57. package/dist/input-normalizer.test.d.ts +1 -0
  58. package/dist/input-normalizer.test.js +48 -0
  59. package/dist/intent-classifier.d.ts +37 -0
  60. package/dist/intent-classifier.js +150 -0
  61. package/dist/intent-classifier.test.d.ts +1 -0
  62. package/dist/intent-classifier.test.js +118 -0
  63. package/dist/memory-settler.d.ts +49 -0
  64. package/dist/memory-settler.js +161 -0
  65. package/dist/mouth-types.d.ts +90 -0
  66. package/dist/mouth-types.js +2 -0
  67. package/dist/perception-cycle.test.d.ts +1 -0
  68. package/dist/perception-cycle.test.js +301 -0
  69. package/dist/perception-pipeline.d.ts +77 -0
  70. package/dist/perception-pipeline.js +304 -0
  71. package/dist/perception-pipeline.test.d.ts +1 -0
  72. package/dist/perception-pipeline.test.js +65 -0
  73. package/dist/prompt-compiler.d.ts +26 -0
  74. package/dist/prompt-compiler.js +81 -0
  75. package/dist/prompt-compiler.test.d.ts +1 -0
  76. package/dist/prompt-compiler.test.js +90 -0
  77. package/dist/proposals.d.ts +33 -0
  78. package/dist/proposals.js +2 -0
  79. package/dist/response-envelope.d.ts +30 -0
  80. package/dist/response-envelope.js +90 -0
  81. package/dist/runtime-facades.test.d.ts +1 -0
  82. package/dist/runtime-facades.test.js +95 -0
  83. package/dist/runtime.d.ts +116 -0
  84. package/dist/runtime.js +778 -0
  85. package/dist/schema-validator.d.ts +9 -0
  86. package/dist/schema-validator.js +107 -0
  87. package/dist/schema-validator.test.d.ts +1 -0
  88. package/dist/schema-validator.test.js +124 -0
  89. package/dist/session-history.d.ts +20 -0
  90. package/dist/session-history.js +55 -0
  91. package/dist/session-history.test.d.ts +1 -0
  92. package/dist/session-history.test.js +38 -0
  93. package/dist/siduri-db.d.ts +235 -0
  94. package/dist/siduri-db.js +1405 -0
  95. package/dist/siduri-db.test.d.ts +1 -0
  96. package/dist/siduri-db.test.js +1021 -0
  97. package/dist/sqlite-action-store.d.ts +21 -0
  98. package/dist/sqlite-action-store.js +281 -0
  99. package/dist/sqlite-action-store.test.d.ts +1 -0
  100. package/dist/sqlite-action-store.test.js +261 -0
  101. package/dist/teaching.d.ts +14 -0
  102. package/dist/teaching.js +14 -0
  103. package/package.json +43 -0
@@ -0,0 +1,21 @@
1
+ import { ActionAuditEvent } from './action';
2
+ import { ActionStore, PersistentExecutionRecord, ActionApprovalRecord } 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, approverRole?: string, toolName?: string, parametersHash?: string, companionId?: string, actorId?: string): Promise<void>;
16
+ isActionApproved(executionId: string): Promise<boolean>;
17
+ getApproval(executionId: string): Promise<ActionApprovalRecord | undefined>;
18
+ appendAudit(event: ActionAuditEvent): Promise<void>;
19
+ getAuditLog(executionId?: string): Promise<ActionAuditEvent[]>;
20
+ close(): void;
21
+ }
@@ -0,0 +1,281 @@
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.db.exec('PRAGMA busy_timeout = 5000');
17
+ this.db.exec('PRAGMA journal_mode = WAL');
18
+ this.initSchema();
19
+ this.initLastAuditHash();
20
+ }
21
+ initSchema() {
22
+ this.db.exec(`
23
+ CREATE TABLE IF NOT EXISTS action_executions (
24
+ execution_id TEXT PRIMARY KEY,
25
+ action_id TEXT NOT NULL,
26
+ tool_name TEXT NOT NULL,
27
+ provider_id TEXT NOT NULL,
28
+ parameters_hash TEXT NOT NULL,
29
+ lifecycle TEXT NOT NULL,
30
+ decision_json TEXT,
31
+ result_json TEXT,
32
+ error TEXT,
33
+ created_at TEXT NOT NULL,
34
+ updated_at TEXT NOT NULL
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS action_approvals (
38
+ execution_id TEXT PRIMARY KEY,
39
+ approver_actor_id TEXT NOT NULL,
40
+ reason TEXT,
41
+ approver_role TEXT,
42
+ approved_at TEXT NOT NULL,
43
+ tool_name TEXT,
44
+ parameters_hash TEXT,
45
+ companion_id TEXT,
46
+ actor_id TEXT
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS action_audit_log (
50
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ execution_id TEXT NOT NULL,
52
+ action_id TEXT NOT NULL,
53
+ tool_name TEXT NOT NULL,
54
+ provider_id TEXT,
55
+ companion_id TEXT NOT NULL,
56
+ actor_id TEXT,
57
+ session_id TEXT,
58
+ channel TEXT,
59
+ correlation_id TEXT,
60
+ risk_level TEXT NOT NULL,
61
+ lifecycle TEXT NOT NULL,
62
+ decision_json TEXT,
63
+ parameters_hash TEXT,
64
+ result_hash TEXT,
65
+ event_hash TEXT NOT NULL,
66
+ previous_event_hash TEXT NOT NULL,
67
+ duration_ms REAL,
68
+ error TEXT,
69
+ timestamp TEXT NOT NULL
70
+ );
71
+ `);
72
+ try {
73
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN approver_role TEXT;');
74
+ }
75
+ catch {
76
+ // Column already exists or table freshly created
77
+ }
78
+ try {
79
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN tool_name TEXT;');
80
+ }
81
+ catch { }
82
+ try {
83
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN parameters_hash TEXT;');
84
+ }
85
+ catch { }
86
+ try {
87
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN companion_id TEXT;');
88
+ }
89
+ catch { }
90
+ try {
91
+ this.db.exec('ALTER TABLE action_approvals ADD COLUMN actor_id TEXT;');
92
+ }
93
+ catch { }
94
+ }
95
+ initLastAuditHash() {
96
+ const row = this.db.prepare('SELECT event_hash FROM action_audit_log ORDER BY id DESC LIMIT 1').get();
97
+ if (row && typeof row.event_hash === 'string') {
98
+ this.lastAuditHash = row.event_hash;
99
+ }
100
+ }
101
+ async reserveExecution(record) {
102
+ try {
103
+ const stmt = this.db.prepare(`
104
+ INSERT INTO action_executions (
105
+ execution_id, action_id, tool_name, provider_id, parameters_hash,
106
+ lifecycle, decision_json, result_json, error, created_at, updated_at
107
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
108
+ `);
109
+ 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);
110
+ return true;
111
+ }
112
+ catch (err) {
113
+ // Primary key constraint violation on execution_id
114
+ if (err.message && (err.message.includes('UNIQUE constraint failed') || err.message.includes('constraint failed'))) {
115
+ return false;
116
+ }
117
+ throw err;
118
+ }
119
+ }
120
+ async updateExecution(record) {
121
+ const stmt = this.db.prepare(`
122
+ INSERT INTO action_executions (
123
+ execution_id, action_id, tool_name, provider_id, parameters_hash,
124
+ lifecycle, decision_json, result_json, error, created_at, updated_at
125
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
126
+ ON CONFLICT(execution_id) DO UPDATE SET
127
+ action_id = excluded.action_id,
128
+ tool_name = excluded.tool_name,
129
+ provider_id = excluded.provider_id,
130
+ parameters_hash = excluded.parameters_hash,
131
+ lifecycle = excluded.lifecycle,
132
+ decision_json = excluded.decision_json,
133
+ result_json = excluded.result_json,
134
+ error = excluded.error,
135
+ updated_at = excluded.updated_at
136
+ `);
137
+ 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);
138
+ }
139
+ async getExecution(executionId) {
140
+ const stmt = this.db.prepare('SELECT * FROM action_executions WHERE execution_id = ?');
141
+ const row = stmt.get(executionId);
142
+ if (!row) {
143
+ return undefined;
144
+ }
145
+ return {
146
+ executionId: row.execution_id,
147
+ actionId: row.action_id,
148
+ toolName: row.tool_name,
149
+ providerId: row.provider_id,
150
+ parametersHash: row.parameters_hash,
151
+ lifecycle: row.lifecycle,
152
+ decision: row.decision_json ? JSON.parse(row.decision_json) : undefined,
153
+ result: row.result_json ? JSON.parse(row.result_json) : undefined,
154
+ error: row.error ?? undefined,
155
+ createdAt: row.created_at,
156
+ updatedAt: row.updated_at,
157
+ };
158
+ }
159
+ async saveApproval(executionId, approverActorId, reason, approverRole, toolName, parametersHash, companionId, actorId) {
160
+ const stmt = this.db.prepare(`
161
+ INSERT INTO action_approvals (
162
+ execution_id, approver_actor_id, reason, approver_role, approved_at,
163
+ tool_name, parameters_hash, companion_id, actor_id
164
+ )
165
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
166
+ ON CONFLICT(execution_id) DO UPDATE SET
167
+ approver_actor_id = excluded.approver_actor_id,
168
+ reason = excluded.reason,
169
+ approver_role = excluded.approver_role,
170
+ approved_at = excluded.approved_at,
171
+ tool_name = excluded.tool_name,
172
+ parameters_hash = excluded.parameters_hash,
173
+ companion_id = excluded.companion_id,
174
+ actor_id = excluded.actor_id
175
+ `);
176
+ stmt.run(executionId, approverActorId, reason ?? null, approverRole ?? null, new Date().toISOString(), toolName ?? null, parametersHash ?? null, companionId ?? null, actorId ?? null);
177
+ }
178
+ async isActionApproved(executionId) {
179
+ const stmt = this.db.prepare('SELECT 1 FROM action_approvals WHERE execution_id = ?');
180
+ const row = stmt.get(executionId);
181
+ return !!row;
182
+ }
183
+ async getApproval(executionId) {
184
+ const stmt = this.db.prepare('SELECT * FROM action_approvals WHERE execution_id = ?');
185
+ const row = stmt.get(executionId);
186
+ if (!row) {
187
+ return undefined;
188
+ }
189
+ return {
190
+ executionId: row.execution_id,
191
+ approverActorId: row.approver_actor_id,
192
+ reason: row.reason ?? undefined,
193
+ approverRole: row.approver_role ?? undefined,
194
+ approvedAt: row.approved_at,
195
+ toolName: row.tool_name ?? undefined,
196
+ parametersHash: row.parameters_hash ?? undefined,
197
+ companionId: row.companion_id ?? undefined,
198
+ actorId: row.actor_id ?? undefined,
199
+ };
200
+ }
201
+ async appendAudit(event) {
202
+ const prevHash = this.lastAuditHash;
203
+ const eventPayload = {
204
+ executionId: event.executionId,
205
+ actionId: event.actionId,
206
+ toolName: event.toolName,
207
+ providerId: event.providerId || null,
208
+ companionId: event.companionId,
209
+ actorId: event.actorId || null,
210
+ sessionId: event.sessionId || null,
211
+ channel: event.channel || null,
212
+ correlationId: event.correlationId || null,
213
+ riskLevel: event.riskLevel,
214
+ lifecycle: event.lifecycle,
215
+ decision: event.decision ? {
216
+ allowed: event.decision.allowed,
217
+ reason: event.decision.reason,
218
+ riskLevel: event.decision.riskLevel,
219
+ decisionCode: event.decision.decisionCode,
220
+ } : null,
221
+ parametersHash: event.parametersHash || null,
222
+ resultHash: event.resultHash || null,
223
+ error: event.error || null,
224
+ timestamp: event.timestamp,
225
+ };
226
+ const canonical = (0, capability_1.canonicalizeJson)(eventPayload);
227
+ const eventHash = crypto
228
+ .createHash('sha256')
229
+ .update(`${prevHash}:${canonical}`, 'utf8')
230
+ .digest('hex');
231
+ this.lastAuditHash = eventHash;
232
+ const resultHash = event.resultHash || eventHash;
233
+ const stmt = this.db.prepare(`
234
+ INSERT INTO action_audit_log (
235
+ execution_id, action_id, tool_name, provider_id, companion_id,
236
+ actor_id, session_id, channel, correlation_id, risk_level,
237
+ lifecycle, decision_json, parameters_hash, result_hash, event_hash,
238
+ previous_event_hash, duration_ms, error, timestamp
239
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
240
+ `);
241
+ 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);
242
+ }
243
+ async getAuditLog(executionId) {
244
+ let rows;
245
+ if (executionId) {
246
+ const stmt = this.db.prepare('SELECT * FROM action_audit_log WHERE execution_id = ? ORDER BY id ASC');
247
+ rows = stmt.all(executionId);
248
+ }
249
+ else {
250
+ const stmt = this.db.prepare('SELECT * FROM action_audit_log ORDER BY id ASC');
251
+ rows = stmt.all();
252
+ }
253
+ return rows.map((row) => ({
254
+ executionId: row.execution_id,
255
+ actionId: row.action_id,
256
+ toolName: row.tool_name,
257
+ providerId: row.provider_id ?? undefined,
258
+ companionId: row.companion_id,
259
+ actorId: row.actor_id ?? undefined,
260
+ sessionId: row.session_id ?? undefined,
261
+ channel: row.channel ?? undefined,
262
+ correlationId: row.correlation_id ?? undefined,
263
+ riskLevel: row.risk_level,
264
+ lifecycle: row.lifecycle,
265
+ decision: row.decision_json ? JSON.parse(row.decision_json) : undefined,
266
+ parametersHash: row.parameters_hash ?? undefined,
267
+ resultHash: row.result_hash ?? undefined,
268
+ eventHash: row.event_hash,
269
+ previousEventHash: row.previous_event_hash,
270
+ durationMs: row.duration_ms ?? undefined,
271
+ error: row.error ?? undefined,
272
+ timestamp: row.timestamp,
273
+ }));
274
+ }
275
+ close() {
276
+ if (this.db) {
277
+ this.db.close();
278
+ }
279
+ }
280
+ }
281
+ exports.SqliteActionStore = SqliteActionStore;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,261 @@
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
+ resultHash: null,
156
+ error: null,
157
+ timestamp: event1.timestamp,
158
+ });
159
+ const expectedHash1 = crypto.createHash('sha256').update(`${initialPrevHash}:${canonical1}`, 'utf8').digest('hex');
160
+ expect(log[0].eventHash).toBe(expectedHash1);
161
+ store.close();
162
+ // Reopen in a new instance and verify audit chain resumes seamlessly
163
+ const storeReopened = new sqlite_action_store_1.SqliteActionStore({ dbPath });
164
+ const event3 = {
165
+ executionId: 'exec-audit-3',
166
+ actionId: 'act-audit-3',
167
+ toolName: 'system/disk_clean',
168
+ providerId: 'system',
169
+ companionId: 'comp-1',
170
+ actorId: 'user-1',
171
+ riskLevel: 'LOW',
172
+ lifecycle: 'COMPLETED',
173
+ timestamp: '2026-09-09T10:00:10.000Z',
174
+ };
175
+ await storeReopened.appendAudit(event3);
176
+ const logReopened = await storeReopened.getAuditLog();
177
+ expect(logReopened).toHaveLength(3);
178
+ expect(logReopened[2].previousEventHash).toBe(logReopened[1].eventHash);
179
+ // Query specific executionId
180
+ const filtered = await storeReopened.getAuditLog('exec-audit-2');
181
+ expect(filtered).toHaveLength(1);
182
+ expect(filtered[0].executionId).toBe('exec-audit-2');
183
+ storeReopened.close();
184
+ });
185
+ it('works seamlessly with ActionPolicyEngine across simulated restarts', async () => {
186
+ const secretKey = 'test_sqlite_policy_key';
187
+ const store1 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
188
+ const engine1 = new action_policy_1.ActionPolicyEngine({
189
+ store: store1,
190
+ secretKey,
191
+ defaultRiskLevel: 'HIGH',
192
+ defaultRequireApprovalForHighRisk: true,
193
+ });
194
+ const dangerousTool = {
195
+ name: 'cleanup_disk',
196
+ providerId: 'system',
197
+ description: 'Clean up disk',
198
+ inputSchema: {},
199
+ riskLevel: 'HIGH',
200
+ requiresApproval: true,
201
+ };
202
+ engine1.registerToolDefinition(dangerousTool);
203
+ const context = {
204
+ companionId: 'comp-local',
205
+ actor: {
206
+ actorId: 'local-owner',
207
+ sessionId: 'sess-1',
208
+ authorizationRole: 'operator',
209
+ capabilities: ['system:manage'],
210
+ authenticated: true,
211
+ },
212
+ conversation: {
213
+ channel: 'direct',
214
+ correlationId: 'corr-1',
215
+ },
216
+ };
217
+ const action = {
218
+ actionId: 'act-danger-1',
219
+ executionId: 'exec-danger-1',
220
+ toolName: 'system/cleanup_disk',
221
+ parameters: { force: true },
222
+ context,
223
+ };
224
+ // 1. Initial attempt without approval -> rejected
225
+ const eval1 = await engine1.evaluateAction(action);
226
+ expect(eval1.decision.allowed).toBe(false);
227
+ expect(eval1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
228
+ // 2. Owner approves
229
+ await engine1.approveAction({
230
+ executionId: 'exec-danger-1',
231
+ approverActorId: 'local-owner',
232
+ approverRole: 'owner',
233
+ reason: 'Owner confirmed cleanup',
234
+ });
235
+ store1.close();
236
+ // 3. Process restart: engine2 with fresh SqliteActionStore connecting to same db
237
+ const store2 = new sqlite_action_store_1.SqliteActionStore({ dbPath });
238
+ const engine2 = new action_policy_1.ActionPolicyEngine({
239
+ store: store2,
240
+ secretKey,
241
+ defaultRiskLevel: 'HIGH',
242
+ defaultRequireApprovalForHighRisk: true,
243
+ });
244
+ engine2.registerToolDefinition(dangerousTool);
245
+ // 4. Evaluate after restart -> approval is retrieved from SQLite and capability granted
246
+ const eval2 = await engine2.evaluateAction(action);
247
+ expect(eval2.decision.allowed).toBe(true);
248
+ expect(eval2.decision.decisionCode).toBe('ALLOWED_POLICY');
249
+ expect(eval2.capability).toBeDefined();
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');
259
+ store2.close();
260
+ });
261
+ });
@@ -0,0 +1,14 @@
1
+ import { RequestContext, MemoryProposal, BehaviorProposal } from './index';
2
+ export interface ExtractedTeaching {
3
+ claims: MemoryProposal[];
4
+ behaviorProposals: BehaviorProposal[];
5
+ }
6
+ /**
7
+ * Deterministic teaching extraction placeholder.
8
+ *
9
+ * In Siduri-X, conversational memory and behavioral proposals are generated
10
+ * directly by the Brain LLM via structured cognitive planning (e.g. `submitResponsePlan`).
11
+ * Deterministic regex matching has been removed in favor of pure LLM comprehension,
12
+ * as all candidates are quarantined in 'pending' status until explicitly approved by the user.
13
+ */
14
+ export declare function extractDeterministicTeaching(_message: string, _context?: RequestContext, _sourceEventId?: string): ExtractedTeaching;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractDeterministicTeaching = extractDeterministicTeaching;
4
+ /**
5
+ * Deterministic teaching extraction placeholder.
6
+ *
7
+ * In Siduri-X, conversational memory and behavioral proposals are generated
8
+ * directly by the Brain LLM via structured cognitive planning (e.g. `submitResponsePlan`).
9
+ * Deterministic regex matching has been removed in favor of pure LLM comprehension,
10
+ * as all candidates are quarantined in 'pending' status until explicitly approved by the user.
11
+ */
12
+ function extractDeterministicTeaching(_message, _context, _sourceEventId) {
13
+ return { claims: [], behaviorProposals: [] };
14
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@sidurijs/core",
3
+ "version": "1.0.0",
4
+ "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/vxnus-studio/siduri-x",
9
+ "directory": "packages/core"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "engines": {
20
+ "node": ">=22.16.0"
21
+ },
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "dev": "tsc -w",
34
+ "test": "jest --config jest.config.json"
35
+ },
36
+ "devDependencies": {
37
+ "@types/jest": "^30.0.0",
38
+ "@types/node": "^26.5.1",
39
+ "jest": "^30.5.1",
40
+ "ts-jest": "^29.4.12",
41
+ "typescript": "^5.9.3"
42
+ }
43
+ }