@highflame/policy 1.1.3 → 1.2.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highflame/policy",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Highflame Cedar policy types and engine wrapper",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,13 +42,15 @@
42
42
  "scripts": {
43
43
  "build": "tsc",
44
44
  "clean": "rm -rf dist",
45
+ "test": "vitest run",
45
46
  "prepublishOnly": "npm run build"
46
47
  },
47
48
  "dependencies": {
48
49
  "@cedar-policy/cedar-wasm": "^4.0.0"
49
50
  },
50
51
  "devDependencies": {
51
- "typescript": "^5.3.0"
52
+ "typescript": "^5.3.0",
53
+ "vitest": "^2.0.0"
52
54
  },
53
55
  "files": [
54
56
  "dist",
@@ -7,20 +7,41 @@ import { EntityUID } from './entities.gen.js';
7
7
  * Action types defined in the Highflame Cedar schema.
8
8
  */
9
9
  export const ActionType = {
10
+ AccessMemory: 'access_memory',
10
11
  AccessServerResource: 'access_server_resource',
12
+ CallExternalApi: 'call_external_api',
11
13
  CallTool: 'call_tool',
12
14
  ConnectServer: 'connect_server',
15
+ DelegateTask: 'delegate_task',
16
+ DeleteFile: 'delete_file',
13
17
  DeployModel: 'deploy_model',
18
+ ExecuteCode: 'execute_code',
19
+ ExportData: 'export_data',
20
+ FilterContent: 'filter_content',
21
+ GitCheckout: 'git_checkout',
22
+ GitClone: 'git_clone',
23
+ GitCommit: 'git_commit',
24
+ GitMerge: 'git_merge',
25
+ GitOperation: 'git_operation',
26
+ GitPull: 'git_pull',
27
+ GitPush: 'git_push',
28
+ GitRebase: 'git_rebase',
29
+ GitReset: 'git_reset',
14
30
  HttpRequest: 'http_request',
31
+ InvokeModel: 'invoke_model',
15
32
  LoadModel: 'load_model',
16
33
  ProcessPrompt: 'process_prompt',
17
34
  ProcessResponse: 'process_response',
18
35
  QuarantineArtifact: 'quarantine_artifact',
19
36
  ReadFile: 'read_file',
37
+ RunBuild: 'run_build',
38
+ RunTests: 'run_tests',
20
39
  ScanArtifact: 'scan_artifact',
21
40
  ScanPackage: 'scan_package',
22
41
  ScanTarget: 'scan_target',
23
42
  SkipGuardrails: 'skip_guardrails',
43
+ SpawnSubprocess: 'spawn_subprocess',
44
+ TransferData: 'transfer_data',
24
45
  ValidateIntegrity: 'validate_integrity',
25
46
  ValidateProvenance: 'validate_provenance',
26
47
  WriteFile: 'write_file',
package/src/builder.ts CHANGED
@@ -58,7 +58,31 @@ export interface PolicyCondition {
58
58
  }
59
59
 
60
60
  /**
61
- * JSON representation of a policy for storage and editing
61
+ * Principal or resource entity constraint.
62
+ * Used to specify type-only constraints (any entity of type) or
63
+ * specific entity constraints (type + id).
64
+ */
65
+ export interface PolicyEntity {
66
+ /** Entity type (e.g., "Agent", "Tool", "FilePath", "User") */
67
+ type: string;
68
+ /** Optional specific entity ID. If omitted, matches any entity of this type. */
69
+ id?: string;
70
+ }
71
+
72
+ /** Alias for PolicyEntity when used as principal constraint */
73
+ export type PolicyPrincipal = PolicyEntity;
74
+
75
+ /** Alias for PolicyEntity when used as resource constraint */
76
+ export type PolicyResource = PolicyEntity;
77
+
78
+ /**
79
+ * Rule severity levels for UI display and prioritization
80
+ */
81
+ export type PolicySeverity = 'critical' | 'high' | 'medium' | 'low';
82
+
83
+ /**
84
+ * JSON representation of a policy for storage and editing.
85
+ * This is the base interface used by PolicyBuilder.
62
86
  */
63
87
  export interface PolicyJSON {
64
88
  /** Unique identifier for this policy */
@@ -68,23 +92,41 @@ export interface PolicyJSON {
68
92
  /** Policy effect */
69
93
  effect: PolicyEffect;
70
94
  /** Principal constraint */
71
- principal: {
72
- type: string;
73
- id?: string;
74
- } | null;
75
- /** Action constraint */
95
+ principal: PolicyEntity | null;
96
+ /** Action constraint - single action or array of actions */
76
97
  action: string | string[];
77
98
  /** Resource constraint */
78
- resource: {
79
- type: string;
80
- id?: string;
81
- } | null;
99
+ resource: PolicyEntity | null;
82
100
  /** Conditions (when clause) */
83
101
  conditions: PolicyCondition[];
84
102
  /** Raw condition string (for advanced users) */
85
103
  rawCondition?: string;
86
104
  }
87
105
 
106
+ /**
107
+ * A policy rule with UI/storage metadata.
108
+ * Extends PolicyJSON with fields needed for UI editing and database storage.
109
+ *
110
+ * This is the canonical type used across all Highflame services:
111
+ * - highflame-studio (UI)
112
+ * - highflame-authz (Go backend)
113
+ * - Any Python services
114
+ *
115
+ * Each PolicyRule maps 1:1 to a Cedar policy statement.
116
+ */
117
+ export interface PolicyRule extends PolicyJSON {
118
+ /** Whether this rule is active (used for toggling rules on/off in UI) */
119
+ enabled: boolean;
120
+ /** Display/evaluation order (0-indexed) */
121
+ order: number;
122
+ /** Optional description (separate from name for longer explanations) */
123
+ description?: string;
124
+ /** Rule severity for display and prioritization */
125
+ severity?: PolicySeverity;
126
+ /** Optional tags for categorization and filtering */
127
+ tags?: string[];
128
+ }
129
+
88
130
  /**
89
131
  * A built policy that can be converted to Cedar text or JSON
90
132
  */
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Engine unit tests
3
+ *
4
+ * Tests the PolicyEngine evaluate() function.
5
+ * These tests are consistent across Go, TypeScript, and Python SDKs.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from 'vitest';
9
+ import {
10
+ PolicyEngine,
11
+ Decision,
12
+ EntityType,
13
+ ActionType,
14
+ ContextKey,
15
+ InputValidationError,
16
+ DEFAULT_LIMITS,
17
+ } from './index.js';
18
+
19
+ describe('PolicyEngine', () => {
20
+ let engine: PolicyEngine;
21
+
22
+ const permitAllPolicy = `
23
+ permit(principal, action, resource);
24
+ `;
25
+
26
+ const denyAllPolicy = `
27
+ forbid(principal, action, resource);
28
+ `;
29
+
30
+ // Simple context-based policy without action constraint for testing context evaluation
31
+ const contextBasedPolicy = `
32
+ @id("allow-production")
33
+ permit(
34
+ principal,
35
+ action,
36
+ resource
37
+ )
38
+ when { context.environment == "production" };
39
+
40
+ @id("deny-all")
41
+ forbid(principal, action, resource);
42
+ `;
43
+
44
+ beforeEach(() => {
45
+ engine = new PolicyEngine();
46
+ });
47
+
48
+ describe('basic evaluation', () => {
49
+ it('should allow when permit policy matches', () => {
50
+ engine.loadPolicies(permitAllPolicy);
51
+
52
+ const decision = engine.evaluateSimple(
53
+ EntityType.Scanner,
54
+ 'test-scanner',
55
+ ActionType.ScanArtifact,
56
+ EntityType.Artifact,
57
+ '/model.safetensors'
58
+ );
59
+
60
+ expect(decision.effect).toBe('Allow');
61
+ });
62
+
63
+ it('should deny when forbid policy matches', () => {
64
+ engine.loadPolicies(denyAllPolicy);
65
+
66
+ const decision = engine.evaluateSimple(
67
+ EntityType.Scanner,
68
+ 'test-scanner',
69
+ ActionType.ScanArtifact,
70
+ EntityType.Artifact,
71
+ '/model.safetensors'
72
+ );
73
+
74
+ expect(decision.effect).toBe('Deny');
75
+ });
76
+
77
+ it('should deny when no policies match (default deny)', () => {
78
+ engine.loadPolicies(''); // No policies
79
+
80
+ const decision = engine.evaluateSimple(
81
+ EntityType.Scanner,
82
+ 'test-scanner',
83
+ ActionType.ScanArtifact,
84
+ EntityType.Artifact,
85
+ '/model.safetensors'
86
+ );
87
+
88
+ expect(decision.effect).toBe('Deny');
89
+ });
90
+ });
91
+
92
+ describe('context-based evaluation', () => {
93
+ beforeEach(() => {
94
+ engine.loadPolicies(contextBasedPolicy);
95
+ });
96
+
97
+ it('should allow when context matches permit condition', () => {
98
+ // Use simple permit policy to test context evaluation in isolation
99
+ const simplePermitPolicy = `
100
+ permit(principal, action, resource)
101
+ when { context.environment == "production" };
102
+ `;
103
+ const testEngine = new PolicyEngine();
104
+ testEngine.loadPolicies(simplePermitPolicy);
105
+
106
+ const decision = testEngine.evaluateSimple(
107
+ EntityType.Scanner,
108
+ 'palisade',
109
+ ActionType.ScanArtifact,
110
+ EntityType.Artifact,
111
+ '/model.safetensors',
112
+ { environment: 'production' }
113
+ );
114
+
115
+ expect(decision.effect).toBe('Allow');
116
+ });
117
+
118
+ it('should deny when context does not match permit condition', () => {
119
+ const decision = engine.evaluateSimple(
120
+ EntityType.Scanner,
121
+ 'palisade',
122
+ ActionType.ScanArtifact,
123
+ EntityType.Artifact,
124
+ '/model.safetensors',
125
+ { environment: 'development' }
126
+ );
127
+
128
+ expect(decision.effect).toBe('Deny');
129
+ });
130
+
131
+ it('should deny when context is missing required field', () => {
132
+ const decision = engine.evaluateSimple(
133
+ EntityType.Scanner,
134
+ 'palisade',
135
+ ActionType.ScanArtifact,
136
+ EntityType.Artifact,
137
+ '/model.safetensors',
138
+ {} // Missing environment
139
+ );
140
+
141
+ expect(decision.effect).toBe('Deny');
142
+ });
143
+ });
144
+
145
+ describe('input validation', () => {
146
+ beforeEach(() => {
147
+ engine.loadPolicies(permitAllPolicy);
148
+ });
149
+
150
+ it('should accept valid context', () => {
151
+ const decision = engine.evaluateSimple(
152
+ EntityType.Scanner,
153
+ 'test',
154
+ ActionType.ScanArtifact,
155
+ EntityType.Artifact,
156
+ '/model.safetensors',
157
+ {
158
+ environment: 'production',
159
+ severity: 'HIGH',
160
+ count: 42,
161
+ enabled: true,
162
+ }
163
+ );
164
+
165
+ expect(decision.effect).toBe('Allow');
166
+ });
167
+
168
+ it('should reject context with too many keys', () => {
169
+ const engine = new PolicyEngine({ limits: { maxContextKeys: 5 } });
170
+ engine.loadPolicies(permitAllPolicy);
171
+
172
+ const bigContext: Record<string, unknown> = {};
173
+ for (let i = 0; i < 10; i++) {
174
+ bigContext[`key${i}`] = 'value';
175
+ }
176
+
177
+ expect(() => {
178
+ engine.evaluateSimple(
179
+ EntityType.Scanner,
180
+ 'test',
181
+ ActionType.ScanArtifact,
182
+ EntityType.Artifact,
183
+ '/model.safetensors',
184
+ bigContext
185
+ );
186
+ }).toThrow(InputValidationError);
187
+ });
188
+
189
+ it('should reject context with too long strings', () => {
190
+ const engine = new PolicyEngine({ limits: { maxStringLength: 100 } });
191
+ engine.loadPolicies(permitAllPolicy);
192
+
193
+ const longString = 'x'.repeat(200);
194
+
195
+ expect(() => {
196
+ engine.evaluateSimple(
197
+ EntityType.Scanner,
198
+ 'test',
199
+ ActionType.ScanArtifact,
200
+ EntityType.Artifact,
201
+ '/model.safetensors',
202
+ { value: longString }
203
+ );
204
+ }).toThrow(InputValidationError);
205
+ });
206
+
207
+ it('should reject deeply nested context', () => {
208
+ const engine = new PolicyEngine({ limits: { maxNestingDepth: 3 } });
209
+ engine.loadPolicies(permitAllPolicy);
210
+
211
+ const deepContext = {
212
+ level1: {
213
+ level2: {
214
+ level3: {
215
+ level4: {
216
+ level5: 'too deep',
217
+ },
218
+ },
219
+ },
220
+ },
221
+ };
222
+
223
+ expect(() => {
224
+ engine.evaluateSimple(
225
+ EntityType.Scanner,
226
+ 'test',
227
+ ActionType.ScanArtifact,
228
+ EntityType.Artifact,
229
+ '/model.safetensors',
230
+ deepContext
231
+ );
232
+ }).toThrow(InputValidationError);
233
+ });
234
+
235
+ it('should allow skipping validation', () => {
236
+ const engine = new PolicyEngine({
237
+ skipValidation: true,
238
+ limits: { maxContextKeys: 1 },
239
+ });
240
+ engine.loadPolicies(permitAllPolicy);
241
+
242
+ // This would normally fail validation
243
+ const decision = engine.evaluateSimple(
244
+ EntityType.Scanner,
245
+ 'test',
246
+ ActionType.ScanArtifact,
247
+ EntityType.Artifact,
248
+ '/model.safetensors',
249
+ { key1: 'value1', key2: 'value2', key3: 'value3' }
250
+ );
251
+
252
+ expect(decision.effect).toBe('Allow');
253
+ });
254
+ });
255
+
256
+ describe('complex context types', () => {
257
+ beforeEach(() => {
258
+ engine.loadPolicies(permitAllPolicy);
259
+ });
260
+
261
+ it('should handle array context values', () => {
262
+ const decision = engine.evaluateSimple(
263
+ EntityType.Scanner,
264
+ 'test',
265
+ ActionType.ScanArtifact,
266
+ EntityType.Artifact,
267
+ '/model.safetensors',
268
+ { threats: ['malware', 'backdoor', 'injection'] }
269
+ );
270
+
271
+ expect(decision.effect).toBe('Allow');
272
+ });
273
+
274
+ it('should handle nested object context', () => {
275
+ const decision = engine.evaluateSimple(
276
+ EntityType.Scanner,
277
+ 'test',
278
+ ActionType.ScanArtifact,
279
+ EntityType.Artifact,
280
+ '/model.safetensors',
281
+ {
282
+ metadata: {
283
+ format: 'safetensors',
284
+ size: 1024,
285
+ },
286
+ }
287
+ );
288
+
289
+ expect(decision.effect).toBe('Allow');
290
+ });
291
+
292
+ it('should handle empty context', () => {
293
+ // Cedar doesn't have null values - this tests that empty context works
294
+ const decision = engine.evaluateSimple(
295
+ EntityType.Scanner,
296
+ 'test',
297
+ ActionType.ScanArtifact,
298
+ EntityType.Artifact,
299
+ '/model.safetensors',
300
+ {}
301
+ );
302
+
303
+ expect(decision.effect).toBe('Allow');
304
+ });
305
+
306
+ it('should handle boolean context values', () => {
307
+ const decision = engine.evaluateSimple(
308
+ EntityType.Scanner,
309
+ 'test',
310
+ ActionType.ScanArtifact,
311
+ EntityType.Artifact,
312
+ '/model.safetensors',
313
+ { is_signed: true, is_malicious: false }
314
+ );
315
+
316
+ expect(decision.effect).toBe('Allow');
317
+ });
318
+
319
+ it('should handle integer context values', () => {
320
+ // Cedar uses Long type for numbers (integers only, no floats)
321
+ const decision = engine.evaluateSimple(
322
+ EntityType.Scanner,
323
+ 'test',
324
+ ActionType.ScanArtifact,
325
+ EntityType.Artifact,
326
+ '/model.safetensors',
327
+ { severity_score: 7, count: 100 }
328
+ );
329
+
330
+ expect(decision.effect).toBe('Allow');
331
+ });
332
+ });
333
+
334
+ describe('error handling', () => {
335
+ it('should handle invalid policy syntax gracefully', () => {
336
+ const invalidPolicy = `permit(principal, action, resource`; // Missing closing paren
337
+
338
+ expect(() => {
339
+ engine.loadPolicies(invalidPolicy);
340
+ engine.evaluateSimple(
341
+ EntityType.Scanner,
342
+ 'test',
343
+ ActionType.ScanArtifact,
344
+ EntityType.Artifact,
345
+ '/model.safetensors'
346
+ );
347
+ }).not.toThrow();
348
+
349
+ // Should return deny with reason
350
+ engine.loadPolicies(invalidPolicy);
351
+ const decision = engine.evaluateSimple(
352
+ EntityType.Scanner,
353
+ 'test',
354
+ ActionType.ScanArtifact,
355
+ EntityType.Artifact,
356
+ '/model.safetensors'
357
+ );
358
+
359
+ expect(decision.effect).toBe('Deny');
360
+ });
361
+ });
362
+
363
+ describe('DEFAULT_LIMITS', () => {
364
+ it('should have consistent default values', () => {
365
+ expect(DEFAULT_LIMITS.maxContextKeys).toBe(100);
366
+ expect(DEFAULT_LIMITS.maxStringLength).toBe(1_000_000);
367
+ expect(DEFAULT_LIMITS.maxNestingDepth).toBe(10);
368
+ expect(DEFAULT_LIMITS.maxContextSizeBytes).toBe(10_000_000);
369
+ });
370
+ });
371
+ });
package/src/engine.ts CHANGED
@@ -8,6 +8,132 @@ import { EntityType, EntityUID, Entity } from "./entities.gen.js";
8
8
  import { ActionType } from "./actions.gen.js";
9
9
  import { CEDAR_SCHEMA } from "./schema.gen.js";
10
10
 
11
+ // =============================================================================
12
+ // INPUT VALIDATION LIMITS (consistent across all language SDKs)
13
+ // =============================================================================
14
+
15
+ /**
16
+ * Default limits for input validation.
17
+ * These are consistent across Go, TypeScript, and Python SDKs.
18
+ */
19
+ export const DEFAULT_LIMITS = {
20
+ /** Maximum number of keys in a context map */
21
+ maxContextKeys: 100,
22
+ /** Maximum length of any string value (1MB) */
23
+ maxStringLength: 1_000_000,
24
+ /** Maximum nesting depth for objects/arrays */
25
+ maxNestingDepth: 10,
26
+ /** Maximum total context size in bytes (10MB) */
27
+ maxContextSizeBytes: 10_000_000,
28
+ } as const;
29
+
30
+ export interface InputLimits {
31
+ maxContextKeys?: number;
32
+ maxStringLength?: number;
33
+ maxNestingDepth?: number;
34
+ maxContextSizeBytes?: number;
35
+ }
36
+
37
+ export interface EngineOptions {
38
+ /** Custom input validation limits */
39
+ limits?: InputLimits;
40
+ /** Skip input validation (not recommended for production) */
41
+ skipValidation?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Error thrown when input validation fails.
46
+ */
47
+ export class InputValidationError extends Error {
48
+ constructor(message: string) {
49
+ super(message);
50
+ this.name = "InputValidationError";
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Validate context input against configured limits.
56
+ * @throws InputValidationError if validation fails
57
+ */
58
+ function validateContext(
59
+ context: Record<string, unknown> | undefined,
60
+ limits: Required<InputLimits>,
61
+ depth: number = 0
62
+ ): void {
63
+ if (!context) return;
64
+
65
+ // Check nesting depth
66
+ if (depth > limits.maxNestingDepth) {
67
+ throw new InputValidationError(
68
+ `Context nesting depth exceeds maximum of ${limits.maxNestingDepth}`
69
+ );
70
+ }
71
+
72
+ const keys = Object.keys(context);
73
+
74
+ // Check number of keys (only at top level)
75
+ if (depth === 0 && keys.length > limits.maxContextKeys) {
76
+ throw new InputValidationError(
77
+ `Context has ${keys.length} keys, exceeds maximum of ${limits.maxContextKeys}`
78
+ );
79
+ }
80
+
81
+ // Check total size (only at top level)
82
+ if (depth === 0) {
83
+ let contextStr: string;
84
+ try {
85
+ contextStr = JSON.stringify(context);
86
+ } catch (e) {
87
+ throw new InputValidationError(
88
+ `Context is invalid or too complex: ${e instanceof Error ? e.message : String(e)}`
89
+ );
90
+ }
91
+ if (contextStr.length > limits.maxContextSizeBytes) {
92
+ throw new InputValidationError(
93
+ `Context size (${contextStr.length} bytes) exceeds maximum of ${limits.maxContextSizeBytes} bytes`
94
+ );
95
+ }
96
+ }
97
+
98
+ // Validate each value
99
+ for (const value of Object.values(context)) {
100
+ validateValue(value, limits, depth);
101
+ }
102
+ }
103
+
104
+ function validateValue(
105
+ value: unknown,
106
+ limits: Required<InputLimits>,
107
+ depth: number
108
+ ): void {
109
+ if (value === null || value === undefined) return;
110
+
111
+ if (typeof value === "string") {
112
+ if (value.length > limits.maxStringLength) {
113
+ throw new InputValidationError(
114
+ `String value length (${value.length}) exceeds maximum of ${limits.maxStringLength}`
115
+ );
116
+ }
117
+ return;
118
+ }
119
+
120
+ if (Array.isArray(value)) {
121
+ if (depth + 1 > limits.maxNestingDepth) {
122
+ throw new InputValidationError(
123
+ `Array nesting depth exceeds maximum of ${limits.maxNestingDepth}`
124
+ );
125
+ }
126
+ for (const item of value) {
127
+ validateValue(item, limits, depth + 1);
128
+ }
129
+ return;
130
+ }
131
+
132
+ if (typeof value === "object") {
133
+ validateContext(value as Record<string, unknown>, limits, depth + 1);
134
+ }
135
+ }
136
+
11
137
  export interface Decision {
12
138
  effect: "Allow" | "Deny";
13
139
  determining_policies: string[];
@@ -50,6 +176,18 @@ function toCedarValue(value: unknown): cedar.CedarValueJson {
50
176
  export class PolicyEngine {
51
177
  private policies: string = "";
52
178
  private schema: string | undefined;
179
+ private options: EngineOptions;
180
+ private limits: Required<InputLimits>;
181
+
182
+ constructor(options?: EngineOptions) {
183
+ this.options = options ?? {};
184
+ this.limits = {
185
+ maxContextKeys: options?.limits?.maxContextKeys ?? DEFAULT_LIMITS.maxContextKeys,
186
+ maxStringLength: options?.limits?.maxStringLength ?? DEFAULT_LIMITS.maxStringLength,
187
+ maxNestingDepth: options?.limits?.maxNestingDepth ?? DEFAULT_LIMITS.maxNestingDepth,
188
+ maxContextSizeBytes: options?.limits?.maxContextSizeBytes ?? DEFAULT_LIMITS.maxContextSizeBytes,
189
+ };
190
+ }
53
191
 
54
192
  /**
55
193
  * Load policies from a Cedar policy string.
@@ -75,8 +213,14 @@ export class PolicyEngine {
75
213
 
76
214
  /**
77
215
  * Evaluate a policy request and return a decision.
216
+ * @throws InputValidationError if context validation fails
78
217
  */
79
218
  evaluate(req: EvaluateRequest): Decision {
219
+ // Validate input unless explicitly skipped
220
+ if (!this.options.skipValidation) {
221
+ validateContext(req.context, this.limits);
222
+ }
223
+
80
224
  // Build EntityUIDs in Cedar JSON format
81
225
  const principal: cedar.EntityUidJson = {
82
226
  type: req.principal.type,
@@ -135,6 +279,7 @@ export class PolicyEngine {
135
279
 
136
280
  /**
137
281
  * Convenience method for simple evaluations.
282
+ * @throws InputValidationError if context validation fails
138
283
  */
139
284
  evaluateSimple(
140
285
  principalType: EntityType,
@@ -7,8 +7,12 @@
7
7
  export const EntityType = {
8
8
  Agent: 'Agent',
9
9
  Artifact: 'Artifact',
10
+ ExternalAPI: 'ExternalAPI',
10
11
  FilePath: 'FilePath',
12
+ GitBranch: 'GitBranch',
11
13
  HttpEndpoint: 'HttpEndpoint',
14
+ Memory: 'Memory',
15
+ Model: 'Model',
12
16
  Package: 'Package',
13
17
  Repository: 'Repository',
14
18
  Resource: 'Resource',