@highflame/policy 2.0.1 → 2.0.3

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 (47) hide show
  1. package/dist/annotations.d.ts +127 -0
  2. package/dist/annotations.d.ts.map +1 -0
  3. package/dist/annotations.js +175 -0
  4. package/dist/annotations.js.map +1 -0
  5. package/dist/builder.d.ts +114 -25
  6. package/dist/builder.d.ts.map +1 -1
  7. package/dist/builder.js +295 -113
  8. package/dist/builder.js.map +1 -1
  9. package/dist/entity-metadata-types.gen.d.ts +17 -0
  10. package/dist/entity-metadata-types.gen.d.ts.map +1 -0
  11. package/dist/entity-metadata-types.gen.js +3 -0
  12. package/dist/entity-metadata-types.gen.js.map +1 -0
  13. package/dist/index.d.ts +5 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/overwatch-entities.gen.d.ts +12 -0
  18. package/dist/overwatch-entities.gen.d.ts.map +1 -0
  19. package/dist/overwatch-entities.gen.js +38 -0
  20. package/dist/overwatch-entities.gen.js.map +1 -0
  21. package/dist/palisade-entities.gen.d.ts +12 -0
  22. package/dist/palisade-entities.gen.d.ts.map +1 -0
  23. package/dist/palisade-entities.gen.js +46 -0
  24. package/dist/palisade-entities.gen.js.map +1 -0
  25. package/dist/parser.d.ts +1 -1
  26. package/dist/parser.d.ts.map +1 -1
  27. package/dist/parser.js +18 -11
  28. package/dist/parser.js.map +1 -1
  29. package/dist/parser.test.js +2 -2
  30. package/dist/parser.test.js.map +1 -1
  31. package/dist/studio-ui.test.js +526 -1
  32. package/dist/studio-ui.test.js.map +1 -1
  33. package/dist/types.d.ts +5 -1
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js +6 -1
  36. package/dist/types.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/annotations.ts +243 -0
  39. package/src/builder.ts +386 -127
  40. package/src/entity-metadata-types.gen.ts +19 -0
  41. package/src/index.ts +13 -1
  42. package/src/overwatch-entities.gen.ts +41 -0
  43. package/src/palisade-entities.gen.ts +49 -0
  44. package/src/parser.test.ts +2 -2
  45. package/src/parser.ts +20 -12
  46. package/src/studio-ui.test.ts +606 -0
  47. package/src/types.ts +15 -1
package/src/builder.ts CHANGED
@@ -14,29 +14,103 @@
14
14
  * .when("context.environment == \"production\"")
15
15
  * .build();
16
16
  *
17
- * // Get Cedar policy text
17
+ * // Get Cedar policy text (with proper @annotations)
18
18
  * const cedarText = policy.toCedar();
19
19
  *
20
20
  * // Get JSON representation (for storage/editing)
21
21
  * const policyJson = policy.toJSON();
