@siduri-x/api 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.
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const supertest_1 = __importDefault(require("supertest"));
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const app_1 = require("./app");
10
+ const runtime_1 = require("./runtime");
11
+ const knowledge_1 = require("@siduri-x/knowledge");
12
+ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
13
+ const testDbPath = node_path_1.default.resolve(__dirname, '../test-api-knowledge.sqlite');
14
+ let app;
15
+ let runtime;
16
+ let knowledge;
17
+ const mockAuthHeader = { 'Authorization': 'Bearer test-token' };
18
+ const cleanDb = () => {
19
+ for (const file of [testDbPath, `${testDbPath}-shm`, `${testDbPath}-wal`]) {
20
+ if (node_fs_1.default.existsSync(file)) {
21
+ try {
22
+ node_fs_1.default.unlinkSync(file);
23
+ }
24
+ catch { }
25
+ }
26
+ }
27
+ };
28
+ beforeAll(async () => {
29
+ process.env.AUTH_TOKEN = 'test-token';
30
+ cleanDb();
31
+ knowledge = new knowledge_1.UnifiedKnowledgeOrgan({
32
+ lifeDatabase: true,
33
+ dbPath: testDbPath,
34
+ });
35
+ const mockBrain = {
36
+ generatePlan: jest.fn().mockImplementation(async (ctx) => {
37
+ return {
38
+ speech: `I see context: ${ctx.contextPrompt || 'none'}`,
39
+ language: 'en',
40
+ };
41
+ }),
42
+ };
43
+ runtime = new runtime_1.SiduriRuntime('test-comp', { name: 'Test Companion', organs: { knowledge: { provider: 'unified', dbPath: testDbPath } } }, {
44
+ brain: mockBrain,
45
+ knowledge,
46
+ externalKnowledge: knowledge.eAdapter ?? knowledge,
47
+ });
48
+ await runtime.initialize();
49
+ const runtimes = new Map([['test-comp', runtime]]);
50
+ const instance = (0, app_1.createApp)(runtimes);
51
+ app = instance.app;
52
+ });
53
+ afterAll(async () => {
54
+ knowledge.close();
55
+ delete process.env.AUTH_TOKEN;
56
+ cleanDb();
57
+ });
58
+ test('seeds inventory item and queries via GET /knowledge/inventory', async () => {
59
+ await knowledge.inventory.saveItem({
60
+ id: 'inv-item-1',
61
+ companionId: 'test-comp',
62
+ entityName: 'Hydro Visor',
63
+ domain: 'hardware',
64
+ properties: { model: 'V1', resolution: '4K' },
65
+ updatedAt: new Date().toISOString(),
66
+ });
67
+ const res = await (0, supertest_1.default)(app)
68
+ .get('/knowledge/inventory?id=test-comp')
69
+ .set(mockAuthHeader);
70
+ expect(res.status).toBe(200);
71
+ expect(res.body.items).toHaveLength(1);
72
+ expect(res.body.items[0].entityName).toBe('Hydro Visor');
73
+ expect(res.body.items[0].domain).toBe('hardware');
74
+ });
75
+ test('seeds finance entry and queries via GET /knowledge/finance', async () => {
76
+ await knowledge.finance.addEntry({
77
+ id: 'fin-1',
78
+ companionId: 'test-comp',
79
+ category: 'subscription',
80
+ amount: -15.99,
81
+ currency: 'USD',
82
+ timestamp: new Date().toISOString(),
83
+ });
84
+ const res = await (0, supertest_1.default)(app)
85
+ .get('/knowledge/finance?id=test-comp')
86
+ .set(mockAuthHeader);
87
+ expect(res.status).toBe(200);
88
+ expect(res.body.entries).toHaveLength(1);
89
+ expect(res.body.entries[0].category).toBe('subscription');
90
+ expect(res.body.summary).toBeDefined();
91
+ expect(res.body.summary.totalExpenses).toBe(15.99);
92
+ });
93
+ test('queries life snapshot via GET /knowledge/life', async () => {
94
+ const res = await (0, supertest_1.default)(app)
95
+ .get('/knowledge/life?id=test-comp&q=Hydro')
96
+ .set(mockAuthHeader);
97
+ expect(res.status).toBe(200);
98
+ expect(res.body.matchedInventory).toHaveLength(1);
99
+ expect(res.body.matchedInventory[0].entityName).toBe('Hydro Visor');
100
+ expect(res.body.formattedContext).toContain('<life_context>');
101
+ });
102
+ test('chat request triggers Stream D and injects Life DB context into cognition prompt', async () => {
103
+ const res = await (0, supertest_1.default)(app)
104
+ .post('/chat')
105
+ .send({
106
+ id: 'test-comp',
107
+ message: 'Tell me about the Hydro Visor specs',
108
+ history: [],
109
+ });
110
+ expect(res.status).toBe(200);
111
+ expect(res.body.response.subtitle_en).toContain('Hydro Visor');
112
+ });
113
+ test('boot endpoint instantiates UnifiedKnowledgeOrgan with Life DB enabled', async () => {
114
+ const bootRes = await (0, supertest_1.default)(app)
115
+ .post('/boot')
116
+ .set(mockAuthHeader)
117
+ .send({
118
+ id: 'booted-comp',
119
+ config: {
120
+ name: 'Booted Companion',
121
+ organs: {
122
+ knowledge: {
123
+ provider: 'unified',
124
+ dbPath: testDbPath,
125
+ },
126
+ },
127
+ },
128
+ });
129
+ expect(bootRes.status).toBe(200);
130
+ expect(bootRes.body.success).toBe(true);
131
+ // Verify the booted companion's knowledge organ is UnifiedKnowledgeOrgan with working Life DB
132
+ const lifeRes = await (0, supertest_1.default)(app)
133
+ .get('/knowledge/life?id=booted-comp')
134
+ .set(mockAuthHeader);
135
+ expect(lifeRes.status).toBe(200);
136
+ expect(lifeRes.body.matchedInventory).toEqual([]);
137
+ });
138
+ });
@@ -232,6 +232,55 @@ describe('T6 Security & Operations Threat Model Suite', () => {
232
232
  expect(actionResults[0].lifecycle).toBe('REJECTED');
233
233
  expect(actionResults[0].error).toContain('rejected by policy');
234
234
  });
