cedar-mcp-server 1.0.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/.editorconfig +12 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release.yml +42 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +241 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +182 -0
- package/README.md +1635 -0
- package/SECURITY.md +37 -0
- package/dist/http-server.d.ts +61 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +194 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/policy-ast.d.ts +49 -0
- package/dist/parser/policy-ast.d.ts.map +1 -0
- package/dist/parser/policy-ast.js +311 -0
- package/dist/parser/policy-ast.js.map +1 -0
- package/dist/prompts/index.d.ts +38 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +172 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/ref-resolver.d.ts +23 -0
- package/dist/resources/ref-resolver.d.ts.map +1 -0
- package/dist/resources/ref-resolver.js +128 -0
- package/dist/resources/ref-resolver.js.map +1 -0
- package/dist/resources/store-manager.d.ts +64 -0
- package/dist/resources/store-manager.d.ts.map +1 -0
- package/dist/resources/store-manager.js +221 -0
- package/dist/resources/store-manager.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +539 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/advise/avp-rules.d.ts +49 -0
- package/dist/tools/advise/avp-rules.d.ts.map +1 -0
- package/dist/tools/advise/avp-rules.js +59 -0
- package/dist/tools/advise/avp-rules.js.map +1 -0
- package/dist/tools/advise/cedar-patterns.d.ts +24 -0
- package/dist/tools/advise/cedar-patterns.d.ts.map +1 -0
- package/dist/tools/advise/cedar-patterns.js +57 -0
- package/dist/tools/advise/cedar-patterns.js.map +1 -0
- package/dist/tools/advise/context-builder.d.ts +28 -0
- package/dist/tools/advise/context-builder.d.ts.map +1 -0
- package/dist/tools/advise/context-builder.js +89 -0
- package/dist/tools/advise/context-builder.js.map +1 -0
- package/dist/tools/advise/gotchas.d.ts +15 -0
- package/dist/tools/advise/gotchas.d.ts.map +1 -0
- package/dist/tools/advise/gotchas.js +83 -0
- package/dist/tools/advise/gotchas.js.map +1 -0
- package/dist/tools/advise.d.ts +96 -0
- package/dist/tools/advise.d.ts.map +1 -0
- package/dist/tools/advise.js +258 -0
- package/dist/tools/advise.js.map +1 -0
- package/dist/tools/authorize-batch.d.ts +35 -0
- package/dist/tools/authorize-batch.d.ts.map +1 -0
- package/dist/tools/authorize-batch.js +262 -0
- package/dist/tools/authorize-batch.js.map +1 -0
- package/dist/tools/authorize.d.ts +115 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +373 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/check-change.d.ts +19 -0
- package/dist/tools/check-change.d.ts.map +1 -0
- package/dist/tools/check-change.js +91 -0
- package/dist/tools/check-change.js.map +1 -0
- package/dist/tools/diff-schema.d.ts +103 -0
- package/dist/tools/diff-schema.d.ts.map +1 -0
- package/dist/tools/diff-schema.js +379 -0
- package/dist/tools/diff-schema.js.map +1 -0
- package/dist/tools/diff-stores.d.ts +45 -0
- package/dist/tools/diff-stores.d.ts.map +1 -0
- package/dist/tools/diff-stores.js +222 -0
- package/dist/tools/diff-stores.js.map +1 -0
- package/dist/tools/explain.d.ts +80 -0
- package/dist/tools/explain.d.ts.map +1 -0
- package/dist/tools/explain.js +187 -0
- package/dist/tools/explain.js.map +1 -0
- package/dist/tools/format.d.ts +11 -0
- package/dist/tools/format.d.ts.map +1 -0
- package/dist/tools/format.js +20 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/generate-sample.d.ts +28 -0
- package/dist/tools/generate-sample.d.ts.map +1 -0
- package/dist/tools/generate-sample.js +568 -0
- package/dist/tools/generate-sample.js.map +1 -0
- package/dist/tools/link-template.d.ts +17 -0
- package/dist/tools/link-template.d.ts.map +1 -0
- package/dist/tools/link-template.js +78 -0
- package/dist/tools/link-template.js.map +1 -0
- package/dist/tools/list-template-links.d.ts +16 -0
- package/dist/tools/list-template-links.d.ts.map +1 -0
- package/dist/tools/list-template-links.js +22 -0
- package/dist/tools/list-template-links.js.map +1 -0
- package/dist/tools/list-templates.d.ts +16 -0
- package/dist/tools/list-templates.d.ts.map +1 -0
- package/dist/tools/list-templates.js +36 -0
- package/dist/tools/list-templates.js.map +1 -0
- package/dist/tools/translate.d.ts +11 -0
- package/dist/tools/translate.d.ts.map +1 -0
- package/dist/tools/translate.js +53 -0
- package/dist/tools/translate.js.map +1 -0
- package/dist/tools/validate-entities.d.ts +19 -0
- package/dist/tools/validate-entities.d.ts.map +1 -0
- package/dist/tools/validate-entities.js +88 -0
- package/dist/tools/validate-entities.js.map +1 -0
- package/dist/tools/validate-schema.d.ts +22 -0
- package/dist/tools/validate-schema.d.ts.map +1 -0
- package/dist/tools/validate-schema.js +89 -0
- package/dist/tools/validate-schema.js.map +1 -0
- package/dist/tools/validate-template.d.ts +18 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +59 -0
- package/dist/tools/validate-template.js.map +1 -0
- package/dist/tools/validate.d.ts +90 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +351 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/utils/format-detector.d.ts +49 -0
- package/dist/utils/format-detector.d.ts.map +1 -0
- package/dist/utils/format-detector.js +298 -0
- package/dist/utils/format-detector.js.map +1 -0
- package/examples/README.md +36 -0
- package/examples/abac-multi-tenant/README.md +150 -0
- package/examples/abac-multi-tenant/entities/users-and-docs.json +33 -0
- package/examples/abac-multi-tenant/policies/member-read-internal.cedar +9 -0
- package/examples/abac-multi-tenant/policies/owner-full-access.cedar +9 -0
- package/examples/abac-multi-tenant/policies/premium-share-guard.cedar +9 -0
- package/examples/abac-multi-tenant/policies/private-doc-guard.cedar +13 -0
- package/examples/abac-multi-tenant/run.ts +92 -0
- package/examples/abac-multi-tenant/schema.json +60 -0
- package/examples/api-gateway-path-routing/README.md +154 -0
- package/examples/api-gateway-path-routing/entities/users-and-roles.json +20 -0
- package/examples/api-gateway-path-routing/policies/admin-full-access.cedar +6 -0
- package/examples/api-gateway-path-routing/policies/developer-projects.cedar +14 -0
- package/examples/api-gateway-path-routing/policies/viewer-readonly.cedar +10 -0
- package/examples/api-gateway-path-routing/run.ts +108 -0
- package/examples/api-gateway-path-routing/schema.json +54 -0
- package/examples/rbac-document-management/README.md +167 -0
- package/examples/rbac-document-management/entities/users-and-docs.json +43 -0
- package/examples/rbac-document-management/policies/admin.cedar +6 -0
- package/examples/rbac-document-management/policies/editor.cedar +6 -0
- package/examples/rbac-document-management/policies/top-secret-forbid.cedar +13 -0
- package/examples/rbac-document-management/policies/viewer.cedar +6 -0
- package/examples/rbac-document-management/run.ts +87 -0
- package/examples/rbac-document-management/schema.json +57 -0
- package/package.json +50 -0
- package/src/http-server.ts +239 -0
- package/src/index.ts +294 -0
- package/src/parser/policy-ast.ts +345 -0
- package/src/prompts/README.md +3 -0
- package/src/prompts/index.ts +217 -0
- package/src/resources/ref-resolver.ts +134 -0
- package/src/resources/store-manager.ts +248 -0
- package/src/server.ts +711 -0
- package/src/tools/advise/avp-rules.ts +70 -0
- package/src/tools/advise/cedar-patterns.ts +73 -0
- package/src/tools/advise/context-builder.ts +109 -0
- package/src/tools/advise/gotchas.ts +92 -0
- package/src/tools/advise.ts +366 -0
- package/src/tools/authorize-batch.ts +345 -0
- package/src/tools/authorize.ts +464 -0
- package/src/tools/check-change.ts +119 -0
- package/src/tools/diff-schema.ts +510 -0
- package/src/tools/diff-stores.ts +298 -0
- package/src/tools/explain.ts +278 -0
- package/src/tools/format.ts +33 -0
- package/src/tools/generate-sample.ts +665 -0
- package/src/tools/link-template.ts +109 -0
- package/src/tools/list-template-links.ts +41 -0
- package/src/tools/list-templates.ts +55 -0
- package/src/tools/translate.ts +66 -0
- package/src/tools/validate-entities.ts +125 -0
- package/src/tools/validate-schema.ts +128 -0
- package/src/tools/validate-template.ts +72 -0
- package/src/tools/validate.ts +459 -0
- package/src/utils/format-detector.ts +356 -0
- package/test/fixtures/docmgmt.ts +121 -0
- package/test/fixtures/multitenant.ts +163 -0
- package/test/index.test.ts +96 -0
- package/test/integration/e2e/behavior.test.ts +359 -0
- package/test/integration/e2e/edge-cases.test.ts +365 -0
- package/test/integration/e2e/failure-modes.test.ts +266 -0
- package/test/integration/e2e/protocol.test.ts +252 -0
- package/test/integration/http-smoke.test.ts +588 -0
- package/test/integration/smoke.test.ts +475 -0
- package/test/prompts/prompts.test.ts +173 -0
- package/test/property/properties.test.ts +234 -0
- package/test/resources/ref-resolver.test.ts +186 -0
- package/test/resources/store-manager.test.ts +344 -0
- package/test/setup.test.ts +7 -0
- package/test/tools/advise/avp-rules.test.ts +76 -0
- package/test/tools/advise.test.ts +339 -0
- package/test/tools/authorize-batch.test.ts +459 -0
- package/test/tools/authorize.test.ts +682 -0
- package/test/tools/check-change.test.ts +104 -0
- package/test/tools/cross-fixture.test.ts +170 -0
- package/test/tools/diff-schema.test.ts +355 -0
- package/test/tools/diff-stores.test.ts +291 -0
- package/test/tools/explain.test.ts +221 -0
- package/test/tools/format.test.ts +33 -0
- package/test/tools/generate-sample.test.ts +480 -0
- package/test/tools/link-template.test.ts +90 -0
- package/test/tools/list-templates.test.ts +151 -0
- package/test/tools/translate.test.ts +89 -0
- package/test/tools/validate-entities.test.ts +178 -0
- package/test/tools/validate-schema.test.ts +86 -0
- package/test/tools/validate-template.test.ts +89 -0
- package/test/tools/validate.test.ts +331 -0
- package/test/utils/format-detector.test.ts +518 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { handleValidate } from "../../src/tools/validate.js";
|
|
6
|
+
import { storeManager } from "../../src/resources/store-manager.js";
|
|
7
|
+
import { POLICIES, SCHEMA_JSON } from "../fixtures/docmgmt.js";
|
|
8
|
+
|
|
9
|
+
const SCHEMA_STR = JSON.stringify(SCHEMA_JSON);
|
|
10
|
+
|
|
11
|
+
// Minimal Cedar schema reused for the 10d auto-discovery fixtures below.
|
|
12
|
+
const DOCMGMT_SCHEMA_CEDARSCHEMA = `namespace DocMgmt {
|
|
13
|
+
entity User in [Role] = { name: String, email: String };
|
|
14
|
+
entity Role;
|
|
15
|
+
entity Document in [Folder] = { owner: String, classification: String };
|
|
16
|
+
entity Folder;
|
|
17
|
+
action READ appliesTo { principal: [User], resource: [Document], context: {} };
|
|
18
|
+
action WRITE appliesTo { principal: [User], resource: [Document], context: {} };
|
|
19
|
+
action DELETE appliesTo { principal: [User], resource: [Document], context: {} };
|
|
20
|
+
}`;
|
|
21
|
+
|
|
22
|
+
function makeAutoDiscoveryStore(name: string): string {
|
|
23
|
+
const dir = mkdtempSync(join(tmpdir(), `cedar-validate-auto-${name}-`));
|
|
24
|
+
mkdirSync(join(dir, "policies"), { recursive: true });
|
|
25
|
+
writeFileSync(join(dir, "schema.cedarschema"), DOCMGMT_SCHEMA_CEDARSCHEMA);
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("cedar_validate", () => {
|
|
30
|
+
it("returns valid for correct policies against schema", async () => {
|
|
31
|
+
const result = await handleValidate({ policies: POLICIES, schema: SCHEMA_STR });
|
|
32
|
+
|
|
33
|
+
expect(result.valid).toBe(true);
|
|
34
|
+
expect(result.errors).toHaveLength(0);
|
|
35
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns invalid for a policy referencing a non-existent attribute", async () => {
|
|
39
|
+
const bad = `permit(principal, action, resource) when { resource.nonexistent == "x" };`;
|
|
40
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
41
|
+
|
|
42
|
+
expect(result.valid).toBe(false);
|
|
43
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
44
|
+
expect(result.errors[0].message).toContain("nonexistent");
|
|
45
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("includes policy_count in result", async () => {
|
|
49
|
+
const result = await handleValidate({ policies: POLICIES, schema: SCHEMA_STR });
|
|
50
|
+
|
|
51
|
+
expect(result.policy_count).toBe(4);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("accepts Cedar text schema format", async () => {
|
|
55
|
+
const cedarSchema = `
|
|
56
|
+
namespace DocMgmt {
|
|
57
|
+
entity User in [Role] = { name: String, email: String };
|
|
58
|
+
entity Role;
|
|
59
|
+
entity Document in [Folder] = { owner: String, classification: String };
|
|
60
|
+
entity Folder;
|
|
61
|
+
action READ appliesTo { principal: [User], resource: [Document], context: {} };
|
|
62
|
+
action WRITE appliesTo { principal: [User], resource: [Document], context: {} };
|
|
63
|
+
action DELETE appliesTo { principal: [User], resource: [Document], context: {} };
|
|
64
|
+
}
|
|
65
|
+
`.trim();
|
|
66
|
+
|
|
67
|
+
const result = await handleValidate({ policies: POLICIES, schema: cedarSchema });
|
|
68
|
+
expect(result.valid).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("cedar_validate — 10a syntax-only mode (no schema)", () => {
|
|
73
|
+
it("returns parse error with typo hint when schema is omitted and policy contains 'prinicpal'", async () => {
|
|
74
|
+
const bad = `permit (prinicpal in MyApp::Role::"admin", action, resource);`;
|
|
75
|
+
const result = await handleValidate({ policies: bad });
|
|
76
|
+
|
|
77
|
+
expect(result.valid).toBe(false);
|
|
78
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
79
|
+
expect(result.errors[0]!.hint).toMatch(/Did you mean 'principal'\?/);
|
|
80
|
+
expect(result.errors[0]!.line).toBe(1);
|
|
81
|
+
expect(result.validation_mode).toBe("syntax_only");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns valid for a correctly parsed policy when schema is omitted", async () => {
|
|
85
|
+
const ok = `permit (principal, action, resource);`;
|
|
86
|
+
const result = await handleValidate({ policies: ok });
|
|
87
|
+
|
|
88
|
+
expect(result.valid).toBe(true);
|
|
89
|
+
expect(result.errors).toHaveLength(0);
|
|
90
|
+
expect(result.warnings).toHaveLength(0);
|
|
91
|
+
expect(result.policy_count).toBe(1);
|
|
92
|
+
expect(result.validation_mode).toBe("syntax_only");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("cedar_validate — M2 line/column on parse errors", () => {
|
|
97
|
+
it("returns line and column for a parse error on a multi-line policy", async () => {
|
|
98
|
+
// `int` typo (should be `in`) sits on line 3, starting at column 10 (1-indexed)
|
|
99
|
+
const bad = `permit (
|
|
100
|
+
principal,
|
|
101
|
+
action int [DocMgmt::Action::"READ"],
|
|
102
|
+
resource
|
|
103
|
+
);`;
|
|
104
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
105
|
+
|
|
106
|
+
expect(result.valid).toBe(false);
|
|
107
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
108
|
+
const err = result.errors[0]!;
|
|
109
|
+
expect(err.line).toBe(3);
|
|
110
|
+
expect(err.column).toBe(10);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("returns line 1 for a single-line parse error", async () => {
|
|
114
|
+
const bad = `permint (principal, action, resource);`; // `permint` typo on line 1
|
|
115
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
116
|
+
|
|
117
|
+
expect(result.valid).toBe(false);
|
|
118
|
+
expect(result.errors[0]!.line).toBe(1);
|
|
119
|
+
expect(result.errors[0]!.column).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("reports correct column when a multi-byte UTF-8 char (em-dash) appears before the error", async () => {
|
|
123
|
+
// Post-audit regression: WASM reports UTF-8 byte offsets; naive walking
|
|
124
|
+
// as JS-string char indexes drifts on multi-byte chars. The em-dash on
|
|
125
|
+
// line 1 is 3 bytes / 1 code point. The `int` typo on line 4 starts at
|
|
126
|
+
// 1-indexed column 10. A byte-counted column would report 12.
|
|
127
|
+
const bad = `// comment with em—dash
|
|
128
|
+
permit (
|
|
129
|
+
principal,
|
|
130
|
+
action int [DocMgmt::Action::"READ"],
|
|
131
|
+
resource
|
|
132
|
+
);`;
|
|
133
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
134
|
+
|
|
135
|
+
expect(result.valid).toBe(false);
|
|
136
|
+
expect(result.errors[0]!.line).toBe(4);
|
|
137
|
+
expect(result.errors[0]!.column).toBe(10);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("cedar_validate — M1 hint for common typos", () => {
|
|
142
|
+
it("suggests 'in' when policy uses 'int' as a token", async () => {
|
|
143
|
+
const bad = `permit (principal, action int [DocMgmt::Action::"READ"], resource);`;
|
|
144
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
145
|
+
|
|
146
|
+
expect(result.valid).toBe(false);
|
|
147
|
+
expect(result.errors[0]!.hint).toMatch(/'in'/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("suggests 'principal' when policy uses 'prinipal'", async () => {
|
|
151
|
+
const bad = `permit (prinipal, action, resource);`;
|
|
152
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
153
|
+
|
|
154
|
+
expect(result.valid).toBe(false);
|
|
155
|
+
expect(result.errors[0]!.hint).toMatch(/'principal'/);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("suggests 'permit' when policy uses 'permint'", async () => {
|
|
159
|
+
const bad = `permint (principal, action, resource);`;
|
|
160
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
161
|
+
|
|
162
|
+
expect(result.valid).toBe(false);
|
|
163
|
+
expect(result.errors[0]!.hint).toMatch(/'permit'/);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("leaves hint null for parse errors with no known typo", async () => {
|
|
167
|
+
const bad = `permit (principal, action, resource) when { xyzzy(123) };`;
|
|
168
|
+
const result = await handleValidate({ policies: bad, schema: SCHEMA_STR });
|
|
169
|
+
|
|
170
|
+
// The error may or may not exist depending on Cedar's grammar; if it does, hint should be null
|
|
171
|
+
if (result.errors.length > 0 && result.errors[0]!.hint !== undefined) {
|
|
172
|
+
// Only assert when there is an error to check; the point is we do not over-suggest
|
|
173
|
+
expect(result.errors[0]!.hint).toBeNull();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("cedar_validate — 10d auto-discovery", () => {
|
|
179
|
+
const tempDirs: string[] = [];
|
|
180
|
+
|
|
181
|
+
afterEach(() => {
|
|
182
|
+
// Reset the singleton store manager so per-test state never leaks. Also
|
|
183
|
+
// tear down the temp workspace directories created during the test.
|
|
184
|
+
storeManager.loadFromRoots([]);
|
|
185
|
+
while (tempDirs.length > 0) {
|
|
186
|
+
const dir = tempDirs.pop()!;
|
|
187
|
+
rmSync(dir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("pulls the schema from the single loaded store when input.schema is omitted", async () => {
|
|
192
|
+
const storePath = makeAutoDiscoveryStore("single");
|
|
193
|
+
tempDirs.push(storePath);
|
|
194
|
+
storeManager.loadFromRoots([{ uri: `file://${storePath}`, name: "workspace" }]);
|
|
195
|
+
|
|
196
|
+
const result = await handleValidate({ policies: POLICIES });
|
|
197
|
+
|
|
198
|
+
expect(result.valid).toBe(true);
|
|
199
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
200
|
+
expect(result.auto_discovered).toEqual({ schema_from: "workspace" });
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("honors an explicit store parameter even when multiple stores are loaded", async () => {
|
|
204
|
+
const blue = makeAutoDiscoveryStore("blue");
|
|
205
|
+
const green = makeAutoDiscoveryStore("green");
|
|
206
|
+
tempDirs.push(blue, green);
|
|
207
|
+
storeManager.loadFromRoots([
|
|
208
|
+
{ uri: `file://${blue}`, name: "blue" },
|
|
209
|
+
{ uri: `file://${green}`, name: "green" },
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
const result = await handleValidate({ policies: POLICIES, store: "green" });
|
|
213
|
+
|
|
214
|
+
expect(result.valid).toBe(true);
|
|
215
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
216
|
+
expect(result.auto_discovered).toEqual({ schema_from: "green" });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("returns an ambiguity error when multiple stores are loaded and no store is passed", async () => {
|
|
220
|
+
const blue = makeAutoDiscoveryStore("blue");
|
|
221
|
+
const green = makeAutoDiscoveryStore("green");
|
|
222
|
+
tempDirs.push(blue, green);
|
|
223
|
+
storeManager.loadFromRoots([
|
|
224
|
+
{ uri: `file://${blue}`, name: "blue" },
|
|
225
|
+
{ uri: `file://${green}`, name: "green" },
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
const result = await handleValidate({ policies: POLICIES });
|
|
229
|
+
|
|
230
|
+
expect(result.valid).toBe(false);
|
|
231
|
+
expect(result.errors).toHaveLength(1);
|
|
232
|
+
expect(result.errors[0]!.message).toMatch(/Multiple stores are loaded/);
|
|
233
|
+
expect(result.errors[0]!.message).toContain("blue");
|
|
234
|
+
expect(result.errors[0]!.message).toContain("green");
|
|
235
|
+
expect(result.errors[0]!.message).toMatch(/Pass store/);
|
|
236
|
+
// Stays in syntax_only because we never picked a schema to type-check against.
|
|
237
|
+
expect(result.validation_mode).toBe("syntax_only");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("cedar_validate — 11c explicit validation_mode opt-in", () => {
|
|
242
|
+
const tempDirs: string[] = [];
|
|
243
|
+
|
|
244
|
+
afterEach(() => {
|
|
245
|
+
storeManager.loadFromRoots([]);
|
|
246
|
+
while (tempDirs.length > 0) {
|
|
247
|
+
const dir = tempDirs.pop()!;
|
|
248
|
+
rmSync(dir, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("validation_mode='auto' (default explicit) behaves like the existing auto-discovery path", async () => {
|
|
253
|
+
const storePath = makeAutoDiscoveryStore("workspace-auto");
|
|
254
|
+
tempDirs.push(storePath);
|
|
255
|
+
storeManager.loadFromRoots([{ uri: `file://${storePath}`, name: "workspace-auto" }]);
|
|
256
|
+
|
|
257
|
+
const result = await handleValidate({ policies: POLICIES, validation_mode: "auto" });
|
|
258
|
+
|
|
259
|
+
expect(result.valid).toBe(true);
|
|
260
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
261
|
+
expect(result.auto_discovered).toEqual({ schema_from: "workspace-auto" });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("validation_mode='syntax_only' forces parser-only and skips workspace auto-discovery even when a store is loaded", async () => {
|
|
265
|
+
const storePath = makeAutoDiscoveryStore("workspace-bypass");
|
|
266
|
+
tempDirs.push(storePath);
|
|
267
|
+
storeManager.loadFromRoots([{ uri: `file://${storePath}`, name: "workspace-bypass" }]);
|
|
268
|
+
|
|
269
|
+
const result = await handleValidate({
|
|
270
|
+
policies: `permit (principal, action, resource);`,
|
|
271
|
+
validation_mode: "syntax_only",
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(result.valid).toBe(true);
|
|
275
|
+
expect(result.validation_mode).toBe("syntax_only");
|
|
276
|
+
// Critical: no auto_discovered field — the store was loaded but we explicitly bypassed it.
|
|
277
|
+
expect(result.auto_discovered).toBeUndefined();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("validation_mode='syntax_only' ignores a schema passed inline (parser-only on demand)", async () => {
|
|
281
|
+
const result = await handleValidate({
|
|
282
|
+
policies: `permit (principal, action, resource);`,
|
|
283
|
+
schema: SCHEMA_STR,
|
|
284
|
+
validation_mode: "syntax_only",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(result.valid).toBe(true);
|
|
288
|
+
expect(result.validation_mode).toBe("syntax_only");
|
|
289
|
+
expect(result.auto_discovered).toBeUndefined();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("validation_mode='syntax_and_schema' with an inline schema runs full type-check", async () => {
|
|
293
|
+
const result = await handleValidate({
|
|
294
|
+
policies: POLICIES,
|
|
295
|
+
schema: SCHEMA_STR,
|
|
296
|
+
validation_mode: "syntax_and_schema",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
expect(result.valid).toBe(true);
|
|
300
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("validation_mode='syntax_and_schema' resolves from the workspace when no inline schema was provided", async () => {
|
|
304
|
+
const storePath = makeAutoDiscoveryStore("workspace-explicit");
|
|
305
|
+
tempDirs.push(storePath);
|
|
306
|
+
storeManager.loadFromRoots([{ uri: `file://${storePath}`, name: "workspace-explicit" }]);
|
|
307
|
+
|
|
308
|
+
const result = await handleValidate({
|
|
309
|
+
policies: POLICIES,
|
|
310
|
+
validation_mode: "syntax_and_schema",
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(result.valid).toBe(true);
|
|
314
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
315
|
+
expect(result.auto_discovered).toEqual({ schema_from: "workspace-explicit" });
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("validation_mode='syntax_and_schema' with no schema available returns a clear error (not a silent fall-through to syntax_only)", async () => {
|
|
319
|
+
const result = await handleValidate({
|
|
320
|
+
policies: `permit (principal, action, resource);`,
|
|
321
|
+
validation_mode: "syntax_and_schema",
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(result.valid).toBe(false);
|
|
325
|
+
expect(result.errors).toHaveLength(1);
|
|
326
|
+
expect(result.errors[0]!.message).toMatch(/syntax_and_schema/);
|
|
327
|
+
expect(result.errors[0]!.message).toMatch(/schema/);
|
|
328
|
+
// The response's validation_mode reflects what the caller asked for, not what got run.
|
|
329
|
+
expect(result.validation_mode).toBe("syntax_and_schema");
|
|
330
|
+
});
|
|
331
|
+
});
|