22
22
  * ```
23
+ *
24
+ * Cedar Annotations:
25
+ * Policies include proper Cedar annotations that are embedded in the policy text:
26
+ * ```cedar
27
+ * @id("rule-001")
28
+ * @name("Block critical threats")
29
+ * @severity("high")
30
+ * permit(...) when {...};
31
+ * ```
23
32
  */
24
33
 
25
34
  import { EntityType, EntityUID } from './entities.gen.js';
26
35
  import { ActionType } from './actions.gen.js';
36
+ import {
37
+ type PolicyAnnotations,
38
+ type CustomAnnotations,
39
+ type PolicySeverity,
40
+ generateAnnotationLines,
41
+ generateRuleId,
42
+ } from './annotations.js';
43
+
44
+ // ============================================================================
45
+ // Security: Input Validation and Escaping
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Valid identifier pattern for Cedar (alphanumeric, underscore, with optional namespace).
50
+ */
51
+ const VALID_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)*$/;
52
+
53
+ /**
54
+ * Pattern that matches potentially dangerous content in raw conditions.
55
+ */
56
+ const DANGEROUS_PATTERN_REGEX = /[;}]|\/\/|\/\*|\*\/|permit\s*\(|forbid\s*\(/;
57
+
58
+ /**
59
+ * Escape a string value for use in Cedar string literals.
60
+ * This prevents injection attacks by escaping backslashes and double quotes.
61
+ */
62
+ function escapeCedarString(value: string): string {
63
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
64
+ }
65
+
66
+ /**
67
+ * Check if a string is a valid Cedar identifier.
68
+ */
69
+ function isValidIdentifier(s: string): boolean {
70
+ return VALID_IDENTIFIER_REGEX.test(s);
71
+ }
72
+
73
+ /**
74
+ * Sanitize an identifier, replacing invalid characters with underscores.
75
+ */
76
+ function sanitizeIdentifier(s: string, context: string): string {
77
+ if (isValidIdentifier(s)) {
78
+ return s;
79
+ }
80
+ // Replace invalid characters with underscores
81
+ const sanitized = s.replace(/[^A-Za-z0-9_:]/g, '_');
82
+ if (sanitized === '' || !isValidIdentifier(sanitized)) {
83
+ return `invalid_${context}`;
84
+ }
85
+ return sanitized;
86
+ }
87
+
88
+ /**
89
+ * Validate a raw condition string for potentially dangerous patterns.
90
+ * Returns true if the condition is safe to use.
91
+ */
92
+ function isValidRawCondition(condition: string): boolean {
93
+ return !DANGEROUS_PATTERN_REGEX.test(condition);
94
+ }
27
95
 
28
96
  /**
29
97
  * Format an action string for Cedar policy text.
30
98
  * Detects if action is already namespaced (contains 'Action::"') and preserves it,
31
99
  * otherwise wraps with Action::"...".
100
+ * Escapes the action name to prevent injection attacks.
32
101
  */
33
102
  function formatAction(action: string): string {
34
103
  if (action.includes('Action::"')) {
35
- // Already namespaced (e.g., 'Overwatch::Action::"call_tool"')
104
+ // Already namespaced - extract and escape the action name
105
+ const parts = action.split('Action::"');
106
+ if (parts.length === 2) {
107
+ const actionName = parts[1].replace(/"$/, '');
108
+ return `${parts[0]}Action::"${escapeCedarString(actionName)}"`;
109
+ }
36
110
  return action;
37
111
  }
38
112
  // Non-namespaced, wrap with Action::"..."
39
- return `Action::"${action}"`;
113
+ return `Action::"${escapeCedarString(action)}"`;
40
114
  }
41
115
 
42
116
  /**
@@ -88,14 +162,14 @@ export type PolicyPrincipal = PolicyEntity;
88
162
  /** Alias for PolicyEntity when used as resource constraint */
89
163
  export type PolicyResource = PolicyEntity;
90
164
 
91
- /**
92
- * Rule severity levels for UI display and prioritization
93
- */
94
- export type PolicySeverity = 'critical' | 'high' | 'medium' | 'low';
165
+ // Re-export PolicySeverity from annotations for backwards compatibility
166
+ export type { PolicySeverity } from './annotations.js';
95
167
 
96
168
  /**
97
169
  * JSON representation of a policy for storage and editing.
98
- * This is the base interface used by PolicyBuilder.
170
+ * This is the base interface used by PolicyBuilder (legacy format).
171
+ *
172
+ * @deprecated Use PolicyRule with annotations for new code.
99
173
  */
100
174
  export interface PolicyJSON {
101
175
  /** Unique identifier for this policy */
@@ -117,155 +191,125 @@ export interface PolicyJSON {
117
191
  }
118
192
 
119
193
  /**
120
- * A policy rule with UI/storage metadata.
121
- * Extends PolicyJSON with fields needed for UI editing and database storage.
194
+ * A policy rule with full Cedar annotation support.
122
195
  *
123
196
  * This is the canonical type used across all Highflame services:
124
197
  * - highflame-studio (UI)
125
198
  * - highflame-authz (Go backend)
126
199
  * - Any Python services
127
200
  *
128
- * Each PolicyRule maps 1:1 to a Cedar policy statement.
201
+ * Each PolicyRule maps 1:1 to a Cedar policy statement with proper annotations.
202
+ *
203
+ * Annotations are embedded in Cedar text:
204
+ * ```cedar
205
+ * @id("rule-001")
206
+ * @name("Block critical threats")
207
+ * @severity("high")
208
+ * @tags("security,baseline")
209
+ * @compliance("SOC2")
210
+ * forbid(...) when {...};
211
+ * ```
129
212
  */
130
- export interface PolicyRule extends PolicyJSON {
131
- /** Whether this rule is active (used for toggling rules on/off in UI) */
213
+ export interface PolicyRule {
214
+ /** Predefined annotations (embedded in Cedar text) */
215
+ annotations: PolicyAnnotations;
216
+ /** Custom user-defined annotations (embedded in Cedar text) */
217
+ customAnnotations?: CustomAnnotations;
218
+
219
+ /** Policy effect - permit or forbid */
220
+ effect: PolicyEffect;
221
+ /** Principal constraint */
222
+ principal: PolicyEntity | null;
223
+ /** Action constraint - single action or array of actions */
224
+ action: string | string[];
225
+ /** Resource constraint */
226
+ resource: PolicyEntity | null;
227
+ /** Structured conditions (when clause) */
228
+ conditions: PolicyCondition[];
229
+ /** Raw condition string (for advanced/complex conditions) */
230
+ rawCondition?: string;
231
+
232
+ /** Whether this rule is active - NOT embedded in Cedar (runtime state) */
233
+ enabled: boolean;
234
+ /** Display/evaluation order - NOT embedded in Cedar (runtime state) */
235
+ order: number;
236
+ }
237
+
238
+ /**
239
+ * Legacy PolicyRule format for backwards compatibility.
240
+ * Used when parsing policies that don't have the new annotations structure.
241
+ *
242
+ * @deprecated Use PolicyRule with annotations for new code.
243
+ */
244
+ export interface LegacyPolicyRule extends PolicyJSON {
132
245
  enabled: boolean;
133
- /** Display/evaluation order (0-indexed) */
134
246
  order: number;
135
- /** Optional description (separate from name for longer explanations) */
136
247
  description?: string;
137
- /** Rule severity for display and prioritization */
138
248
  severity?: PolicySeverity;
139
- /** Optional tags for categorization and filtering */
140
249
  tags?: string[];
141
250
  }
142
251
 
143
252
  /**
144
- * A built policy that can be converted to Cedar text or JSON
253
+ * Convert a legacy PolicyRule to the new annotations-based format.
254
+ */
255
+ export function convertLegacyRule(legacy: LegacyPolicyRule, index: number = 0): PolicyRule {
256
+ return {
257
+ annotations: {
258
+ id: legacy.id || generateRuleId(),
259
+ name: legacy.name || legacy.id || `Rule ${index + 1}`,
260
+ description: legacy.description,
261
+ severity: legacy.severity,
262
+ tags: legacy.tags,
263
+ },
264
+ effect: legacy.effect,
265
+ principal: legacy.principal,
266
+ action: legacy.action,
267
+ resource: legacy.resource,
268
+ conditions: legacy.conditions,
269
+ rawCondition: legacy.rawCondition,
270
+ enabled: legacy.enabled,
271
+ order: legacy.order,
272
+ };
273
+ }
274
+
275
+ /**
276
+ * A built policy that can be converted to Cedar text or JSON.
277
+ * This class is used by PolicyBuilder for the legacy API.
278
+ *
279
+ * For new code, use ruleToCedar() and rulesToCedar() functions with PolicyRule.
145
280
  */
146
281
  export class Policy {
147
282
  constructor(private readonly data: PolicyJSON) {}
148
283
 
149
284
  /**
150
- * Convert to Cedar policy text
285
+ * Convert to Cedar policy text.
286
+ * Uses proper Cedar @annotation syntax.
151
287
  */
152
288
  toCedar(): string {
153
289
  const lines: string[] = [];
154
290
 
155
- // Policy annotation (comment with name)
156
- if (this.data.name) {
157
- lines.push(`// @name: ${this.data.name}`);
158
- }
159
- if (this.data.id) {
160
- lines.push(`// @id: ${this.data.id}`);
291
+ // Generate proper Cedar annotations
292
+ if (this.data.id || this.data.name) {
293
+ const annotations: PolicyAnnotations = {
294
+ id: this.data.id || generateRuleId(),
295
+ name: this.data.name || this.data.id || 'Unnamed Policy',
296
+ };
297
+ lines.push(...generateAnnotationLines(annotations));
161
298
  }
162
299
 
163
- // Effect and principal
164
- let policyLine = `${this.data.effect} (`;
165
-
166
- // Principal
167
- if (this.data.principal) {
168
- if (this.data.principal.id) {
169
- policyLine += `\n principal == ${this.data.principal.type}::\"${this.data.principal.id}\"`;
170
- } else {
171
- policyLine += `\n principal is ${this.data.principal.type}`;
172
- }
173
- } else {
174
- policyLine += `\n principal`;
175
- }
176
-
177
- // Action
178
- if (Array.isArray(this.data.action)) {
179
- if (this.data.action.length === 1) {
180
- policyLine += `,\n action == ${formatAction(this.data.action[0])}`;
181
- } else {
182
- const actions = this.data.action.map(a => formatAction(a)).join(', ');
183
- policyLine += `,\n action in [${actions}]`;
184
- }
185
- } else {
186
- policyLine += `,\n action == ${formatAction(this.data.action)}`;
187
- }
188
-
189
- // Resource
190
- if (this.data.resource) {
191
- if (this.data.resource.id) {
192
- policyLine += `,\n resource == ${this.data.resource.type}::\"${this.data.resource.id}\"`;
193
- } else {
194
- policyLine += `,\n resource is ${this.data.resource.type}`;
195
- }
196
- } else {
197
- policyLine += `,\n resource`;
198
- }
199
-
200
- policyLine += '\n)';
201
- lines.push(policyLine);
202
-
203
- // When clause
204
- if (this.data.rawCondition) {
205
- lines.push(`when { ${this.data.rawCondition} };`);
206
- } else if (this.data.conditions.length > 0) {
207
- const conditionStr = this.data.conditions
208
- .map(c => this.conditionToCedar(c))
209
- .join(' && ');
210
- lines.push(`when { ${conditionStr} };`);
211
- } else {
212
- lines.push(';');
213
- }
300
+ // Generate policy body
301
+ lines.push(generatePolicyBody(
302
+ this.data.effect,
303
+ this.data.principal,
304
+ this.data.action,
305
+ this.data.resource,
306
+ this.data.conditions,
307
+ this.data.rawCondition
308
+ ));
214
309
 
215
310
  return lines.join('\n');
216
311
  }
