@highflame/policy 2.0.0 → 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.
Files changed (50) hide show
  1. package/_schemas/overwatch/context.json +0 -30
  2. package/_schemas/overwatch/schema.cedarschema +0 -5
  3. package/dist/builder.d.ts.map +1 -1
  4. package/dist/builder.js +16 -3
  5. package/dist/builder.js.map +1 -1
  6. package/dist/entity-metadata-types.gen.d.ts +17 -0
  7. package/dist/entity-metadata-types.gen.d.ts.map +1 -0
  8. package/dist/entity-metadata-types.gen.js +3 -0
  9. package/dist/entity-metadata-types.gen.js.map +1 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +8 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/overwatch-context.gen.d.ts +0 -2
  15. package/dist/overwatch-context.gen.d.ts.map +1 -1
  16. package/dist/overwatch-context.gen.js +0 -2
  17. package/dist/overwatch-context.gen.js.map +1 -1
  18. package/dist/overwatch-entities.gen.d.ts +12 -0
  19. package/dist/overwatch-entities.gen.d.ts.map +1 -0
  20. package/dist/overwatch-entities.gen.js +38 -0
  21. package/dist/overwatch-entities.gen.js.map +1 -0
  22. package/dist/palisade-entities.gen.d.ts +12 -0
  23. package/dist/palisade-entities.gen.d.ts.map +1 -0
  24. package/dist/palisade-entities.gen.js +46 -0
  25. package/dist/palisade-entities.gen.js.map +1 -0
  26. package/dist/schemas.test.js +0 -4
  27. package/dist/schemas.test.js.map +1 -1
  28. package/dist/service-schemas.gen.d.ts +48 -0
  29. package/dist/service-schemas.gen.d.ts.map +1 -0
  30. package/dist/service-schemas.gen.js +581 -0
  31. package/dist/service-schemas.gen.js.map +1 -0
  32. package/dist/studio-ui.test.d.ts +8 -0
  33. package/dist/studio-ui.test.d.ts.map +1 -0
  34. package/dist/studio-ui.test.js +254 -0
  35. package/dist/studio-ui.test.js.map +1 -0
  36. package/dist/types.d.ts +7 -0
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/types.js +8 -0
  39. package/dist/types.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/builder.ts +17 -3
  42. package/src/entity-metadata-types.gen.ts +19 -0
  43. package/src/index.ts +28 -0
  44. package/src/overwatch-context.gen.ts +0 -2
  45. package/src/overwatch-entities.gen.ts +41 -0
  46. package/src/palisade-entities.gen.ts +49 -0
  47. package/src/schemas.test.ts +0 -4
  48. package/src/service-schemas.gen.ts +608 -0
  49. package/src/studio-ui.test.ts +314 -0
  50. package/src/types.ts +28 -0
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Studio UI Integration Tests
3
+ *
4
+ * These tests simulate exactly how the Studio UI (Overwatch admin dashboard)
5
+ * will use the @highflame/policy npm package.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+
10
+ // Browser-safe imports (simulating '@highflame/policy/types')
11
+ import {
12
+ EntityType,
13
+ ActionType,
14
+ PolicyBuilder,
15
+ OVERWATCH_SCHEMA,
16
+ PALISADE_SCHEMA,
17
+ OVERWATCH_CONTEXT,
18
+ PALISADE_CONTEXT,
19
+ OverwatchContextKey,
20
+ PalisadeContextKey,
21
+ // Entity metadata for UI dropdowns
22
+ OVERWATCH_ENTITIES,
23
+ OVERWATCH_ACTION_ENTITIES,
24
+ PALISADE_ENTITIES,
25
+ PALISADE_ACTION_ENTITIES,
26
+ type ServiceContext,
27
+ type ActionContext,
28
+ type ServiceEntityMetadata,
29
+ type ActionEntityMetadata,
30
+ } from './types.js';
31
+
32
+ // Node.js only imports (for API routes)
33
+ import {
34
+ PolicyEngine,
35
+ PolicyValidator,
36
+ newEntityUID,
37
+ newEntity,
38
+ } from './index.js';
39
+
40
+ describe('Studio UI Integration Tests', () => {
41
+ /**
42
+ * Test: PolicyBuilder action formatting
43
+ *
44
+ * Tests that simple action names are properly formatted to Cedar syntax.
45
+ * This is the "normal approach" for non-namespaced schemas.
46
+ */
47
+ it('should format simple action names to Cedar syntax', () => {
48
+ // Using simple action name (normal approach)
49
+ const policy = PolicyBuilder.permit()
50
+ .principalType('User')
51
+ .action('call_tool')
52
+ .resourceType('Tool')
53
+ .build();
54
+
55
+ const cedarText = policy.toCedar();
56
+
57
+ // Simple actions should be wrapped as Action::"..."
58
+ expect(cedarText).toContain('action == Action::"call_tool"');
59
+ expect(cedarText).not.toContain('Action::"Action::'); // No double-wrapping
60
+ });
61
+
62
+ /**
63
+ * Test 1: Schema and Context Loading
64
+ *
65
+ * Studio UI needs to load schemas for validation and context metadata
66
+ * for populating form dropdowns.
67
+ */
68
+ it('should load schemas and context metadata for form builders', () => {
69
+ // Schemas should be strings
70
+ expect(typeof OVERWATCH_SCHEMA).toBe('string');
71
+ expect(OVERWATCH_SCHEMA).toContain('namespace Overwatch');
72
+ expect(typeof PALISADE_SCHEMA).toBe('string');
73
+ expect(PALISADE_SCHEMA).toContain('namespace Palisade');
74
+
75
+ // Context metadata should have action definitions
76
+ expect(OVERWATCH_CONTEXT.service).toBe('overwatch');
77
+ expect(OVERWATCH_CONTEXT.actions.length).toBeGreaterThan(0);
78
+
79
+ // Find call_tool action and verify context attributes
80
+ const callToolAction = OVERWATCH_CONTEXT.actions.find(
81
+ (a: ActionContext) => a.name === 'call_tool'
82
+ );
83
+ expect(callToolAction).toBeDefined();
84
+ expect(callToolAction!.context_attributes.length).toBeGreaterThan(0);
85
+
86
+ // Context keys should be available for type-safe form building
87
+ expect(OverwatchContextKey.ToolName).toBe('tool_name');
88
+ expect(OverwatchContextKey.ThreatCount).toBe('threat_count');
89
+ expect(PalisadeContextKey.Severity).toBe('severity');
90
+ expect(PalisadeContextKey.Environment).toBe('environment');
91
+ });
92
+
93
+ /**
94
+ * Test 2: Full Round-Trip - Create Policy → Validate → Evaluate
95
+ *
96
+ * This simulates the complete flow:
97
+ * 1. User creates a policy using PolicyBuilder in the UI
98
+ * 2. API validates the policy against the schema
99
+ * 3. Runtime evaluates the policy
100
+ */
101
+ it('should complete full policy lifecycle for Overwatch', () => {
102
+ // Step 1: User creates policy in form UI
103
+ const policy = PolicyBuilder.permit()
104
+ .principalType('Overwatch::User')
105
+ .action('Overwatch::Action::"call_tool"')
106
+ .resourceType('Overwatch::Tool')
107
+ .whenRaw('context.threat_count < 5')
108
+ .build();
109
+
110
+ const cedarText = policy.toCedar();
111
+ expect(cedarText).toContain('permit');
112
+ expect(cedarText).toContain('Overwatch::User');
113
+
114
+ // Step 2: API validates policy against schema
115
+ const validator = new PolicyValidator(OVERWATCH_SCHEMA);
116
+ const validationResult = validator.validate(cedarText);
117
+ expect(validationResult.valid).toBe(true);
118
+
119
+ // Step 3: Runtime loads and evaluates policy
120
+ const engine = new PolicyEngine({ schema: OVERWATCH_SCHEMA });
121
+ engine.loadPolicies(cedarText);
122
+
123
+ const entities = [
124
+ newEntity('Overwatch::User', 'mcp_client', { user_type: 'external', email: 'test@example.com' }),
125
+ newEntity('Overwatch::Tool', 'shell', { tool_name: 'shell', risk_level: 'high' }),
126
+ ];
127
+
128
+ // Full context as Guardian will provide at runtime
129
+ const baseContext = {
130
+ content: 'ls -la',
131
+ source: 'claudecode',
132
+ event: 'PreToolUse',
133
+ user_email: 'test@example.com',
134
+ tool_name: 'shell',
135
+ mcp_server: 'filesystem',
136
+ mcp_tool: 'shell',
137
+ path: '/workspace',
138
+ cwd: '/workspace',
139
+ workspace_root: '/workspace',
140
+ highest_severity: 'low',
141
+ threat_categories: [],
142
+ threat_types: [],
143
+ yara_threats: [],
144
+ max_threat_severity: 1,
145
+ contains_secrets: false,
146
+ response_content: '',
147
+ };
148
+
149
+ // Should allow when threat_count < 5
150
+ const allowDecision = engine.evaluate({
151
+ principal: newEntityUID('Overwatch::User', 'mcp_client'),
152
+ action: 'Overwatch::Action::"call_tool"',
153
+ resource: newEntityUID('Overwatch::Tool', 'shell'),
154
+ context: { ...baseContext, threat_count: 3 },
155
+ entities,
156
+ });
157
+ expect(allowDecision.effect).toBe('Allow');
158
+
159
+ // Should deny when threat_count >= 5 (no matching permit)
160
+ const denyDecision = engine.evaluate({
161
+ principal: newEntityUID('Overwatch::User', 'mcp_client'),
162
+ action: 'Overwatch::Action::"call_tool"',
163
+ resource: newEntityUID('Overwatch::Tool', 'shell'),
164
+ context: { ...baseContext, threat_count: 10 },
165
+ entities,
166
+ });
167
+ expect(denyDecision.effect).toBe('Deny');
168
+ });
169
+
170
+ /**
171
+ * Test 3: Full Round-Trip for Palisade
172
+ *
173
+ * Same flow but for Palisade ML security policies.
174
+ */
175
+ it('should complete full policy lifecycle for Palisade', () => {
176
+ // Step 1: Create a forbid policy for CRITICAL findings
177
+ const policy = PolicyBuilder.forbid()
178
+ .principalType('Palisade::Scanner')
179
+ .action('Palisade::Action::"load_model"')
180
+ .resourceType('Palisade::Artifact')
181
+ .whenRaw('context.severity == "CRITICAL"')
182
+ .build();
183
+
184
+ const cedarText = policy.toCedar();
185
+
186
+ // Step 2: Validate
187
+ const validator = new PolicyValidator(PALISADE_SCHEMA);
188
+ expect(validator.validate(cedarText).valid).toBe(true);
189
+
190
+ // Step 3: Evaluate
191
+ const engine = new PolicyEngine({ schema: PALISADE_SCHEMA });
192
+ engine.loadPolicies(cedarText);
193
+
194
+ const entities = [
195
+ newEntity('Palisade::Scanner', 'palisade', { scanner_type: 'ml' }),
196
+ newEntity('Palisade::Artifact', 'model.pkl', {
197
+ artifact_format: 'pickle',
198
+ path: '/model.pkl',
199
+ signed: false,
200
+ signer: 'unsigned',
201
+ }),
202
+ ];
203
+
204
+ // Should deny CRITICAL findings
205
+ const decision = engine.evaluate({
206
+ principal: newEntityUID('Palisade::Scanner', 'palisade'),
207
+ action: 'Palisade::Action::"load_model"',
208
+ resource: newEntityUID('Palisade::Artifact', 'model.pkl'),
209
+ context: { severity: 'CRITICAL' },
210
+ entities,
211
+ });
212
+ expect(decision.effect).toBe('Deny');
213
+ });
214
+
215
+ /**
216
+ * Test 4: Entity Metadata for UI Dropdowns - Overwatch
217
+ *
218
+ * Studio UI needs to know which entity types can be principals,
219
+ * which can be resources, and what actions are available.
220
+ * This data is extracted from Cedar schema appliesTo blocks.
221
+ */
222
+ it('should provide correct entity metadata for Overwatch UI dropdowns', () => {
223
+ // Verify ServiceEntityMetadata structure
224
+ expect(OVERWATCH_ENTITIES.principals).toBeDefined();
225
+ expect(OVERWATCH_ENTITIES.resources).toBeDefined();
226
+ expect(OVERWATCH_ENTITIES.actions).toBeDefined();
227
+
228
+ // Overwatch principals should include User and Agent
229
+ expect(OVERWATCH_ENTITIES.principals).toContain('Agent');
230
+ expect(OVERWATCH_ENTITIES.principals).toContain('User');
231
+ expect(OVERWATCH_ENTITIES.principals).toHaveLength(2);
232
+
233
+ // Overwatch resources should include all resource types
234
+ expect(OVERWATCH_ENTITIES.resources).toContain('FilePath');
235
+ expect(OVERWATCH_ENTITIES.resources).toContain('LlmPrompt');
236
+ expect(OVERWATCH_ENTITIES.resources).toContain('Server');
237
+ expect(OVERWATCH_ENTITIES.resources).toContain('Tool');
238
+ expect(OVERWATCH_ENTITIES.resources).toHaveLength(4);
239
+
240
+ // Overwatch actions should match schema
241
+ expect(OVERWATCH_ENTITIES.actions).toContain('call_tool');
242
+ expect(OVERWATCH_ENTITIES.actions).toContain('connect_server');
243
+ expect(OVERWATCH_ENTITIES.actions).toContain('process_prompt');
244
+ expect(OVERWATCH_ENTITIES.actions).toContain('read_file');
245
+ expect(OVERWATCH_ENTITIES.actions).toContain('write_file');
246
+ expect(OVERWATCH_ENTITIES.actions).toHaveLength(5);
247
+ });
248
+
249
+ /**
250
+ * Test 5: Per-Action Entity Mapping - Overwatch
251
+ *
252
+ * Studio UI needs to filter dropdowns based on selected action.
253
+ * Each action has specific valid principals and resources.
254
+ */
255
+ it('should provide per-action entity mapping for Overwatch', () => {
256
+ // call_tool action should have correct principals and resources
257
+ const callTool = OVERWATCH_ACTION_ENTITIES['call_tool'];
258
+ expect(callTool).toBeDefined();
259
+ expect(callTool.principals).toContain('User');
260
+ expect(callTool.principals).toContain('Agent');
261
+ expect(callTool.resources).toContain('Tool');
262
+ expect(callTool.resources).toContain('FilePath');
263
+
264
+ // connect_server action should only apply to Server resource
265
+ const connectServer = OVERWATCH_ACTION_ENTITIES['connect_server'];
266
+ expect(connectServer).toBeDefined();
267
+ expect(connectServer.principals).toContain('User');
268
+ expect(connectServer.principals).toContain('Agent');
269
+ expect(connectServer.resources).toContain('Server');
270
+ expect(connectServer.resources).not.toContain('Tool');
271
+
272
+ // process_prompt action should only apply to LlmPrompt resource
273
+ const processPrompt = OVERWATCH_ACTION_ENTITIES['process_prompt'];
274
+ expect(processPrompt).toBeDefined();
275
+ expect(processPrompt.resources).toContain('LlmPrompt');
276
+ expect(processPrompt.resources).not.toContain('Tool');
277
+
278
+ // read_file and write_file should apply to FilePath resource
279
+ const readFile = OVERWATCH_ACTION_ENTITIES['read_file'];
280
+ const writeFile = OVERWATCH_ACTION_ENTITIES['write_file'];
281
+ expect(readFile.resources).toContain('FilePath');
282
+ expect(writeFile.resources).toContain('FilePath');
283
+ });
284
+
285
+ /**
286
+ * Test 6: Entity Metadata for Palisade
287
+ *
288
+ * Verify Palisade service also has correct entity metadata.
289
+ */
290
+ it('should provide correct entity metadata for Palisade UI dropdowns', () => {
291
+ // Palisade has Scanner as principal
292
+ expect(PALISADE_ENTITIES.principals).toContain('Scanner');
293
+
294
+ // Palisade resources include Artifact and Package
295
+ expect(PALISADE_ENTITIES.resources).toContain('Artifact');
296
+ expect(PALISADE_ENTITIES.resources).toContain('Package');
297
+
298
+ // Palisade actions
299
+ expect(PALISADE_ENTITIES.actions).toContain('load_model');
300
+ expect(PALISADE_ENTITIES.actions).toContain('scan_artifact');
301
+ expect(PALISADE_ENTITIES.actions).toContain('quarantine_artifact');
302
+
303
+ // Per-action mapping - load_model applies to Artifact
304
+ const loadModel = PALISADE_ACTION_ENTITIES['load_model'];
305
+ expect(loadModel).toBeDefined();
306
+ expect(loadModel.principals).toContain('Scanner');
307
+ expect(loadModel.resources).toContain('Artifact');
308
+
309
+ // scan_package applies to Package resource
310
+ const scanPackage = PALISADE_ACTION_ENTITIES['scan_package'];
311
+ expect(scanPackage).toBeDefined();
312
+ expect(scanPackage.resources).toContain('Package');
313
+ });
314
+ });
package/src/types.ts CHANGED
@@ -16,3 +16,31 @@ export * from './builder.js';
16
16
 
17
17
  // Error types - works in browser (no WASM dependency)
18
18
  export * from './errors.js';
19
+
20
+ // Service-specific schemas and context (inlined, browser-safe)
21
+ export {
22
+ OVERWATCH_SCHEMA,
23
+ OVERWATCH_CONTEXT,
24
+ PALISADE_SCHEMA,
25
+ PALISADE_CONTEXT,
26
+ } from './service-schemas.gen.js';
27
+ export type {
28
+ ContextAttribute,
29
+ ActionContext,
30
+ ServiceContext,
31
+ } from './service-schemas.gen.js';
32
+
33
+ // Service-specific context key enums
34
+ export { OverwatchContextKey } from './overwatch-context.gen.js';
35
+ export { PalisadeContextKey } from './palisade-context.gen.js';
36
+
37
+ // Service-specific entity metadata (for UI - principals, resources, actions)
38
+ export {
39
+ OVERWATCH_ENTITIES,
40
+ OVERWATCH_ACTION_ENTITIES,
41
+ } from './overwatch-entities.gen.js';
42
+ export {
43
+ PALISADE_ENTITIES,
44
+ PALISADE_ACTION_ENTITIES,
45
+ } from './palisade-entities.gen.js';
46
+ export type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';