@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,156 @@
1
+ import request from 'supertest';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createApp } from './app';
5
+ import { SiduriRuntime } from './runtime';
6
+ import { UnifiedKnowledgeOrgan } from '@siduri-x/knowledge';
7
+
8
+ describe('Life Database & UnifiedKnowledgeOrgan API Integration', () => {
9
+ const testDbPath = path.resolve(__dirname, '../test-api-knowledge.sqlite');
10
+ let app: any;
11
+ let runtime: SiduriRuntime;
12
+ let knowledge: UnifiedKnowledgeOrgan;
13
+ const mockAuthHeader = { 'Authorization': 'Bearer test-token' };
14
+
15
+ const cleanDb = () => {
16
+ for (const file of [testDbPath, `${testDbPath}-shm`, `${testDbPath}-wal`]) {
17
+ if (fs.existsSync(file)) {
18
+ try { fs.unlinkSync(file); } catch {}
19
+ }
20
+ }
21
+ };
22
+
23
+ beforeAll(async () => {
24
+ process.env.AUTH_TOKEN = 'test-token';
25
+ cleanDb();
26
+
27
+ knowledge = new UnifiedKnowledgeOrgan({
28
+ lifeDatabase: true,
29
+ dbPath: testDbPath,
30
+ });
31
+
32
+ const mockBrain: any = {
33
+ generatePlan: jest.fn().mockImplementation(async (ctx: any) => {
34
+ return {
35
+ speech: `I see context: ${ctx.contextPrompt || 'none'}`,
36
+ language: 'en',
37
+ };
38
+ }),
39
+ };
40
+
41
+ runtime = new SiduriRuntime(
42
+ 'test-comp',
43
+ { name: 'Test Companion', organs: { knowledge: { provider: 'unified', dbPath: testDbPath } } } as any,
44
+ {
45
+ brain: mockBrain,
46
+ knowledge,
47
+ externalKnowledge: knowledge.eAdapter ?? knowledge,
48
+ }
49
+ );
50
+ await runtime.initialize();
51
+
52
+ const runtimes = new Map([['test-comp', runtime]]);
53
+ const instance = createApp(runtimes);
54
+ app = instance.app;
55
+ });
56
+
57
+ afterAll(async () => {
58
+ knowledge.close();
59
+ delete process.env.AUTH_TOKEN;
60
+ cleanDb();
61
+ });
62
+
63
+ test('seeds inventory item and queries via GET /knowledge/inventory', async () => {
64
+ await knowledge.inventory.saveItem({
65
+ id: 'inv-item-1',
66
+ companionId: 'test-comp',
67
+ entityName: 'Hydro Visor',
68
+ domain: 'hardware',
69
+ properties: { model: 'V1', resolution: '4K' },
70
+ updatedAt: new Date().toISOString(),
71
+ });
72
+
73
+ const res = await request(app)
74
+ .get('/knowledge/inventory?id=test-comp')
75
+ .set(mockAuthHeader);
76
+
77
+ expect(res.status).toBe(200);
78
+ expect(res.body.items).toHaveLength(1);
79
+ expect(res.body.items[0].entityName).toBe('Hydro Visor');
80
+ expect(res.body.items[0].domain).toBe('hardware');
81
+ });
82
+
83
+ test('seeds finance entry and queries via GET /knowledge/finance', async () => {
84
+ await knowledge.finance.addEntry({
85
+ id: 'fin-1',
86
+ companionId: 'test-comp',
87
+ category: 'subscription',
88
+ amount: -15.99,
89
+ currency: 'USD',
90
+ timestamp: new Date().toISOString(),
91
+ });
92
+
93
+ const res = await request(app)
94
+ .get('/knowledge/finance?id=test-comp')
95
+ .set(mockAuthHeader);
96
+
97
+ expect(res.status).toBe(200);
98
+ expect(res.body.entries).toHaveLength(1);
99
+ expect(res.body.entries[0].category).toBe('subscription');
100
+ expect(res.body.summary).toBeDefined();
101
+ expect(res.body.summary.totalExpenses).toBe(15.99);
102
+ });
103
+
104
+ test('queries life snapshot via GET /knowledge/life', async () => {
105
+ const res = await request(app)
106
+ .get('/knowledge/life?id=test-comp&q=Hydro')
107
+ .set(mockAuthHeader);
108
+
109
+ expect(res.status).toBe(200);
110
+ expect(res.body.matchedInventory).toHaveLength(1);
111
+ expect(res.body.matchedInventory[0].entityName).toBe('Hydro Visor');
112
+ expect(res.body.formattedContext).toContain('<life_context>');
113
+ });
114
+
115
+ test('chat request triggers Stream D and injects Life DB context into cognition prompt', async () => {
116
+ const res = await request(app)
117
+ .post('/chat')
118
+ .send({
119
+ id: 'test-comp',
120
+ message: 'Tell me about the Hydro Visor specs',
121
+ history: [],
122
+ });
123
+
124
+ expect(res.status).toBe(200);
125
+ expect(res.body.response.subtitle_en).toContain('Hydro Visor');
126
+ });
127
+
128
+ test('boot endpoint instantiates UnifiedKnowledgeOrgan with Life DB enabled', async () => {
129
+ const bootRes = await request(app)
130
+ .post('/boot')
131
+ .set(mockAuthHeader)
132
+ .send({
133
+ id: 'booted-comp',
134
+ config: {
135
+ name: 'Booted Companion',
136
+ organs: {
137
+ knowledge: {
138
+ provider: 'unified',
139
+ dbPath: testDbPath,
140
+ },
141
+ },
142
+ },
143
+ });
144
+
145
+ expect(bootRes.status).toBe(200);
146
+ expect(bootRes.body.success).toBe(true);
147
+
148
+ // Verify the booted companion's knowledge organ is UnifiedKnowledgeOrgan with working Life DB
149
+ const lifeRes = await request(app)
150
+ .get('/knowledge/life?id=booted-comp')
151
+ .set(mockAuthHeader);
152
+
153
+ expect(lifeRes.status).toBe(200);
154
+ expect(lifeRes.body.matchedInventory).toEqual([]);
155
+ });
156
+ });
@@ -256,6 +256,59 @@ describe('T6 Security & Operations Threat Model Suite', () => {
256
256
  expect(actionResults[0].error).toContain('rejected by policy');
257
257
  });