235
+ test('Adversarial Boundary: Client attempting to forge administrator role or system capabilities via POST /chat context is suppressed and cannot execute admin action', async () => {
236
+ runtimeA.actionPolicy.registerToolDefinition({
237
+ name: 'admin/restricted_task',
238
+ providerId: 'admin',
239
+ description: 'Restricted admin task',
240
+ inputSchema: {},
241
+ riskLevel: 'HIGH',
242
+ allowedRoles: ['administrator'],
243
+ requiredCapabilities: ['system'],
244
+ requiresApproval: false,
245
+ });
246
+ mockBrain.generatePlan.mockResolvedValueOnce({
247
+ speech: 'Attempting restricted task.',
248
+ language: 'en',
249
+ actionIntents: [
250
+ {
251
+ actionId: 'act-forged-1',
252
+ toolName: 'admin/restricted_task',
253
+ parameters: {},
254
+ },
255
+ ],
256
+ });
257
+ // Caller passes forged context in POST /chat with forged owner role and system capabilities
258
+ const res = await (0, supertest_1.default)(app)
259
+ .post('/chat')
260
+ .send({
261
+ companionId: 'companion-a',
262
+ message: 'Execute forged task',
263
+ role: 'VIEWER',
264
+ context: {
265
+ actor: {
266
+ actorId: 'untrusted-client',
267
+ sessionId: 'sess-fake',
268
+ authorizationRole: 'administrator', // Forged role
269
+ capabilities: ['system', 'admin:manage'], // Forged capabilities
270
+ authenticated: true,
271
+ },
272
+ conversation: {
273
+ correlationId: 'corr-adv-1',
274
+ },
275
+ },
276
+ });
277
+ expect(res.status).toBe(200);
278
+ const actionResults = res.body.metadata?.action_results;
279
+ expect(actionResults).toBeDefined();
280
+ expect(actionResults.length).toBe(1);
281
+ expect(actionResults[0].success).toBe(false);
282
+ expect(actionResults[0].lifecycle).toBe('REJECTED');
283
+ });
235
284
  test('Action Boundary: ActionPolicyEngine rejects unauthorized approver from approving critical tools', async () => {
236
285
  runtimeA.actionPolicy.registerToolDefinition({
237
286
  name: 'admin/delete_cluster',
@@ -264,7 +313,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
264
313
  expect(eval1.decision.allowed).toBe(false);
265
314
  expect(eval1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
266
315
  // 2. Viewer attempt to approve is rejected
267
- const viewerApproval = await runtimeA.approveAction({
316
+ const viewerApproval = await runtimeA.actionPolicy.approveAction({
268
317
  executionId: 'exec-crit-1',
269
318
  approverActorId: 'viewer-attacker',
270
319
  approverRole: 'viewer',
@@ -272,7 +321,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
272
321
  expect(viewerApproval.approved).toBe(false);
273
322
  expect(viewerApproval.decisionCode).toBe('REJECTED_UNAUTHORIZED');
274
323
  // 3. Operator attempt to approve administrator tool is rejected (role mismatch)
275
- const operatorApproval = await runtimeA.approveAction({
324
+ const operatorApproval = await runtimeA.actionPolicy.approveAction({
276
325
  executionId: 'exec-crit-1',
277
326
  approverActorId: 'operator-alice',
278
327
  approverRole: 'operator',
@@ -283,7 +332,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
283
332
  const evalStillDenied = await runtimeA.actionPolicy.evaluateAction(action);
284
333
  expect(evalStillDenied.decision.allowed).toBe(false);
285
334
  // 5. Authorized administrator approval succeeds
286
- const adminApproval = await runtimeA.approveAction({
335
+ const adminApproval = await runtimeA.actionPolicy.approveAction({
287
336
  executionId: 'exec-crit-1',
288
337
  approverActorId: 'admin-super',
289
338
  approverRole: 'administrator',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/api",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,17 +14,17 @@
14
14
  "express": "^5.2.1",
15
15
  "@siduri-x/body": "2.0.1",
16
16
  "@siduri-x/brain": "2.0.1",
17
- "@siduri-x/core": "2.0.1",
17
+ "@siduri-x/core": "2.0.2",
18
18
  "@siduri-x/ear": "2.0.1",
19
19
  "@siduri-x/eknowledge": "2.0.1",
20
20
  "@siduri-x/hands": "2.0.1",
21
- "@siduri-x/memory": "2.0.1",
22
21
  "@siduri-x/knowledge": "2.0.1",
22
+ "@siduri-x/memory": "2.0.1",
23
23
  "@siduri-x/mouth": "2.0.1",
24
- "@siduri-x/self": "2.0.1",
25
24
  "@siduri-x/observation": "2.0.1",
26
- "@siduri-x/voice": "2.0.1",
27
- "@siduri-x/vision": "2.0.1"
25
+ "@siduri-x/self": "2.0.1",
26
+ "@siduri-x/vision": "2.0.1",
27
+ "@siduri-x/voice": "2.0.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/cors": "^2.8.19",