@siduri-x/core 1.0.4 → 1.0.5
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/package.json +1 -1
- package/dist/action-policy.d.ts +0 -45
- package/dist/action-policy.js +0 -224
- package/dist/action-policy.test.d.ts +0 -1
- package/dist/action-policy.test.js +0 -194
- package/dist/action.d.ts +0 -72
- package/dist/action.js +0 -2
- package/dist/adversarial.test.d.ts +0 -1
- package/dist/adversarial.test.js +0 -493
- package/dist/architecture-boundary.test.d.ts +0 -1
- package/dist/architecture-boundary.test.js +0 -116
- package/dist/capability.d.ts +0 -57
- package/dist/capability.js +0 -130
- package/dist/capability.test.d.ts +0 -1
- package/dist/capability.test.js +0 -269
- package/dist/chat-contract.d.ts +0 -77
- package/dist/chat-contract.js +0 -65
- package/dist/context.d.ts +0 -47
- package/dist/context.js +0 -92
- package/dist/context.test.d.ts +0 -1
- package/dist/context.test.js +0 -109
- package/dist/dispatcher.d.ts +0 -14
- package/dist/dispatcher.js +0 -40
- package/dist/dispatcher.test.d.ts +0 -1
- package/dist/dispatcher.test.js +0 -60
- package/dist/ear-types.d.ts +0 -33
- package/dist/ear-types.js +0 -2
- package/dist/evidence.d.ts +0 -72
- package/dist/evidence.js +0 -45
- package/dist/evidence.test.d.ts +0 -1
- package/dist/evidence.test.js +0 -101
- package/dist/experience.d.ts +0 -56
- package/dist/experience.js +0 -78
- package/dist/experience.test.d.ts +0 -1
- package/dist/experience.test.js +0 -58
- package/dist/gating.d.ts +0 -45
- package/dist/gating.js +0 -189
- package/dist/gating.test.d.ts +0 -1
- package/dist/gating.test.js +0 -190
- package/dist/index.d.ts +0 -266
- package/dist/index.js +0 -29
- package/dist/runtime.d.ts +0 -50
- package/dist/runtime.js +0 -411
- package/dist/teaching.d.ts +0 -15
- package/dist/teaching.js +0 -159
package/package.json
CHANGED
package/dist/action-policy.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { RequestContext } from './context';
|
|
2
|
-
import { ActionIntent, ActionPolicyDecision, ActionAuditEvent, ToolDefinition, ActionRiskLevel, ActionLifecycleState } from './action';
|
|
3
|
-
import { AuthorizationCapability, ActionStore } from './capability';
|
|
4
|
-
export interface ActionPolicyRule {
|
|
5
|
-
toolNamePattern: string | RegExp;
|
|
6
|
-
riskLevel?: ActionRiskLevel;
|
|
7
|
-
requiredCapabilities?: string[];
|
|
8
|
-
allowedRoles?: string[];
|
|
9
|
-
allowedChannels?: string[];
|
|
10
|
-
requiresExplicitApproval?: boolean;
|
|
11
|
-
}
|
|
12
|
-
export interface ActionPolicyEngineOptions {
|
|
13
|
-
rules?: ActionPolicyRule[];
|
|
14
|
-
defaultRiskLevel?: ActionRiskLevel;
|
|
15
|
-
defaultRequireApprovalForHighRisk?: boolean;
|
|
16
|
-
store?: ActionStore;
|
|
17
|
-
secretKey?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface ApproveActionOptions {
|
|
20
|
-
executionId: string;
|
|
21
|
-
approverActorId: string;
|
|
22
|
-
reason?: string;
|
|
23
|
-
}
|
|
24
|
-
export declare class ActionPolicyEngine {
|
|
25
|
-
private readonly toolRegistry;
|
|
26
|
-
private readonly rules;
|
|
27
|
-
private readonly store;
|
|
28
|
-
private readonly defaultRiskLevel;
|
|
29
|
-
private readonly defaultRequireApprovalForHighRisk;
|
|
30
|
-
private readonly secretKey;
|
|
31
|
-
private readonly approvedExecutions;
|
|
32
|
-
constructor(options?: ActionPolicyEngineOptions);
|
|
33
|
-
registerToolDefinition(tool: ToolDefinition): void;
|
|
34
|
-
unregisterToolDefinition(toolName: string): boolean;
|
|
35
|
-
getRegisteredTools(): ToolDefinition[];
|
|
36
|
-
findToolDefinition(toolName: string): ToolDefinition | undefined;
|
|
37
|
-
evaluateAction(action: ActionIntent, context?: RequestContext): Promise<{
|
|
38
|
-
decision: ActionPolicyDecision;
|
|
39
|
-
capability?: AuthorizationCapability;
|
|
40
|
-
}>;
|
|
41
|
-
approveAction(options: ApproveActionOptions): Promise<boolean>;
|
|
42
|
-
recordAudit(action: ActionIntent, context: RequestContext | undefined, decision: ActionPolicyDecision | undefined, lifecycle: ActionLifecycleState, result?: unknown, error?: string, durationMs?: number): Promise<ActionAuditEvent>;
|
|
43
|
-
getAuditLog(): Promise<ActionAuditEvent[]>;
|
|
44
|
-
getStore(): ActionStore;
|
|
45
|
-
}
|
package/dist/action-policy.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ActionPolicyEngine = void 0;
|
|
4
|
-
const capability_1 = require("./capability");
|
|
5
|
-
class ActionPolicyEngine {
|
|
6
|
-
toolRegistry = new Map();
|
|
7
|
-
rules = [];
|
|
8
|
-
store;
|
|
9
|
-
defaultRiskLevel;
|
|
10
|
-
defaultRequireApprovalForHighRisk;
|
|
11
|
-
secretKey;
|
|
12
|
-
approvedExecutions = new Set();
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this.rules = options.rules ?? [];
|
|
15
|
-
this.defaultRiskLevel = options.defaultRiskLevel ?? 'HIGH';
|
|
16
|
-
this.defaultRequireApprovalForHighRisk = options.defaultRequireApprovalForHighRisk ?? true;
|
|
17
|
-
this.store = options.store ?? new capability_1.InMemoryActionStore();
|
|
18
|
-
const envSecret = typeof process !== 'undefined' && process.env ? process.env.ACTION_POLICY_SECRET : undefined;
|
|
19
|
-
const providedSecret = options.secretKey || envSecret;
|
|
20
|
-
if (!providedSecret && typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
21
|
-
throw new Error('FATAL: ACTION_POLICY_SECRET is required in production environment');
|
|
22
|
-
}
|
|
23
|
-
this.secretKey = providedSecret ?? 'siduri_y_action_policy_secret';
|
|
24
|
-
}
|
|
25
|
-
registerToolDefinition(tool) {
|
|
26
|
-
const key = tool.providerId ? `${tool.providerId}/${tool.name}` : tool.name;
|
|
27
|
-
this.toolRegistry.set(key, tool);
|
|
28
|
-
// Also index by bare name if unique
|
|
29
|
-
if (!this.toolRegistry.has(tool.name)) {
|
|
30
|
-
this.toolRegistry.set(tool.name, tool);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
unregisterToolDefinition(toolName) {
|
|
34
|
-
return this.toolRegistry.delete(toolName);
|
|
35
|
-
}
|
|
36
|
-
getRegisteredTools() {
|
|
37
|
-
const set = new Set(this.toolRegistry.values());
|
|
38
|
-
return Array.from(set);
|
|
39
|
-
}
|
|
40
|
-
findToolDefinition(toolName) {
|
|
41
|
-
return this.toolRegistry.get(toolName);
|
|
42
|
-
}
|
|
43
|
-
async evaluateAction(action, context) {
|
|
44
|
-
const effectiveContext = action.context || context;
|
|
45
|
-
const executionId = action.executionId || `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
46
|
-
action.executionId = executionId;
|
|
47
|
-
const toolDef = this.findToolDefinition(action.toolName);
|
|
48
|
-
const paramsHash = (0, capability_1.computeParametersHash)(action.parameters);
|
|
49
|
-
// Rule: Unknown tools/actions must not be implicitly authorized
|
|
50
|
-
if (!toolDef) {
|
|
51
|
-
const decision = {
|
|
52
|
-
allowed: false,
|
|
53
|
-
reason: `Tool "${action.toolName}" is not registered or known in action policy`,
|
|
54
|
-
riskLevel: 'CRITICAL',
|
|
55
|
-
executionId,
|
|
56
|
-
decisionCode: 'REJECTED_UNKNOWN_TOOL',
|
|
57
|
-
};
|
|
58
|
-
await this.recordAudit(action, effectiveContext, decision, 'REJECTED');
|
|
59
|
-
return { decision };
|
|
60
|
-
}
|
|
61
|
-
const riskLevel = toolDef.riskLevel || this.defaultRiskLevel;
|
|
62
|
-
const requiredCaps = toolDef.requiredCapabilities || [];
|
|
63
|
-
// Check request context presence
|
|
64
|
-
if (!effectiveContext) {
|
|
65
|
-
const decision = {
|
|
66
|
-
allowed: false,
|
|
67
|
-
reason: 'Missing RequestContext: Actions cannot be authorized without request context',
|
|
68
|
-
riskLevel,
|
|
69
|
-
requiredCapabilities: requiredCaps,
|
|
70
|
-
executionId,
|
|
71
|
-
decisionCode: 'REJECTED_UNAUTHORIZED',
|
|
72
|
-
};
|
|
73
|
-
await this.recordAudit(action, undefined, decision, 'REJECTED');
|
|
74
|
-
return { decision };
|
|
75
|
-
}
|
|
76
|
-
// Role check if tool restricts roles (supports administrator/owner role parity)
|
|
77
|
-
if (toolDef.allowedRoles && toolDef.allowedRoles.length > 0) {
|
|
78
|
-
const actorRole = effectiveContext.actor.authorizationRole;
|
|
79
|
-
const normalizedActorRoles = new Set([actorRole.toLowerCase()]);
|
|
80
|
-
if (actorRole === 'administrator' || actorRole === 'owner') {
|
|
81
|
-
normalizedActorRoles.add('administrator');
|
|
82
|
-
normalizedActorRoles.add('owner');
|
|
83
|
-
normalizedActorRoles.add('admin');
|
|
84
|
-
}
|
|
85
|
-
const isAuthorizedRole = toolDef.allowedRoles.some((r) => normalizedActorRoles.has(r.toLowerCase()));
|
|
86
|
-
if (!isAuthorizedRole) {
|
|
87
|
-
const decision = {
|
|
88
|
-
allowed: false,
|
|
89
|
-
reason: `Actor role "${actorRole}" is not authorized to execute tool "${action.toolName}"`,
|
|
90
|
-
riskLevel,
|
|
91
|
-
requiredCapabilities: requiredCaps,
|
|
92
|
-
executionId,
|
|
93
|
-
decisionCode: 'REJECTED_UNAUTHORIZED',
|
|
94
|
-
};
|
|
95
|
-
await this.recordAudit(action, effectiveContext, decision, 'REJECTED');
|
|
96
|
-
return { decision };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// Channel check if tool restricts channels
|
|
100
|
-
if (toolDef.allowedChannels && toolDef.allowedChannels.length > 0) {
|
|
101
|
-
const channel = effectiveContext.conversation.channel;
|
|
102
|
-
if (!toolDef.allowedChannels.includes(channel)) {
|
|
103
|
-
const decision = {
|
|
104
|
-
allowed: false,
|
|
105
|
-
reason: `Tool "${action.toolName}" cannot be executed in channel "${channel}"`,
|
|
106
|
-
riskLevel,
|
|
107
|
-
requiredCapabilities: requiredCaps,
|
|
108
|
-
executionId,
|
|
109
|
-
decisionCode: 'REJECTED_CHANNEL_RESTRICTED',
|
|
110
|
-
};
|
|
111
|
-
await this.recordAudit(action, effectiveContext, decision, 'REJECTED');
|
|
112
|
-
return { decision };
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Capability check
|
|
116
|
-
const actorCaps = effectiveContext.actor.capabilities || [];
|
|
117
|
-
const missingCaps = requiredCaps.filter((cap) => !actorCaps.includes(cap));
|
|
118
|
-
if (missingCaps.length > 0) {
|
|
119
|
-
const decision = {
|
|
120
|
-
allowed: false,
|
|
121
|
-
reason: `Actor is missing required capabilities: [${missingCaps.join(', ')}]`,
|
|
122
|
-
riskLevel,
|
|
123
|
-
requiredCapabilities: requiredCaps,
|
|
124
|
-
executionId,
|
|
125
|
-
decisionCode: 'REJECTED_MISSING_CAPABILITY',
|
|
126
|
-
};
|
|
127
|
-
await this.recordAudit(action, effectiveContext, decision, 'REJECTED');
|
|
128
|
-
return { decision };
|
|
129
|
-
}
|
|
130
|
-
// Risk level approval check
|
|
131
|
-
const requiresExplicitApproval = toolDef.requiresApproval ??
|
|
132
|
-
(this.defaultRequireApprovalForHighRisk && (riskLevel === 'HIGH' || riskLevel === 'CRITICAL'));
|
|
133
|
-
let isApproved = this.approvedExecutions.has(executionId);
|
|
134
|
-
if (!isApproved && typeof this.store.isActionApproved === 'function') {
|
|
135
|
-
isApproved = await this.store.isActionApproved(executionId);
|
|
136
|
-
if (isApproved) {
|
|
137
|
-
this.approvedExecutions.add(executionId);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (requiresExplicitApproval && !isApproved) {
|
|
141
|
-
const decision = {
|
|
142
|
-
allowed: false,
|
|
143
|
-
reason: `Action "${action.toolName}" has risk level ${riskLevel} and requires explicit approval`,
|
|
144
|
-
riskLevel,
|
|
145
|
-
requiredCapabilities: requiredCaps,
|
|
146
|
-
executionId,
|
|
147
|
-
decisionCode: 'REJECTED_HIGH_RISK_UNAPPROVED',
|
|
148
|
-
};
|
|
149
|
-
await this.recordAudit(action, effectiveContext, decision, 'POLICY_CHECKED');
|
|
150
|
-
return { decision };
|
|
151
|
-
}
|
|
152
|
-
const decision = {
|
|
153
|
-
allowed: true,
|
|
154
|
-
reason: 'Action authorized by policy',
|
|
155
|
-
riskLevel,
|
|
156
|
-
requiredCapabilities: requiredCaps,
|
|
157
|
-
executionId,
|
|
158
|
-
decisionCode: 'ALLOWED_POLICY',
|
|
159
|
-
};
|
|
160
|
-
// Issue cryptographic/structural AuthorizationCapability
|
|
161
|
-
const issuedAt = new Date().toISOString();
|
|
162
|
-
const expiresAt = new Date(Date.now() + 60_000).toISOString();
|
|
163
|
-
const providerId = toolDef.providerId || 'builtin';
|
|
164
|
-
const capabilityPayload = {
|
|
165
|
-
executionId,
|
|
166
|
-
actionId: action.actionId,
|
|
167
|
-
toolName: action.toolName,
|
|
168
|
-
providerId,
|
|
169
|
-
parametersHash: paramsHash,
|
|
170
|
-
companionId: effectiveContext.companionId,
|
|
171
|
-
actorId: effectiveContext.actor.actorId,
|
|
172
|
-
sessionId: effectiveContext.actor.sessionId,
|
|
173
|
-
channel: effectiveContext.conversation.channel,
|
|
174
|
-
correlationId: effectiveContext.conversation.correlationId,
|
|
175
|
-
riskLevel,
|
|
176
|
-
issuedAt,
|
|
177
|
-
expiresAt,
|
|
178
|
-
};
|
|
179
|
-
const signature = (0, capability_1.signCapabilityPayload)(capabilityPayload, this.secretKey);
|
|
180
|
-
const capability = {
|
|
181
|
-
...capabilityPayload,
|
|
182
|
-
allowed: true,
|
|
183
|
-
signature,
|
|
184
|
-
};
|
|
185
|
-
await this.recordAudit(action, effectiveContext, decision, 'APPROVED');
|
|
186
|
-
return { decision, capability };
|
|
187
|
-
}
|
|
188
|
-
async approveAction(options) {
|
|
189
|
-
this.approvedExecutions.add(options.executionId);
|
|
190
|
-
if (typeof this.store.saveApproval === 'function') {
|
|
191
|
-
await this.store.saveApproval(options.executionId, options.approverActorId, options.reason);
|
|
192
|
-
}
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
async recordAudit(action, context, decision, lifecycle, result, error, durationMs) {
|
|
196
|
-
const event = {
|
|
197
|
-
executionId: action.executionId || decision?.executionId || 'unknown',
|
|
198
|
-
actionId: action.actionId,
|
|
199
|
-
toolName: action.toolName,
|
|
200
|
-
companionId: context?.companionId || 'unknown',
|
|
201
|
-
actorId: context?.actor.actorId,
|
|
202
|
-
sessionId: context?.actor.sessionId,
|
|
203
|
-
channel: context?.conversation.channel,
|
|
204
|
-
correlationId: context?.conversation.correlationId,
|
|
205
|
-
riskLevel: decision?.riskLevel || 'LOW',
|
|
206
|
-
lifecycle,
|
|
207
|
-
decision,
|
|
208
|
-
parametersHash: (0, capability_1.computeParametersHash)(action.parameters),
|
|
209
|
-
resultHash: result !== undefined ? (0, capability_1.computeParametersHash)(result) : undefined,
|
|
210
|
-
timestamp: new Date().toISOString(),
|
|
211
|
-
durationMs,
|
|
212
|
-
error,
|
|
213
|
-
};
|
|
214
|
-
await this.store.appendAudit(event);
|
|
215
|
-
return event;
|
|
216
|
-
}
|
|
217
|
-
async getAuditLog() {
|
|
218
|
-
return this.store.getAuditLog();
|
|
219
|
-
}
|
|
220
|
-
getStore() {
|
|
221
|
-
return this.store;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
exports.ActionPolicyEngine = ActionPolicyEngine;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const action_policy_1 = require("./action-policy");
|
|
4
|
-
const capability_1 = require("./capability");
|
|
5
|
-
describe('ActionPolicyEngine Boundary', () => {
|
|
6
|
-
let engine;
|
|
7
|
-
let sampleContext;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
engine = new action_policy_1.ActionPolicyEngine({
|
|
10
|
-
defaultRiskLevel: 'HIGH',
|
|
11
|
-
defaultRequireApprovalForHighRisk: true,
|
|
12
|
-
secretKey: 'test_secret_key',
|
|
13
|
-
});
|
|
14
|
-
sampleContext = {
|
|
15
|
-
companionId: 'companion-1',
|
|
16
|
-
actor: {
|
|
17
|
-
actorId: 'user-1',
|
|
18
|
-
sessionId: 'sess-1',
|
|
19
|
-
authorizationRole: 'operator',
|
|
20
|
-
capabilities: ['web:search', 'calc:basic'],
|
|
21
|
-
authenticated: true,
|
|
22
|
-
},
|
|
23
|
-
conversation: {
|
|
24
|
-
channel: 'direct',
|
|
25
|
-
audienceId: 'audience-direct',
|
|
26
|
-
correlationId: 'corr-1',
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
const searchTool = {
|
|
30
|
-
name: 'search_web',
|
|
31
|
-
providerId: 'builtin',
|
|
32
|
-
description: 'Search the web',
|
|
33
|
-
inputSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
|
|
34
|
-
riskLevel: 'LOW',
|
|
35
|
-
requiredCapabilities: ['web:search'],
|
|
36
|
-
allowedChannels: ['direct', 'public', 'private'],
|
|
37
|
-
};
|
|
38
|
-
const deleteTool = {
|
|
39
|
-
name: 'delete_database',
|
|
40
|
-
providerId: 'admin',
|
|
41
|
-
description: 'Delete entire database',
|
|
42
|
-
inputSchema: { type: 'object' },
|
|
43
|
-
riskLevel: 'CRITICAL',
|
|
44
|
-
requiredCapabilities: ['admin:delete'],
|
|
45
|
-
allowedRoles: ['administrator'],
|
|
46
|
-
requiresApproval: true,
|
|
47
|
-
};
|
|
48
|
-
engine.registerToolDefinition(searchTool);
|
|
49
|
-
engine.registerToolDefinition(deleteTool);
|
|
50
|
-
});
|
|
51
|
-
it('approves an authorized low-risk action and issues a signed AuthorizationCapability', async () => {
|
|
52
|
-
const action = {
|
|
53
|
-
actionId: 'act-1',
|
|
54
|
-
toolName: 'builtin/search_web',
|
|
55
|
-
parameters: { query: 'Antigravity' },
|
|
56
|
-
context: sampleContext,
|
|
57
|
-
};
|
|
58
|
-
const { decision, capability } = await engine.evaluateAction(action);
|
|
59
|
-
expect(decision.allowed).toBe(true);
|
|
60
|
-
expect(decision.decisionCode).toBe('ALLOWED_POLICY');
|
|
61
|
-
expect(decision.riskLevel).toBe('LOW');
|
|
62
|
-
expect(capability).toBeDefined();
|
|
63
|
-
expect(capability?.allowed).toBe(true);
|
|
64
|
-
expect(capability?.toolName).toBe('builtin/search_web');
|
|
65
|
-
expect(capability?.actionId).toBe('act-1');
|
|
66
|
-
expect(capability?.signature).toBeDefined();
|
|
67
|
-
expect((0, capability_1.verifyCapabilitySignature)(capability, 'test_secret_key')).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
it('rejects an unknown tool/action by default and issues no capability', async () => {
|
|
70
|
-
const action = {
|
|
71
|
-
actionId: 'act-2',
|
|
72
|
-
toolName: 'unregistered_tool',
|
|
73
|
-
parameters: {},
|
|
74
|
-
context: sampleContext,
|
|
75
|
-
};
|
|
76
|
-
const { decision, capability } = await engine.evaluateAction(action);
|
|
77
|
-
expect(decision.allowed).toBe(false);
|
|
78
|
-
expect(decision.decisionCode).toBe('REJECTED_UNKNOWN_TOOL');
|
|
79
|
-
expect(decision.riskLevel).toBe('CRITICAL');
|
|
80
|
-
expect(capability).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
it('rejects action if request context is missing', async () => {
|
|
83
|
-
const action = {
|
|
84
|
-
actionId: 'act-3',
|
|
85
|
-
toolName: 'builtin/search_web',
|
|
86
|
-
parameters: { query: 'test' },
|
|
87
|
-
// No context provided
|
|
88
|
-
};
|
|
89
|
-
const { decision, capability } = await engine.evaluateAction(action);
|
|
90
|
-
expect(decision.allowed).toBe(false);
|
|
91
|
-
expect(decision.decisionCode).toBe('REJECTED_UNAUTHORIZED');
|
|
92
|
-
expect(decision.reason).toContain('Missing RequestContext');
|
|
93
|
-
expect(capability).toBeUndefined();
|
|
94
|
-
});
|
|
95
|
-
it('rejects action when actor lacks required capability', async () => {
|
|
96
|
-
const action = {
|
|
97
|
-
actionId: 'act-4',
|
|
98
|
-
toolName: 'admin/delete_database',
|
|
99
|
-
parameters: {},
|
|
100
|
-
context: sampleContext, // has 'web:search' and 'calc:basic', not 'admin:delete'
|
|
101
|
-
};
|
|
102
|
-
const { decision, capability } = await engine.evaluateAction(action);
|
|
103
|
-
expect(decision.allowed).toBe(false);
|
|
104
|
-
expect(decision.decisionCode).toBe('REJECTED_UNAUTHORIZED'); // operator is not administrator
|
|
105
|
-
expect(capability).toBeUndefined();
|
|
106
|
-
});
|
|
107
|
-
it('rejects high/critical risk action requiring explicit approval before approval is granted, and allows after approval', async () => {
|
|
108
|
-
const adminContext = {
|
|
109
|
-
...sampleContext,
|
|
110
|
-
actor: {
|
|
111
|
-
...sampleContext.actor,
|
|
112
|
-
authorizationRole: 'administrator',
|
|
113
|
-
capabilities: ['admin:delete'],
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
const action = {
|
|
117
|
-
actionId: 'act-5',
|
|
118
|
-
toolName: 'admin/delete_database',
|
|
119
|
-
parameters: {},
|
|
120
|
-
context: adminContext,
|
|
121
|
-
executionId: 'exec-admin-delete-1',
|
|
122
|
-
};
|
|
123
|
-
// First attempt: unapproved
|
|
124
|
-
const res1 = await engine.evaluateAction(action);
|
|
125
|
-
expect(res1.decision.allowed).toBe(false);
|
|
126
|
-
expect(res1.decision.decisionCode).toBe('REJECTED_HIGH_RISK_UNAPPROVED');
|
|
127
|
-
expect(res1.capability).toBeUndefined();
|
|
128
|
-
// Grant explicit approval
|
|
129
|
-
engine.approveAction({
|
|
130
|
-
executionId: 'exec-admin-delete-1',
|
|
131
|
-
approverActorId: 'admin-super-user',
|
|
132
|
-
});
|
|
133
|
-
// Second attempt: approved
|
|
134
|
-
const res2 = await engine.evaluateAction(action);
|
|
135
|
-
expect(res2.decision.allowed).toBe(true);
|
|
136
|
-
expect(res2.decision.decisionCode).toBe('ALLOWED_POLICY');
|
|
137
|
-
expect(res2.capability).toBeDefined();
|
|
138
|
-
expect((0, capability_1.verifyCapabilitySignature)(res2.capability, 'test_secret_key')).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
it('records structured tamper-evident audit log entries with hash chaining', async () => {
|
|
141
|
-
const action = {
|
|
142
|
-
actionId: 'act-audit',
|
|
143
|
-
toolName: 'builtin/search_web',
|
|
144
|
-
parameters: { query: 'Audit Test' },
|
|
145
|
-
context: sampleContext,
|
|
146
|
-
};
|
|
147
|
-
await engine.evaluateAction(action);
|
|
148
|
-
const auditLogs = await engine.getAuditLog();
|
|
149
|
-
expect(auditLogs.length).toBeGreaterThan(0);
|
|
150
|
-
const log = auditLogs.find((l) => l.actionId === 'act-audit');
|
|
151
|
-
expect(log).toBeDefined();
|
|
152
|
-
expect(log?.actorId).toBe('user-1');
|
|
153
|
-
expect(log?.toolName).toBe('builtin/search_web');
|
|
154
|
-
expect(log?.parametersHash).toBeDefined();
|
|
155
|
-
expect(log?.resultHash).toBeDefined();
|
|
156
|
-
});
|
|
157
|
-
it('supports single-owner role parity between owner and administrator', async () => {
|
|
158
|
-
const ownerTool = {
|
|
159
|
-
name: 'builtin/owner_tool',
|
|
160
|
-
description: 'Owner only tool',
|
|
161
|
-
inputSchema: {},
|
|
162
|
-
riskLevel: 'LOW',
|
|
163
|
-
allowedRoles: ['owner'],
|
|
164
|
-
};
|
|
165
|
-
engine.registerToolDefinition(ownerTool);
|
|
166
|
-
// Context with authorizationRole: 'administrator'
|
|
167
|
-
const adminContext = {
|
|
168
|
-
...sampleContext,
|
|
169
|
-
actor: {
|
|
170
|
-
...sampleContext.actor,
|
|
171
|
-
authorizationRole: 'administrator',
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
const actionWithAdmin = {
|
|
175
|
-
actionId: 'act-owner-admin',
|
|
176
|
-
toolName: 'builtin/owner_tool',
|
|
177
|
-
parameters: {},
|
|
178
|
-
context: adminContext,
|
|
179
|
-
};
|
|
180
|
-
const res = await engine.evaluateAction(actionWithAdmin);
|
|
181
|
-
expect(res.decision.allowed).toBe(true);
|
|
182
|
-
expect(res.capability).toBeDefined();
|
|
183
|
-
// Context with authorizationRole: 'operator' (non-owner) is rejected
|
|
184
|
-
const operatorAction = {
|
|
185
|
-
actionId: 'act-owner-op',
|
|
186
|
-
toolName: 'builtin/owner_tool',
|
|
187
|
-
parameters: {},
|
|
188
|
-
context: sampleContext, // has authorizationRole: 'operator'
|
|
189
|
-
};
|
|
190
|
-
const resOp = await engine.evaluateAction(operatorAction);
|
|
191
|
-
expect(resOp.decision.allowed).toBe(false);
|
|
192
|
-
expect(resOp.decision.decisionCode).toBe('REJECTED_UNAUTHORIZED');
|
|
193
|
-
});
|
|
194
|
-
});
|
package/dist/action.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { RequestContext } from './context';
|
|
2
|
-
export type ActionRiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
3
|
-
export type ActionLifecycleState = 'PROPOSED' | 'VALIDATED' | 'POLICY_CHECKED' | 'APPROVED' | 'EXECUTING' | 'COMPLETED' | 'REJECTED' | 'FAILED' | 'CANCELLED' | 'TIMED_OUT';
|
|
4
|
-
export interface ActionIntent {
|
|
5
|
-
actionId: string;
|
|
6
|
-
toolName: string;
|
|
7
|
-
parameters: Record<string, unknown>;
|
|
8
|
-
description?: string;
|
|
9
|
-
context?: RequestContext;
|
|
10
|
-
executionId?: string;
|
|
11
|
-
}
|
|
12
|
-
export interface ActionPolicyDecision {
|
|
13
|
-
allowed: boolean;
|
|
14
|
-
reason: string;
|
|
15
|
-
riskLevel: ActionRiskLevel;
|
|
16
|
-
requiredCapabilities?: string[];
|
|
17
|
-
executionId: string;
|
|
18
|
-
decisionCode: 'ALLOWED_AUTO' | 'ALLOWED_POLICY' | 'REJECTED_UNKNOWN_TOOL' | 'REJECTED_UNAUTHORIZED' | 'REJECTED_MISSING_CAPABILITY' | 'REJECTED_HIGH_RISK_UNAPPROVED' | 'REJECTED_CHANNEL_RESTRICTED' | 'REJECTED_COMPANION_MISMATCH' | 'REJECTED_POLICY';
|
|
19
|
-
}
|
|
20
|
-
export interface ActionAuditEvent {
|
|
21
|
-
executionId: string;
|
|
22
|
-
actionId: string;
|
|
23
|
-
toolName: string;
|
|
24
|
-
providerId?: string;
|
|
25
|
-
companionId: string;
|
|
26
|
-
actorId?: string;
|
|
27
|
-
sessionId?: string;
|
|
28
|
-
channel?: string;
|
|
29
|
-
correlationId?: string;
|
|
30
|
-
riskLevel: ActionRiskLevel;
|
|
31
|
-
lifecycle: ActionLifecycleState;
|
|
32
|
-
decision?: ActionPolicyDecision;
|
|
33
|
-
parametersHash?: string;
|
|
34
|
-
resultHash?: string;
|
|
35
|
-
eventHash?: string;
|
|
36
|
-
previousEventHash?: string;
|
|
37
|
-
timestamp: string;
|
|
38
|
-
durationMs?: number;
|
|
39
|
-
error?: string;
|
|
40
|
-
}
|
|
41
|
-
export interface ActionExecutionResult {
|
|
42
|
-
actionId: string;
|
|
43
|
-
executionId: string;
|
|
44
|
-
toolName: string;
|
|
45
|
-
lifecycle: ActionLifecycleState;
|
|
46
|
-
success: boolean;
|
|
47
|
-
result?: unknown;
|
|
48
|
-
error?: string;
|
|
49
|
-
decision?: ActionPolicyDecision;
|
|
50
|
-
durationMs?: number;
|
|
51
|
-
}
|
|
52
|
-
export interface ToolDefinition {
|
|
53
|
-
name: string;
|
|
54
|
-
description: string;
|
|
55
|
-
inputSchema: Record<string, unknown>;
|
|
56
|
-
providerId?: string;
|
|
57
|
-
riskLevel?: ActionRiskLevel;
|
|
58
|
-
requiredCapabilities?: string[];
|
|
59
|
-
allowedRoles?: string[];
|
|
60
|
-
allowedChannels?: string[];
|
|
61
|
-
timeoutMs?: number;
|
|
62
|
-
requiresApproval?: boolean;
|
|
63
|
-
}
|
|
64
|
-
export interface ToolExecutionOptions {
|
|
65
|
-
timeoutMs?: number;
|
|
66
|
-
signal?: AbortSignal;
|
|
67
|
-
}
|
|
68
|
-
export interface HandsOrgan {
|
|
69
|
-
listTools(): Promise<ToolDefinition[]>;
|
|
70
|
-
executeAction(action: ActionIntent, authorization: any, // AuthorizationCapability required
|
|
71
|
-
options?: ToolExecutionOptions): Promise<ActionExecutionResult>;
|
|
72
|
-
}
|
package/dist/action.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|