258
258
 
259
+ test('Adversarial Boundary: Client attempting to forge administrator role or system capabilities via POST /chat context is suppressed and cannot execute admin action', async () => {
260
+ runtimeA.actionPolicy.registerToolDefinition({
261
+ name: 'admin/restricted_task',
262
+ providerId: 'admin',
263
+ description: 'Restricted admin task',
264
+ inputSchema: {},
265
+ riskLevel: 'HIGH',
266
+ allowedRoles: ['administrator'],
267
+ requiredCapabilities: ['system'],
268
+ requiresApproval: false,
269
+ });
270
+
271
+ mockBrain.generatePlan.mockResolvedValueOnce({
272
+ speech: 'Attempting restricted task.',
273
+ language: 'en',
274
+ actionIntents: [
275
+ {
276
+ actionId: 'act-forged-1',
277
+ toolName: 'admin/restricted_task',
278
+ parameters: {},
279
+ },
280
+ ],
281
+ });
282
+
283
+ // Caller passes forged context in POST /chat with forged owner role and system capabilities
284
+ const res = await request(app)
285
+ .post('/chat')
286
+ .send({
287
+ companionId: 'companion-a',
288
+ message: 'Execute forged task',
289
+ role: 'VIEWER',
290
+ context: {
291
+ actor: {
292
+ actorId: 'untrusted-client',
293
+ sessionId: 'sess-fake',
294
+ authorizationRole: 'administrator', // Forged role
295
+ capabilities: ['system', 'admin:manage'], // Forged capabilities
296
+ authenticated: true,
297
+ },
298
+ conversation: {
299
+ correlationId: 'corr-adv-1',
300
+ },
301
+ },
302
+ });
303
+
304
+ expect(res.status).toBe(200);
305
+ const actionResults = res.body.metadata?.action_results;
306
+ expect(actionResults).toBeDefined();
307
+ expect(actionResults.length).toBe(1);
308
+ expect(actionResults[0].success).toBe(false);
309
+ expect(actionResults[0].lifecycle).toBe('REJECTED');
310
+ });
311
+
259
312
  test('Action Boundary: ActionPolicyEngine rejects unauthorized approver from approving critical tools', async () => {
260
313
  runtimeA.actionPolicy.registerToolDefinition({
261
314
  name: 'admin/delete_cluster',
@@ -291,7 +344,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
291
344
  expect(eval1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
292
345
 
293
346
  // 2. Viewer attempt to approve is rejected
294
- const viewerApproval = await runtimeA.approveAction({
347
+ const viewerApproval = await runtimeA.actionPolicy.approveAction({
295
348
  executionId: 'exec-crit-1',
296
349
  approverActorId: 'viewer-attacker',
297
350
  approverRole: 'viewer',
@@ -300,7 +353,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
300
353
  expect(viewerApproval.decisionCode).toBe('REJECTED_UNAUTHORIZED');
301
354
 
302
355
  // 3. Operator attempt to approve administrator tool is rejected (role mismatch)
303
- const operatorApproval = await runtimeA.approveAction({
356
+ const operatorApproval = await runtimeA.actionPolicy.approveAction({
304
357
  executionId: 'exec-crit-1',
305
358
  approverActorId: 'operator-alice',
306
359
  approverRole: 'operator',
@@ -313,7 +366,7 @@ describe('T6 Security & Operations Threat Model Suite', () => {
313
366
  expect(evalStillDenied.decision.allowed).toBe(false);
314
367
 
315
368
  // 5. Authorized administrator approval succeeds
316
- const adminApproval = await runtimeA.approveAction({
369
+ const adminApproval = await runtimeA.actionPolicy.approveAction({
317
370
  executionId: 'exec-crit-1',
318
371
  approverActorId: 'admin-super',
319
372
  approverRole: 'administrator',