@highflame/policy 2.0.8 → 2.0.10

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 (128) hide show
  1. package/_schemas/overwatch/context.json +54 -54
  2. package/_schemas/overwatch/schema.cedarschema +77 -68
  3. package/dist/actions.gen.d.ts +0 -1
  4. package/dist/actions.gen.js +0 -1
  5. package/dist/annotations.d.ts +0 -1
  6. package/dist/annotations.js +0 -1
  7. package/dist/builder.d.ts +45 -14
  8. package/dist/builder.js +99 -33
  9. package/dist/context.gen.d.ts +0 -1
  10. package/dist/context.gen.js +0 -1
  11. package/dist/engine.d.ts +20 -3
  12. package/dist/engine.js +50 -21
  13. package/dist/entities.gen.d.ts +0 -1
  14. package/dist/entities.gen.js +0 -1
  15. package/dist/entity-metadata-types.gen.d.ts +0 -1
  16. package/dist/entity-metadata-types.gen.js +0 -1
  17. package/dist/errors.d.ts +0 -1
  18. package/dist/errors.js +0 -1
  19. package/dist/index.d.ts +0 -1
  20. package/dist/index.js +0 -1
  21. package/dist/overwatch-context.gen.d.ts +0 -1
  22. package/dist/overwatch-context.gen.js +0 -1
  23. package/dist/overwatch-defaults.gen.d.ts +0 -1
  24. package/dist/overwatch-defaults.gen.js +24 -3
  25. package/dist/overwatch-entities.gen.d.ts +0 -1
  26. package/dist/overwatch-entities.gen.js +0 -1
  27. package/dist/palisade-context.gen.d.ts +0 -1
  28. package/dist/palisade-context.gen.js +0 -1
  29. package/dist/palisade-entities.gen.d.ts +0 -1
  30. package/dist/palisade-entities.gen.js +0 -1
  31. package/dist/parser.d.ts +0 -1
  32. package/dist/parser.js +0 -1
  33. package/dist/schema.gen.d.ts +0 -1
  34. package/dist/schema.gen.js +0 -1
  35. package/dist/schemas.d.ts +0 -1
  36. package/dist/schemas.js +0 -1
  37. package/dist/service-schemas.gen.d.ts +5 -12
  38. package/dist/service-schemas.gen.js +172 -84
  39. package/dist/types.d.ts +0 -1
  40. package/dist/types.js +0 -1
  41. package/package.json +1 -2
  42. package/dist/actions.gen.d.ts.map +0 -1
  43. package/dist/actions.gen.js.map +0 -1
  44. package/dist/annotations.d.ts.map +0 -1
  45. package/dist/annotations.js.map +0 -1
  46. package/dist/builder.d.ts.map +0 -1
  47. package/dist/builder.js.map +0 -1
  48. package/dist/context.gen.d.ts.map +0 -1
  49. package/dist/context.gen.js.map +0 -1
  50. package/dist/engine.d.ts.map +0 -1
  51. package/dist/engine.js.map +0 -1
  52. package/dist/engine.test.d.ts +0 -8
  53. package/dist/engine.test.d.ts.map +0 -1
  54. package/dist/engine.test.js +0 -190
  55. package/dist/engine.test.js.map +0 -1
  56. package/dist/entities.gen.d.ts.map +0 -1
  57. package/dist/entities.gen.js.map +0 -1
  58. package/dist/entity-metadata-types.gen.d.ts.map +0 -1
  59. package/dist/entity-metadata-types.gen.js.map +0 -1
  60. package/dist/errors.d.ts.map +0 -1
  61. package/dist/errors.js.map +0 -1
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/overwatch-context.gen.d.ts.map +0 -1
  65. package/dist/overwatch-context.gen.js.map +0 -1
  66. package/dist/overwatch-defaults.gen.d.ts.map +0 -1
  67. package/dist/overwatch-defaults.gen.js.map +0 -1
  68. package/dist/overwatch-defaults.test.d.ts +0 -8
  69. package/dist/overwatch-defaults.test.d.ts.map +0 -1
  70. package/dist/overwatch-defaults.test.js +0 -145
  71. package/dist/overwatch-defaults.test.js.map +0 -1
  72. package/dist/overwatch-entities.gen.d.ts.map +0 -1
  73. package/dist/overwatch-entities.gen.js.map +0 -1
  74. package/dist/overwatch-rebac.test.d.ts +0 -25
  75. package/dist/overwatch-rebac.test.d.ts.map +0 -1
  76. package/dist/overwatch-rebac.test.js +0 -301
  77. package/dist/overwatch-rebac.test.js.map +0 -1
  78. package/dist/palisade-context.gen.d.ts.map +0 -1
  79. package/dist/palisade-context.gen.js.map +0 -1
  80. package/dist/palisade-entities.gen.d.ts.map +0 -1
  81. package/dist/palisade-entities.gen.js.map +0 -1
  82. package/dist/parser.d.ts.map +0 -1
  83. package/dist/parser.js.map +0 -1
  84. package/dist/parser.test.d.ts +0 -8
  85. package/dist/parser.test.d.ts.map +0 -1
  86. package/dist/parser.test.js +0 -212
  87. package/dist/parser.test.js.map +0 -1
  88. package/dist/schema.gen.d.ts.map +0 -1
  89. package/dist/schema.gen.js.map +0 -1
  90. package/dist/schemas.d.ts.map +0 -1
  91. package/dist/schemas.js.map +0 -1
  92. package/dist/schemas.test.d.ts +0 -8
  93. package/dist/schemas.test.d.ts.map +0 -1
  94. package/dist/schemas.test.js +0 -407
  95. package/dist/schemas.test.js.map +0 -1
  96. package/dist/service-schemas.gen.d.ts.map +0 -1
  97. package/dist/service-schemas.gen.js.map +0 -1
  98. package/dist/studio-ui.test.d.ts +0 -8
  99. package/dist/studio-ui.test.d.ts.map +0 -1
  100. package/dist/studio-ui.test.js +0 -687
  101. package/dist/studio-ui.test.js.map +0 -1
  102. package/dist/types.d.ts.map +0 -1
  103. package/dist/types.js.map +0 -1
  104. package/src/actions.gen.ts +0 -57
  105. package/src/annotations.ts +0 -243
  106. package/src/builder.ts +0 -799
  107. package/src/context.gen.ts +0 -10
  108. package/src/engine.test.ts +0 -370
  109. package/src/engine.ts +0 -497
  110. package/src/entities.gen.ts +0 -65
  111. package/src/entity-metadata-types.gen.ts +0 -19
  112. package/src/errors.ts +0 -195
  113. package/src/index.ts +0 -62
  114. package/src/overwatch-context.gen.ts +0 -45
  115. package/src/overwatch-defaults.gen.ts +0 -1255
  116. package/src/overwatch-defaults.test.ts +0 -176
  117. package/src/overwatch-entities.gen.ts +0 -41
  118. package/src/overwatch-rebac.test.ts +0 -346
  119. package/src/palisade-context.gen.ts +0 -28
  120. package/src/palisade-entities.gen.ts +0 -49
  121. package/src/parser.test.ts +0 -251
  122. package/src/parser.ts +0 -579
  123. package/src/schema.gen.ts +0 -134
  124. package/src/schemas.test.ts +0 -477
  125. package/src/schemas.ts +0 -91
  126. package/src/service-schemas.gen.ts +0 -608
  127. package/src/studio-ui.test.ts +0 -813
  128. package/src/types.ts +0 -66