217
312
 
218
- /**
219
- * Convert a condition to Cedar syntax
220
- */
221
- private conditionToCedar(condition: PolicyCondition): string {
222
- const { field, operator, value } = condition;
223
- const valueStr = this.valueToString(value);
224
-
225
- switch (operator) {
226
- case 'eq':
227
- return `context.${field} == ${valueStr}`;
228
- case 'neq':
229
- return `context.${field} != ${valueStr}`;
230
- case 'lt':
231
- return `context.${field} < ${valueStr}`;
232
- case 'lte':
233
- return `context.${field} <= ${valueStr}`;
234
- case 'gt':
235
- return `context.${field} > ${valueStr}`;
236
- case 'gte':
237
- return `context.${field} >= ${valueStr}`;
238
- case 'contains':
239
- return `context.${field}.contains(${valueStr})`;
240
- case 'in':
241
- if (Array.isArray(value)) {
242
- const items = value.map(v => `\"${v}\"`).join(', ');
243
- return `context.${field} in [${items}]`;
244
- }
245
- return `context.${field} in ${valueStr}`;
246
- case 'like':
247
- return `context.${field} like ${valueStr}`;
248
- default:
249
- return `context.${field} == ${valueStr}`;
250
- }
251
- }
252
-
253
- /**
254
- * Convert a value to Cedar string representation
255
- */
256
- private valueToString(value: string | number | boolean | string[]): string {
257
- if (typeof value === 'string') {
258
- return `\"${value}\"`;
259
- }
260
- if (typeof value === 'number' || typeof value === 'boolean') {
261
- return String(value);
262
- }
263
- if (Array.isArray(value)) {
264
- return `[${value.map(v => `\"${v}\"`).join(', ')}]`;
265
- }
266
- return String(value);
267
- }
268
-
269
313
  /**
270
314
  * Get JSON representation for storage
271
315
  */
@@ -288,6 +332,221 @@ export class Policy {
288
332
  }
289
333
  }
290
334
 
335
+ // ============================================================================
336
+ // Cedar Generation Functions
337
+ // ============================================================================
338
+
339
+ /**
340
+ * Convert a condition to Cedar syntax.
341
+ * Field names are sanitized to prevent injection attacks.
342
+ */
343
+ function conditionToCedar(condition: PolicyCondition): string {
344
+ const field = sanitizeIdentifier(condition.field, 'field');
345
+ const { operator, value } = condition;
346
+ const valueStr = valueToString(value);
347
+
348
+ switch (operator) {
349
+ case 'eq':
350
+ return `context.${field} == ${valueStr}`;
351
+ case 'neq':
352
+ return `context.${field} != ${valueStr}`;
353
+ case 'lt':
354
+ return `context.${field} < ${valueStr}`;
355
+ case 'lte':
356
+ return `context.${field} <= ${valueStr}`;
357
+ case 'gt':
358
+ return `context.${field} > ${valueStr}`;
359
+ case 'gte':
360
+ return `context.${field} >= ${valueStr}`;
361
+ case 'contains':
362
+ return `context.${field}.contains(${valueStr})`;
363
+ case 'in':
364
+ if (Array.isArray(value)) {
365
+ const items = value.map(v => `"${escapeCedarString(v)}"`).join(', ');
366
+ return `context.${field} in [${items}]`;
367
+ }
368
+ return `context.${field} in ${valueStr}`;
369
+ case 'like':
370
+ return `context.${field} like ${valueStr}`;
371
+ default:
372
+ return `context.${field} == ${valueStr}`;
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Convert a value to Cedar string representation.
378
+ * String values are escaped to prevent injection attacks.
379
+ */
380
+ function valueToString(value: string | number | boolean | string[]): string {
381
+ if (typeof value === 'string') {
382
+ return `"${escapeCedarString(value)}"`;
383
+ }
384
+ if (typeof value === 'number' || typeof value === 'boolean') {
385
+ return String(value);
386
+ }
387
+ if (Array.isArray(value)) {
388
+ return `[${value.map(v => `"${escapeCedarString(v)}"`).join(', ')}]`;
389
+ }
390
+ return String(value);
391
+ }
392
+
393
+ /**
394
+ * Generate the Cedar policy body (permit/forbid statement).
395
+ * All inputs are sanitized/escaped to prevent injection attacks.
396
+ */
397
+ function generatePolicyBody(
398
+ effect: PolicyEffect,
399
+ principal: PolicyEntity | null,
400
+ action: string | string[],
401
+ resource: PolicyEntity | null,
402
+ conditions: PolicyCondition[],
403
+ rawCondition?: string
404
+ ): string {
405
+ let policyLine = `${effect} (`;
406
+
407
+ // Principal
408
+ if (principal) {
409
+ const entityType = sanitizeIdentifier(principal.type, 'principal_type');
410
+ if (principal.id) {
411
+ policyLine += `\n principal == ${entityType}::"${escapeCedarString(principal.id)}"`;
412
+ } else {
413
+ policyLine += `\n principal is ${entityType}`;
414
+ }
415
+ } else {
416
+ policyLine += `\n principal`;
417
+ }
418
+
419
+ // Action
420
+ if (Array.isArray(action)) {
421
+ if (action.length === 1) {
422
+ policyLine += `,\n action == ${formatAction(action[0])}`;
423
+ } else {
424
+ const actions = action.map(a => formatAction(a)).join(', ');
425
+ policyLine += `,\n action in [${actions}]`;
426
+ }
427
+ } else {
428
+ policyLine += `,\n action == ${formatAction(action)}`;
429
+ }
430
+
431
+ // Resource
432
+ if (resource) {
433
+ const entityType = sanitizeIdentifier(resource.type, 'resource_type');
434
+ if (resource.id) {
435
+ policyLine += `,\n resource == ${entityType}::"${escapeCedarString(resource.id)}"`;
436
+ } else {
437
+ policyLine += `,\n resource is ${entityType}`;
438
+ }
439
+ } else {
440
+ policyLine += `,\n resource`;
441
+ }
442
+
443
+ policyLine += '\n)';
444
+
445
+ // When clause
446
+ // SECURITY: rawCondition is validated to prevent injection attacks.
447
+ // If validation fails, fall back to structured conditions.
448
+ if (rawCondition) {
449
+ if (isValidRawCondition(rawCondition)) {
450
+ policyLine += `\nwhen { ${rawCondition} };`;
451
+ } else if (conditions.length > 0) {
452
+ // Fallback to structured conditions if rawCondition is rejected
453
+ const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
454
+ policyLine += `\nwhen { ${conditionStr} };`;
455
+ } else {
456
+ policyLine += ';';
457
+ }
458
+ } else if (conditions.length > 0) {
459
+ const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
460
+ policyLine += `\nwhen { ${conditionStr} };`;
461
+ } else {
462
+ policyLine += ';';
463
+ }
464
+
465
+ return policyLine;
466
+ }
467
+
468
+ /**
469
+ * Convert a PolicyRule to Cedar policy text with proper annotations.
470
+ *
471
+ * @param rule - The PolicyRule to convert
472
+ * @returns Cedar policy text string
473
+ *
474
+ * @example
475
+ * ```typescript
476
+ * const rule: PolicyRule = {
477
+ * annotations: { id: 'rule-001', name: 'Block threats', severity: 'high' },
478
+ * effect: 'forbid',
479
+ * principal: null,
480
+ * action: 'call_tool',
481
+ * resource: null,
482
+ * conditions: [{ field: 'threat_count', operator: 'gt', value: 0 }],
483
+ * enabled: true,
484
+ * order: 0,
485
+ * };
486
+ *
487
+ * const cedar = ruleToCedar(rule);
488
+ * // Output:
489
+ * // @id("rule-001")
490
+ * // @name("Block threats")
491
+ * // @severity("high")
492
+ * // forbid (
493
+ * // principal,
494
+ * // action == Action::"call_tool",
495
+ * // resource
496
+ * // )
497
+ * // when { context.threat_count > 0 };
498
+ * ```
499
+ */
500
+ export function ruleToCedar(rule: PolicyRule): string {
501
+ const lines: string[] = [];
502
+
503
+ // Generate Cedar annotations
504
+ lines.push(...generateAnnotationLines(rule.annotations, rule.customAnnotations));
505
+
506
+ // Generate policy body
507
+ lines.push(generatePolicyBody(
508
+ rule.effect,
509
+ rule.principal,
510
+ rule.action,
511
+ rule.resource,
512
+ rule.conditions,
513
+ rule.rawCondition
514
+ ));
515
+
516
+ return lines.join('\n');
517
+ }
518
+
519
+ /**
520
+ * Convert multiple PolicyRules to Cedar policy text.
521
+ * Only enabled rules are included, sorted by order.
522
+ *
523
+ * @param rules - Array of PolicyRules to convert
524
+ * @param includeDisabled - If true, include disabled rules as comments (default: false)
525
+ * @returns Cedar policy text with all rules separated by blank lines
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * const rules: PolicyRule[] = [...];
530
+ * const cedarText = rulesToCedar(rules);
531
+ * ```
532
+ */
533
+ export function rulesToCedar(rules: PolicyRule[], includeDisabled: boolean = false): string {
534
+ const sortedRules = [...rules].sort((a, b) => a.order - b.order);
535
+
536
+ const cedarPolicies: string[] = [];
537
+ for (const rule of sortedRules) {
538
+ if (rule.enabled) {
539
+ cedarPolicies.push(ruleToCedar(rule));
540
+ } else if (includeDisabled) {
541
+ // Include disabled rules as comments
542
+ const cedarLines = ruleToCedar(rule).split('\n');
543
+ cedarPolicies.push(cedarLines.map(line => `// [DISABLED] ${line}`).join('\n'));
544
+ }
545
+ }
546
+
547
+ return cedarPolicies.join('\n\n');
548
+ }
549
+
291
550
  /**
292
551
  * Builder for constructing Cedar policies with type safety.
293
552
  */
@@ -0,0 +1,19 @@
1
+ // Code generated by highflame-policy-codegen. DO NOT EDIT.
2
+
3
+ /**
4
+ * Entity metadata for a service, extracted from Cedar schema appliesTo blocks.
5
+ * Used by Studio UI to populate dropdowns in policy editor.
6
+ */
7
+ export interface ServiceEntityMetadata {
8
+ readonly principals: readonly string[];
9
+ readonly resources: readonly string[];
10
+ readonly actions: readonly string[];
11
+ }
12
+
13
+ /**
14
+ * Entity metadata for a specific action.
15
+ */
16
+ export interface ActionEntityMetadata {
17
+ readonly principals: readonly string[];
18
+ readonly resources: readonly string[];
19
+ }
package/src/index.ts CHANGED
@@ -14,12 +14,13 @@ export * from './engine.js';
14
14
  export * from './builder.js';
15
15
  export * from './parser.js';
16
16
  export * from './errors.js';
17
+ export * from './annotations.js';
17
18
 
18
19
  // Service-specific schemas and context (inlined)
19
20
  export {
20
21
  OVERWATCH_SCHEMA,
21
- PALISADE_SCHEMA,
22
22
  OVERWATCH_CONTEXT,
23
+ PALISADE_SCHEMA,
23
24
  PALISADE_CONTEXT,
24
25
  } from './service-schemas.gen.js';
25
26
  export type {
@@ -31,3 +32,14 @@ export type {
31
32
  // Service-specific context key enums
32
33
  export { OverwatchContextKey } from './overwatch-context.gen.js';
33
34
  export { PalisadeContextKey } from './palisade-context.gen.js';
35
+
36
+ // Service-specific entity metadata (for UI - principals, resources, actions)
37
+ export {
38
+ OVERWATCH_ENTITIES,
39
+ OVERWATCH_ACTION_ENTITIES,
40
+ } from './overwatch-entities.gen.js';
41
+ export {
42
+ PALISADE_ENTITIES,
43
+ PALISADE_ACTION_ENTITIES,
44
+ } from './palisade-entities.gen.js';
45
+ export type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';
@@ -0,0 +1,41 @@
1
+ // Code generated by highflame-policy-codegen. DO NOT EDIT.
2
+ // Source: schemas/overwatch/schema.cedarschema
3
+
4
+ import type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';
5
+
6
+ /**
7
+ * Overwatch entity metadata for UI components.
8
+ * Extracted from Cedar schema appliesTo blocks.
9
+ */
10
+ export const OVERWATCH_ENTITIES: ServiceEntityMetadata = {
11
+ principals: ['Agent', 'User'],
12
+ resources: ['FilePath', 'LlmPrompt', 'Server', 'Tool'],
13
+ actions: ['call_tool', 'connect_server', 'process_prompt', 'read_file', 'write_file'],
14
+ } as const;
15
+
16
+ /**
17
+ * Per-action entity mapping for Overwatch.
18
+ * Maps action names to their valid principals and resources.
19
+ */
20
+ export const OVERWATCH_ACTION_ENTITIES: Record<string, ActionEntityMetadata> = {
21
+ 'call_tool': {
22
+ principals: ['User', 'Agent'],
23
+ resources: ['Tool', 'FilePath'],
24
+ },
25
+ 'connect_server': {
26
+ principals: ['User', 'Agent'],
27
+ resources: ['Server'],
28
+ },
29
+ 'process_prompt': {
30
+ principals: ['User', 'Agent'],
31
+ resources: ['LlmPrompt'],
32
+ },
33
+ 'read_file': {
34
+ principals: ['User', 'Agent'],
35
+ resources: ['FilePath'],
36
+ },
37
+ 'write_file': {
38
+ principals: ['User', 'Agent'],
39
+ resources: ['FilePath'],
40
+ },
41
+ } as const;