package/dist/builder.js CHANGED
@@ -133,8 +133,11 @@ export class Policy {
133
133
  /**
134
134
  * Convert to Cedar policy text.
135
135
  * Uses proper Cedar @annotation syntax.
136
+ *
137
+ * @param optionalFields - Set of context field names that are optional and need `context has` guards.
138
+ * Use `getOptionalFields()` to compute this from service context metadata.
136
139
  */
137
- toCedar() {
140
+ toCedar(optionalFields) {
138
141
  const lines = [];
139
142
  // Generate proper Cedar annotations
140
143
  if (this.data.id || this.data.name) {
@@ -145,7 +148,7 @@ export class Policy {
145
148
  lines.push(...generateAnnotationLines(annotations));
146
149
  }
147
150
  // Generate policy body
148
- lines.push(generatePolicyBody(this.data.effect, this.data.principal, this.data.action, this.data.resource, this.data.conditions, this.data.rawCondition));
151
+ lines.push(generatePolicyBody(this.data.effect, this.data.principal, this.data.action, this.data.resource, this.data.conditions, this.data.rawCondition, optionalFields));
149
152
  return lines.join('\n');
150
153
  }
151
154
  /**
@@ -170,40 +173,99 @@ export class Policy {
170
173
  // ============================================================================
171
174
  // Cedar Generation Functions
172
175
  // ============================================================================
176
+ /**
177
+ * Get the set of optional context fields for the given action(s).
178
+ *
179
+ * A field is considered optional if it has `required: false` in ANY of the
180
+ * targeted actions. This is the safe choice because at evaluation time,
181
+ * the policy could be matched against any of the specified actions.
182
+ *
183
+ * @param serviceContext - The service context metadata (from OVERWATCH_CONTEXT, etc.)
184
+ * @param actions - Single action name or array of action names
185
+ * @returns Set of field names that are optional and need `context has` guards
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * import { OVERWATCH_CONTEXT } from '@highflame/policy/types';
190
+ *
191
+ * const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
192
+ * // Set { 'tool_name', 'mcp_server', 'threat_count', ... }
193
+ *
194
+ * const cedar = ruleToCedar(rule, optionalFields);
195
+ * // Conditions on optional fields auto-get `context has` guards
196
+ * ```
197
+ */
198
+ export function getOptionalFields(serviceContext, actions) {
199
+ const actionList = Array.isArray(actions) ? actions : [actions];
200
+ const optionalFields = new Set();
201
+ for (const actionName of actionList) {
202
+ const action = serviceContext.actions.find(a => a.name === actionName);
203
+ if (action) {
204
+ for (const attr of action.context_attributes) {
205
+ if (!attr.required) {
206
+ optionalFields.add(attr.key);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ return optionalFields;
212
+ }
173
213
  /**
174
214
  * Convert a condition to Cedar syntax.
175
215
  * Field names are sanitized to prevent injection attacks.
216
+ *
217
+ * When `optionalFields` is provided and the condition's field is in the set,
218
+ * the output is wrapped with a `context has` guard:
219
+ * `context has field && context.field > value`
176
220
  */
177
- function conditionToCedar(condition) {
221
+ function conditionToCedar(condition, optionalFields) {
178
222
  const field = sanitizeIdentifier(condition.field, 'field');
179
223
  const { operator, value } = condition;
180
224
  const valueStr = valueToString(value);
225
+ let expr;
181
226
  switch (operator) {
182
227
  case 'eq':
183
- return `context.${field} == ${valueStr}`;
228
+ expr = `context.${field} == ${valueStr}`;
229
+ break;
184
230
  case 'neq':
185
- return `context.${field} != ${valueStr}`;
231
+ expr = `context.${field} != ${valueStr}`;
232
+ break;
186
233
  case 'lt':
187
- return `context.${field} < ${valueStr}`;
234
+ expr = `context.${field} < ${valueStr}`;
235
+ break;
188
236
  case 'lte':
189
- return `context.${field} <= ${valueStr}`;
237
+ expr = `context.${field} <= ${valueStr}`;
238
+ break;
190
239
  case 'gt':
191
- return `context.${field} > ${valueStr}`;
240
+ expr = `context.${field} > ${valueStr}`;
241
+ break;
192
242
  case 'gte':
193
- return `context.${field} >= ${valueStr}`;
243
+ expr = `context.${field} >= ${valueStr}`;
244
+ break;
194
245
  case 'contains':
195
- return `context.${field}.contains(${valueStr})`;
246
+ expr = `context.${field}.contains(${valueStr})`;
247
+ break;
196
248
  case 'in':
197
249
  if (Array.isArray(value)) {
198
250
  const items = value.map(v => `"${escapeCedarString(v)}"`).join(', ');
199
- return `context.${field} in [${items}]`;
251
+ expr = `context.${field} in [${items}]`;
252
+ }
253
+ else {
254
+ expr = `context.${field} in ${valueStr}`;
200
255
  }
201
- return `context.${field} in ${valueStr}`;
256
+ break;
202
257
  case 'like':
203
- return `context.${field} like ${valueStr}`;
258
+ expr = `context.${field} like ${valueStr}`;
259
+ break;
204
260
  default:
205
- return `context.${field} == ${valueStr}`;
261
+ expr = `context.${field} == ${valueStr}`;
262
+ break;
263
+ }
264
+ // Auto-inject `context has` guard for optional fields
265
+ if (optionalFields?.has(field)) {
266
+ return `context has ${field} && ${expr}`;
206
267
  }
268
+ return expr;
207
269
  }
208
270
  /**
209
271
  * Convert a value to Cedar string representation.
@@ -225,7 +287,7 @@ function valueToString(value) {
225
287
  * Generate the Cedar policy body (permit/forbid statement).
226
288
  * All inputs are sanitized/escaped to prevent injection attacks.
227
289
  */
228
- function generatePolicyBody(effect, principal, action, resource, conditions, rawCondition) {
290
+ function generatePolicyBody(effect, principal, action, resource, conditions, rawCondition, optionalFields) {
229
291
  let policyLine = `${effect} (`;
230
292
  // Principal
231
293
  if (principal) {
@@ -283,7 +345,7 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
283
345
  }
284
346
  else if (conditions.length > 0) {
285
347
  // Fallback to structured conditions if rawCondition is rejected
286
- const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
348
+ const conditionStr = conditions.map(c => conditionToCedar(c, optionalFields)).join(' && ');
287
349
  policyLine += `\nwhen { ${conditionStr} };`;
288
350
  }
289
351
  else {
@@ -291,7 +353,7 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
291
353
  }
292
354
  }
293
355
  else if (conditions.length > 0) {
294
- const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
356
+ const conditionStr = conditions.map(c => conditionToCedar(c, optionalFields)).join(' && ');
295
357
  policyLine += `\nwhen { ${conditionStr} };`;
296
358
  }
297
359
  else {
@@ -303,6 +365,8 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
303
365
  * Convert a PolicyRule to Cedar policy text with proper annotations.
304
366
  *
305
367
  * @param rule - The PolicyRule to convert
368
+ * @param optionalFields - Set of context field names that are optional and need `context has` guards.
369
+ * Use `getOptionalFields()` to compute this from service context metadata.
306
370
  * @returns Cedar policy text string
307
371
  *
308
372
  * @example
@@ -318,25 +382,22 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
318
382
  * order: 0,
319
383
  * };
320
384
  *
385
+ * // Without optional fields - no guards injected
321
386
  * const cedar = ruleToCedar(rule);
322
- * // Output:
323
- * // @id("rule-001")
324
- * // @name("Block threats")
325
- * // @severity("high")
326
- * // forbid (
327
- * // principal,
328
- * // action == Action::"call_tool",
329
- * // resource
330
- * // )
331
- * // when { context.threat_count > 0 };
387
+ *
388
+ * // With optional fields - auto-injects `context has` guards
389
+ * import { OVERWATCH_CONTEXT } from '@highflame/policy/types';
390
+ * const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
391
+ * const cedarWithGuards = ruleToCedar(rule, optionalFields);
392
+ * // when { context has threat_count && context.threat_count > 0 };
332
393
  * ```
333
394
  */
334
- export function ruleToCedar(rule) {
395
+ export function ruleToCedar(rule, optionalFields) {
335
396
  const lines = [];
336
397
  // Generate Cedar annotations
337
398
  lines.push(...generateAnnotationLines(rule.annotations, rule.customAnnotations));
338
399
  // Generate policy body
339
- lines.push(generatePolicyBody(rule.effect, rule.principal, rule.action, rule.resource, rule.conditions, rule.rawCondition));
400
+ lines.push(generatePolicyBody(rule.effect, rule.principal, rule.action, rule.resource, rule.conditions, rule.rawCondition, optionalFields));
340
401
  return lines.join('\n');
341
402
  }
342
403
  /**
@@ -345,24 +406,30 @@ export function ruleToCedar(rule) {
345
406
  *
346
407
  * @param rules - Array of PolicyRules to convert
347
408
  * @param includeDisabled - If true, include disabled rules as comments (default: false)
409
+ * @param optionalFields - Set of context field names that are optional and need `context has` guards.
410
+ * Use `getOptionalFields()` to compute this from service context metadata.
348
411
  * @returns Cedar policy text with all rules separated by blank lines
349
412
  *
350
413
  * @example
351
414
  * ```typescript
352
415
  * const rules: PolicyRule[] = [...];
353
416
  * const cedarText = rulesToCedar(rules);
417
+ *
418
+ * // With optional fields awareness
419
+ * const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
420
+ * const cedarText = rulesToCedar(rules, false, optionalFields);
354
421
  * ```
355
422
  */
356
- export function rulesToCedar(rules, includeDisabled = false) {
423
+ export function rulesToCedar(rules, includeDisabled = false, optionalFields) {
357
424
  const sortedRules = [...rules].sort((a, b) => a.order - b.order);
358
425
  const cedarPolicies = [];
359
426
  for (const rule of sortedRules) {
360
427
  if (rule.enabled) {
361
- cedarPolicies.push(ruleToCedar(rule));
428
+ cedarPolicies.push(ruleToCedar(rule, optionalFields));
362
429
  }
363
430
  else if (includeDisabled) {
364
431
  // Include disabled rules as comments
365
- const cedarLines = ruleToCedar(rule).split('\n');
432
+ const cedarLines = ruleToCedar(rule, optionalFields).split('\n');
366
433
  cedarPolicies.push(cedarLines.map(line => `// [DISABLED] ${line}`).join('\n'));
367
434
  }
368
435
  }
@@ -586,4 +653,3 @@ export function parseCedarPolicy(cedarText) {
586
653
  return null;
587
654
  }
588
655
  }
589
- //# sourceMappingURL=builder.js.map
@@ -3,4 +3,3 @@
3
3
  */
4
4
  export declare const ContextKey: {};
5
5
  export type ContextKey = (typeof ContextKey)[keyof typeof ContextKey];
6
- //# sourceMappingURL=context.gen.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Context attribute keys for Cedar policy evaluation.
5
5
  */
6
6
  export const ContextKey = {};
7
- //# sourceMappingURL=context.gen.js.map
package/dist/engine.d.ts CHANGED
@@ -38,11 +38,21 @@ export interface EngineOptions {
38
38
  export declare class InputValidationError extends Error {
39
39
  constructor(message: string);
40
40
  }
41
+ /**
42
+ * A policy that contributed to the authorization decision,
43
+ * enriched with its Cedar annotations.
44
+ */
45
+ export interface DeterminingPolicy {
46
+ /** Policy ID (from @id annotation or positional fallback) */
47
+ id: string;
48
+ /** All annotations from this policy as key-value pairs */
49
+ annotations: Record<string, string>;
50
+ }
41
51
  export declare class Decision {
42
52
  readonly effect: "Allow" | "Deny";
43
- readonly determining_policies: string[];
53
+ readonly determining_policies: DeterminingPolicy[];
44
54
  readonly reason?: string;
45
- constructor(effect: "Allow" | "Deny", determining_policies: string[], reason?: string);
55
+ constructor(effect: "Allow" | "Deny", determining_policies: DeterminingPolicy[], reason?: string);
46
56
  isAllowed(): boolean;
47
57
  isDenied(): boolean;
48
58
  }
@@ -63,6 +73,7 @@ export interface EvaluateRequest {
63
73
  */
64
74
  export declare class PolicyEngine {
65
75
  private policySet;
76
+ private policyAnnotations;
66
77
  private schema;
67
78
  private options;
68
79
  private limits;
@@ -74,11 +85,13 @@ export declare class PolicyEngine {
74
85
  /**
75
86
  * Load a single Cedar policy text string.
76
87
  * Uses @id annotations as policy IDs when available.
88
+ * Stores all annotations per policy for enriching evaluation results.
77
89
  */
78
90
  loadPolicy(policy: string): void;
79
91
  /**
80
92
  * Load multiple Cedar policy texts (concatenated with newlines).
81
93
  * Uses @id annotations as policy IDs when available.
94
+ * Stores all annotations per policy for enriching evaluation results.
82
95
  */
83
96
  loadPolicies(policies: string[]): void;
84
97
  /**
@@ -89,6 +102,11 @@ export declare class PolicyEngine {
89
102
  * Load schema from a Cedar schema file.
90
103
  */
91
104
  loadSchemaFromFile(path: string): void;
105
+ /**
106
+ * Returns stored annotations for a given policy ID.
107
+ * Returns undefined if the policy ID is not found.
108
+ */
109
+ getPolicyAnnotations(policyId: string): Record<string, string> | undefined;
92
110
  /**
93
111
  * Evaluate a policy request and return a decision.
94
112
  * @throws InputValidationError if context validation fails
@@ -143,4 +161,3 @@ export declare function validatePolicy(schema: string, policy: string): {
143
161
  };
144
162
  export { EntityType, EntityUID, Entity, newEntityUID, newEntity } from "./entities.gen.js";
145
163
  export { ActionType, actionUID } from "./actions.gen.js";
146
- //# sourceMappingURL=engine.d.ts.map
package/dist/engine.js CHANGED
@@ -147,39 +147,51 @@ function parseActionString(action) {
147
147
  }
148
148
  return { type: actionType, id: actionId };
149
149
  }
150
+ /**
151
+ * Regex to extract Cedar annotations from policy text.
152
+ * Matches `@key("value")` with proper escaped-quote handling.
153
+ */
154
+ const ANNOTATION_REGEX = /@(\w+)\("((?:[^"\\]|\\.)*)"\)/g;
155
+ /**
156
+ * Extract all `@key("value")` annotations from a single Cedar policy text string.
157
+ */
158
+ function extractAnnotationsFromText(policyText) {
159
+ const annotations = {};
160
+ let match;
161
+ const regex = new RegExp(ANNOTATION_REGEX.source, ANNOTATION_REGEX.flags);
162
+ while ((match = regex.exec(policyText)) !== null) {
163
+ // Unescape the annotation value (reverse Cedar escaping)
164
+ annotations[match[1]] = match[2].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
165
+ }
166
+ return annotations;
167
+ }
150
168
  /**
151
169
  * Extract @id annotations from Cedar policy text and return a
152
- * Record<PolicyId, Policy> for cedar-wasm. This ensures that
153
- * determining_policies in evaluation results use the @id values
154
- * instead of positional IDs (policy0, policy1...).
155
- *
156
- * Falls back to the raw string when no @id annotations are found.
170
+ * Record<PolicyId, Policy> for cedar-wasm, along with a map of
171
+ * all annotations per policy for enriching evaluation results.
157
172
  */
158
- function extractPolicyIds(policyText) {
173
+ function extractPolicies(policyText) {
174
+ const annotationsMap = new Map();
159
175
  const parts = cedar.policySetTextToParts(policyText);
160
176
  if (parts.type !== "success" || parts.policies.length === 0) {
161
- return policyText;
177
+ return { policySet: policyText, annotations: annotationsMap };
162
178
  }
163
179
  const policyMap = {};
164
- let hasAnnotationIds = false;
165
180
  for (let i = 0; i < parts.policies.length; i++) {
166
181
  const policy = parts.policies[i];
167
- const idMatch = policy.match(/@id\("([^"]+)"\)/);
168
- if (idMatch) {
169
- policyMap[idMatch[1]] = policy;
170
- hasAnnotationIds = true;
171
- }
172
- else {
173
- policyMap[`policy${i}`] = policy;
174
- }
182
+ const policyAnnotations = extractAnnotationsFromText(policy);
183
+ const policyId = policyAnnotations["id"] || `policy${i}`;
184
+ policyMap[policyId] = policy;
185
+ annotationsMap.set(policyId, policyAnnotations);
175
186
  }
176
- return hasAnnotationIds ? policyMap : policyText;
187
+ return { policySet: policyMap, annotations: annotationsMap };
177
188
  }
178
189
  /**
179
190
  * PolicyEngine wraps cedar-wasm with Highflame schema types.
180
191
  */
181
192
  export class PolicyEngine {
182
193
  policySet = "";
194
+ policyAnnotations = new Map();
183
195
  schema;
184
196
  options;
185
197
  limits;
@@ -203,16 +215,22 @@ export class PolicyEngine {
203
215
  /**
204
216
  * Load a single Cedar policy text string.
205
217
  * Uses @id annotations as policy IDs when available.
218
+ * Stores all annotations per policy for enriching evaluation results.
206
219
  */
207
220
  loadPolicy(policy) {
208
- this.policySet = extractPolicyIds(policy);
221
+ const extracted = extractPolicies(policy);
222
+ this.policySet = extracted.policySet;
223
+ this.policyAnnotations = extracted.annotations;
209
224
  }
210
225
  /**
211
226
  * Load multiple Cedar policy texts (concatenated with newlines).
212
227
  * Uses @id annotations as policy IDs when available.
228
+ * Stores all annotations per policy for enriching evaluation results.
213
229
  */
214
230
  loadPolicies(policies) {
215
- this.policySet = extractPolicyIds(policies.join("\n"));
231
+ const extracted = extractPolicies(policies.join("\n"));
232
+ this.policySet = extracted.policySet;
233
+ this.policyAnnotations = extracted.annotations;
216
234
  }
217
235
  /**
218
236
  * Load schema from a Cedar schema string.
@@ -227,6 +245,13 @@ export class PolicyEngine {
227
245
  const content = fs.readFileSync(path, "utf-8");
228
246
  this.loadSchema(content);
229
247
  }
248
+ /**
249
+ * Returns stored annotations for a given policy ID.
250
+ * Returns undefined if the policy ID is not found.
251
+ */
252
+ getPolicyAnnotations(policyId) {
253
+ return this.policyAnnotations.get(policyId);
254
+ }
230
255
  /**
231
256
  * Evaluate a policy request and return a decision.
232
257
  * @throws InputValidationError if context validation fails
@@ -276,7 +301,12 @@ export class PolicyEngine {
276
301
  if (result.type === "failure") {
277
302
  return new Decision("Deny", [], result.errors.map(e => e.message).join("; "));
278
303
  }
279
- return new Decision(result.response.decision === "allow" ? "Allow" : "Deny", result.response.diagnostics.reason, result.response.diagnostics.errors.length > 0
304
+ // Build enriched DeterminingPolicy objects with annotations
305
+ const determiningPolicies = result.response.diagnostics.reason.map(id => ({
306
+ id,
307
+ annotations: this.policyAnnotations.get(id) || {},
308
+ }));
309
+ return new Decision(result.response.decision === "allow" ? "Allow" : "Deny", determiningPolicies, result.response.diagnostics.errors.length > 0
280
310
  ? result.response.diagnostics.errors.map(e => e.error.message).join("; ")
281
311
  : undefined);
282
312
  }
@@ -374,4 +404,3 @@ export function validatePolicy(schema, policy) {
374
404
  // Re-export types
375
405
  export { EntityType, newEntityUID, newEntity } from "./entities.gen.js";
376
406
  export { ActionType, actionUID } from "./actions.gen.js";
377
- //# sourceMappingURL=engine.js.map
@@ -47,4 +47,3 @@ export declare function newEntityUID(type: EntityType | string, id: string): Ent
47
47
  * Create a new Entity.
48
48
  */
49
49
  export declare function newEntity(type: EntityType | string, id: string, attrs?: Record<string, unknown>): Entity;
50
- //# sourceMappingURL=entities.gen.d.ts.map
@@ -41,4 +41,3 @@ export function newEntity(type, id, attrs) {
41
41
  parents: [],
42
42
  };
43
43
  }
44
- //# sourceMappingURL=entities.gen.js.map
@@ -14,4 +14,3 @@ export interface ActionEntityMetadata {
14
14
  readonly principals: readonly string[];
15
15
  readonly resources: readonly string[];
16
16
  }
17
- //# sourceMappingURL=entity-metadata-types.gen.d.ts.map
@@ -1,3 +1,2 @@
1
1
  // Code generated by highflame-policy-codegen. DO NOT EDIT.
2
2
  export {};
3
- //# sourceMappingURL=entity-metadata-types.gen.js.map
package/dist/errors.d.ts CHANGED
@@ -99,4 +99,3 @@ export declare class ParserError extends Error {
99
99
  /** Action scope is nil */
100
100
  static actionScopeNil(): ParserError;
101
101
  }
102
- //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js CHANGED
@@ -124,4 +124,3 @@ export class ParserError extends Error {
124
124
  return new ParserError(ErrorCodes.ACTION_SCOPE_NIL, "Action scope is nil", { field: "action" });
125
125
  }
126
126
  }
127
- //# sourceMappingURL=errors.js.map
package/dist/index.d.ts CHANGED
@@ -16,4 +16,3 @@ export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entitie
16
16
  export type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';
17
17
  export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
18
18
  export type { OverwatchCategory, OverwatchCategoryInfo, OverwatchDefaultPolicy, OverwatchTemplate, } from './overwatch-defaults.gen.js';
19
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -23,4 +23,3 @@ export { OVERWATCH_ENTITIES, OVERWATCH_ACTION_ENTITIES, } from './overwatch-enti
23
23
  export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entities.gen.js';
24
24
  // Service-specific default policies, templates, and categories
25
25
  export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
26
- //# sourceMappingURL=index.js.map
@@ -39,4 +39,3 @@ export declare const OverwatchContextKey: {
39
39
  readonly YaraThreats: "yara_threats";
40
40
  };
41
41
  export type OverwatchContextKey = (typeof OverwatchContextKey)[keyof typeof OverwatchContextKey];
42
- //# sourceMappingURL=overwatch-context.gen.d.ts.map
@@ -40,4 +40,3 @@ export const OverwatchContextKey = {
40
40
  WorkspaceRoot: 'workspace_root',
41
41
  YaraThreats: 'yara_threats',
42
42
  };
43
- //# sourceMappingURL=overwatch-context.gen.js.map
@@ -59,4 +59,3 @@ export declare const OVERWATCH_TEMPLATES_JSON: string;
59
59
  export declare function getOverwatchDefaultsByCategory(category: OverwatchCategory): OverwatchDefaultPolicy[];
60
60
  export declare function getOverwatchTemplatesByCategory(category: OverwatchCategory): OverwatchTemplate[];
61
61
  export declare function getOverwatchTemplateById(id: string): OverwatchTemplate | undefined;
62
- //# sourceMappingURL=overwatch-defaults.gen.d.ts.map