@intentius/chant-lexicon-cedar 0.44.8

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 (265) hide show
  1. package/README.md +190 -0
  2. package/dist/avp/ambient.d.ts +54 -0
  3. package/dist/avp/ambient.d.ts.map +1 -0
  4. package/dist/avp/client.d.ts +127 -0
  5. package/dist/avp/client.d.ts.map +1 -0
  6. package/dist/avp/describe-resources.d.ts +42 -0
  7. package/dist/avp/describe-resources.d.ts.map +1 -0
  8. package/dist/avp/embed.d.ts +120 -0
  9. package/dist/avp/embed.d.ts.map +1 -0
  10. package/dist/avp/live-export.d.ts +94 -0
  11. package/dist/avp/live-export.d.ts.map +1 -0
  12. package/dist/avp/ownership.d.ts +97 -0
  13. package/dist/avp/ownership.d.ts.map +1 -0
  14. package/dist/avp/statement.d.ts +22 -0
  15. package/dist/avp/statement.d.ts.map +1 -0
  16. package/dist/avp/store.d.ts +82 -0
  17. package/dist/avp/store.d.ts.map +1 -0
  18. package/dist/avp/testdata/mock-transport.d.ts +55 -0
  19. package/dist/avp/testdata/mock-transport.d.ts.map +1 -0
  20. package/dist/codegen/docs-cli.d.ts +3 -0
  21. package/dist/codegen/docs-cli.d.ts.map +1 -0
  22. package/dist/codegen/docs.d.ts +26 -0
  23. package/dist/codegen/docs.d.ts.map +1 -0
  24. package/dist/codegen/emit.d.ts +85 -0
  25. package/dist/codegen/emit.d.ts.map +1 -0
  26. package/dist/codegen/generate-cli.d.ts +3 -0
  27. package/dist/codegen/generate-cli.d.ts.map +1 -0
  28. package/dist/codegen/generate.d.ts +37 -0
  29. package/dist/codegen/generate.d.ts.map +1 -0
  30. package/dist/codegen/naming.d.ts +48 -0
  31. package/dist/codegen/naming.d.ts.map +1 -0
  32. package/dist/codegen/package.d.ts +10 -0
  33. package/dist/codegen/package.d.ts.map +1 -0
  34. package/dist/composites/deny-by-default-set.d.ts +58 -0
  35. package/dist/composites/deny-by-default-set.d.ts.map +1 -0
  36. package/dist/composites/index.d.ts +12 -0
  37. package/dist/composites/index.d.ts.map +1 -0
  38. package/dist/composites/owner-can-manage.d.ts +48 -0
  39. package/dist/composites/owner-can-manage.d.ts.map +1 -0
  40. package/dist/config.d.ts +102 -0
  41. package/dist/config.d.ts.map +1 -0
  42. package/dist/coverage.d.ts +59 -0
  43. package/dist/coverage.d.ts.map +1 -0
  44. package/dist/detect.d.ts +18 -0
  45. package/dist/detect.d.ts.map +1 -0
  46. package/dist/generated/index.d.ts +251 -0
  47. package/dist/generated/index.d.ts.map +1 -0
  48. package/dist/generated/runtime.d.ts +2 -0
  49. package/dist/generated/runtime.d.ts.map +1 -0
  50. package/dist/import/adapter.d.ts +21 -0
  51. package/dist/import/adapter.d.ts.map +1 -0
  52. package/dist/import/clause-text.d.ts +38 -0
  53. package/dist/import/clause-text.d.ts.map +1 -0
  54. package/dist/import/generator.d.ts +53 -0
  55. package/dist/import/generator.d.ts.map +1 -0
  56. package/dist/import/parser.d.ts +57 -0
  57. package/dist/import/parser.d.ts.map +1 -0
  58. package/dist/index.d.ts +34 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/init-templates.d.ts +32 -0
  61. package/dist/init-templates.d.ts.map +1 -0
  62. package/dist/integrity.json +22 -0
  63. package/dist/lint/audit-catalog.d.ts +29 -0
  64. package/dist/lint/audit-catalog.d.ts.map +1 -0
  65. package/dist/lint/post-synth/cedar-helpers.d.ts +97 -0
  66. package/dist/lint/post-synth/cedar-helpers.d.ts.map +1 -0
  67. package/dist/lint/post-synth/cedc010.d.ts +17 -0
  68. package/dist/lint/post-synth/cedc010.d.ts.map +1 -0
  69. package/dist/lint/post-synth/cedc011.d.ts +17 -0
  70. package/dist/lint/post-synth/cedc011.d.ts.map +1 -0
  71. package/dist/lint/post-synth/cedc012.d.ts +21 -0
  72. package/dist/lint/post-synth/cedc012.d.ts.map +1 -0
  73. package/dist/lint/post-synth/cedc013.d.ts +20 -0
  74. package/dist/lint/post-synth/cedc013.d.ts.map +1 -0
  75. package/dist/lint/post-synth/cedc014.d.ts +19 -0
  76. package/dist/lint/post-synth/cedc014.d.ts.map +1 -0
  77. package/dist/lint/post-synth/cede010.d.ts +34 -0
  78. package/dist/lint/post-synth/cede010.d.ts.map +1 -0
  79. package/dist/lint/post-synth/cede011.d.ts +22 -0
  80. package/dist/lint/post-synth/cede011.d.ts.map +1 -0
  81. package/dist/lint/post-synth/ceds010.d.ts +24 -0
  82. package/dist/lint/post-synth/ceds010.d.ts.map +1 -0
  83. package/dist/lint/post-synth/ceds011.d.ts +19 -0
  84. package/dist/lint/post-synth/ceds011.d.ts.map +1 -0
  85. package/dist/lint/post-synth/ceds012.d.ts +17 -0
  86. package/dist/lint/post-synth/ceds012.d.ts.map +1 -0
  87. package/dist/lint/post-synth/index.d.ts +3 -0
  88. package/dist/lint/post-synth/index.d.ts.map +1 -0
  89. package/dist/lint/post-synth/wasm-helpers.d.ts +108 -0
  90. package/dist/lint/post-synth/wasm-helpers.d.ts.map +1 -0
  91. package/dist/lint/rules/index.d.ts +10 -0
  92. package/dist/lint/rules/index.d.ts.map +1 -0
  93. package/dist/lint/rules/policy-shape.d.ts +31 -0
  94. package/dist/lint/rules/policy-shape.d.ts.map +1 -0
  95. package/dist/lsp/completions.d.ts +24 -0
  96. package/dist/lsp/completions.d.ts.map +1 -0
  97. package/dist/lsp/hover.d.ts +11 -0
  98. package/dist/lsp/hover.d.ts.map +1 -0
  99. package/dist/lsp/registry.d.ts +45 -0
  100. package/dist/lsp/registry.d.ts.map +1 -0
  101. package/dist/manifest.json +8 -0
  102. package/dist/mcp/index.d.ts +34 -0
  103. package/dist/mcp/index.d.ts.map +1 -0
  104. package/dist/mcp/policy-coverage.d.ts +81 -0
  105. package/dist/mcp/policy-coverage.d.ts.map +1 -0
  106. package/dist/meta.json +699 -0
  107. package/dist/okf/index.md +37 -0
  108. package/dist/okf/rules/CEDC001.md +11 -0
  109. package/dist/okf/rules/CEDC010.md +11 -0
  110. package/dist/okf/rules/CEDC011.md +15 -0
  111. package/dist/okf/rules/CEDC012.md +11 -0
  112. package/dist/okf/rules/CEDC013.md +15 -0
  113. package/dist/okf/rules/CEDC014.md +15 -0
  114. package/dist/okf/rules/CEDE010.md +15 -0
  115. package/dist/okf/rules/CEDE011.md +15 -0
  116. package/dist/okf/rules/CEDS010.md +15 -0
  117. package/dist/okf/rules/CEDS011.md +11 -0
  118. package/dist/okf/rules/CEDS012.md +15 -0
  119. package/dist/okf/types/AdminAction.md +14 -0
  120. package/dist/okf/types/Application.md +14 -0
  121. package/dist/okf/types/ApproveAction.md +13 -0
  122. package/dist/okf/types/CommentAction.md +14 -0
  123. package/dist/okf/types/CreateAction.md +14 -0
  124. package/dist/okf/types/DeleteAction.md +14 -0
  125. package/dist/okf/types/Document.md +18 -0
  126. package/dist/okf/types/Folder.md +15 -0
  127. package/dist/okf/types/Group.md +14 -0
  128. package/dist/okf/types/ListAction.md +14 -0
  129. package/dist/okf/types/Policy.md +29 -0
  130. package/dist/okf/types/ReadAction.md +14 -0
  131. package/dist/okf/types/ServiceAccount.md +15 -0
  132. package/dist/okf/types/ShareAction.md +14 -0
  133. package/dist/okf/types/Team.md +14 -0
  134. package/dist/okf/types/User.md +18 -0
  135. package/dist/okf/types/WriteAction.md +14 -0
  136. package/dist/package-cli.d.ts +3 -0
  137. package/dist/package-cli.d.ts.map +1 -0
  138. package/dist/plugin.d.ts +8 -0
  139. package/dist/plugin.d.ts.map +1 -0
  140. package/dist/rules/cedar-helpers.ts +209 -0
  141. package/dist/rules/cedc010.ts +80 -0
  142. package/dist/rules/cedc011.ts +47 -0
  143. package/dist/rules/cedc012.ts +64 -0
  144. package/dist/rules/cedc013.ts +68 -0
  145. package/dist/rules/cedc014.ts +73 -0
  146. package/dist/rules/cede010.ts +89 -0
  147. package/dist/rules/cede011.ts +58 -0
  148. package/dist/rules/ceds010.ts +57 -0
  149. package/dist/rules/ceds011.ts +42 -0
  150. package/dist/rules/ceds012.ts +50 -0
  151. package/dist/rules/policy-shape.ts +148 -0
  152. package/dist/rules/wasm-helpers.ts +315 -0
  153. package/dist/serializer.d.ts +138 -0
  154. package/dist/serializer.d.ts.map +1 -0
  155. package/dist/spec/fetch.d.ts +71 -0
  156. package/dist/spec/fetch.d.ts.map +1 -0
  157. package/dist/spec/parse.d.ts +113 -0
  158. package/dist/spec/parse.d.ts.map +1 -0
  159. package/dist/spec/pin.d.ts +116 -0
  160. package/dist/spec/pin.d.ts.map +1 -0
  161. package/dist/spec/pinned-names.json +18 -0
  162. package/dist/spec/wasm.d.ts +143 -0
  163. package/dist/spec/wasm.d.ts.map +1 -0
  164. package/dist/types/index.d.ts +219 -0
  165. package/dist/validate-cli.d.ts +3 -0
  166. package/dist/validate-cli.d.ts.map +1 -0
  167. package/dist/validate.d.ts +25 -0
  168. package/dist/validate.d.ts.map +1 -0
  169. package/package.json +75 -0
  170. package/src/avp/OWNERSHIP.md +125 -0
  171. package/src/avp/ambient.test.ts +113 -0
  172. package/src/avp/ambient.ts +124 -0
  173. package/src/avp/client.ts +310 -0
  174. package/src/avp/describe-resources.test.ts +316 -0
  175. package/src/avp/describe-resources.ts +215 -0
  176. package/src/avp/embed.test.ts +114 -0
  177. package/src/avp/embed.ts +190 -0
  178. package/src/avp/live-export.test.ts +232 -0
  179. package/src/avp/live-export.ts +185 -0
  180. package/src/avp/ownership.test.ts +101 -0
  181. package/src/avp/ownership.ts +170 -0
  182. package/src/avp/statement.ts +50 -0
  183. package/src/avp/store.ts +152 -0
  184. package/src/avp/testdata/mock-transport.ts +147 -0
  185. package/src/codegen/docs-cli.ts +7 -0
  186. package/src/codegen/docs.ts +873 -0
  187. package/src/codegen/emit.ts +496 -0
  188. package/src/codegen/generate-cli.ts +18 -0
  189. package/src/codegen/generate.test.ts +128 -0
  190. package/src/codegen/generate.ts +123 -0
  191. package/src/codegen/naming.ts +101 -0
  192. package/src/codegen/package.ts +52 -0
  193. package/src/composites/composites.test.ts +206 -0
  194. package/src/composites/deny-by-default-set.ts +98 -0
  195. package/src/composites/index.ts +13 -0
  196. package/src/composites/owner-can-manage.ts +80 -0
  197. package/src/config-namespace.test.ts +44 -0
  198. package/src/config.test.ts +82 -0
  199. package/src/config.ts +119 -0
  200. package/src/coverage.test.ts +61 -0
  201. package/src/coverage.ts +166 -0
  202. package/src/detect.test.ts +64 -0
  203. package/src/detect.ts +59 -0
  204. package/src/generated/index.d.ts +219 -0
  205. package/src/generated/index.ts +279 -0
  206. package/src/generated/lexicon-cedar.json +699 -0
  207. package/src/generated/runtime.ts +2 -0
  208. package/src/import/adapter.ts +63 -0
  209. package/src/import/clause-text.ts +178 -0
  210. package/src/import/generator.test.ts +133 -0
  211. package/src/import/generator.ts +219 -0
  212. package/src/import/parser.test.ts +265 -0
  213. package/src/import/parser.ts +352 -0
  214. package/src/import/roundtrip.test.ts +127 -0
  215. package/src/import/testdata/full.cedar +36 -0
  216. package/src/import/testdata/full.cedar.json +238 -0
  217. package/src/import/testdata/realistic.cedar +35 -0
  218. package/src/import/testdata/simple.cedar +6 -0
  219. package/src/index.ts +94 -0
  220. package/src/init-templates.test.ts +158 -0
  221. package/src/init-templates.ts +358 -0
  222. package/src/lint/audit-catalog.ts +130 -0
  223. package/src/lint/post-synth/cedar-helpers.ts +209 -0
  224. package/src/lint/post-synth/cedc010.ts +80 -0
  225. package/src/lint/post-synth/cedc011.ts +47 -0
  226. package/src/lint/post-synth/cedc012.ts +64 -0
  227. package/src/lint/post-synth/cedc013.ts +68 -0
  228. package/src/lint/post-synth/cedc014.ts +73 -0
  229. package/src/lint/post-synth/cede010.ts +89 -0
  230. package/src/lint/post-synth/cede011.ts +58 -0
  231. package/src/lint/post-synth/ceds010.ts +57 -0
  232. package/src/lint/post-synth/ceds011.ts +42 -0
  233. package/src/lint/post-synth/ceds012.ts +50 -0
  234. package/src/lint/post-synth/index.ts +25 -0
  235. package/src/lint/post-synth/post-synth.test.ts +474 -0
  236. package/src/lint/post-synth/wasm-helpers.ts +315 -0
  237. package/src/lint/rules/index.ts +12 -0
  238. package/src/lint/rules/policy-shape.test.ts +90 -0
  239. package/src/lint/rules/policy-shape.ts +148 -0
  240. package/src/lsp/completions.test.ts +111 -0
  241. package/src/lsp/completions.ts +55 -0
  242. package/src/lsp/hover.test.ts +82 -0
  243. package/src/lsp/hover.ts +75 -0
  244. package/src/lsp/registry.ts +74 -0
  245. package/src/mcp/index.ts +103 -0
  246. package/src/mcp/policy-coverage.test.ts +176 -0
  247. package/src/mcp/policy-coverage.ts +205 -0
  248. package/src/package-cli.ts +22 -0
  249. package/src/plugin.test.ts +115 -0
  250. package/src/plugin.ts +312 -0
  251. package/src/serializer.test.ts +502 -0
  252. package/src/serializer.ts +335 -0
  253. package/src/skills/chant-cedar-authoring.md +180 -0
  254. package/src/skills/chant-cedar-avp-embedding.md +125 -0
  255. package/src/skills/chant-cedar-meta-policy.md +119 -0
  256. package/src/spec/default-schema.cedarschema +108 -0
  257. package/src/spec/fetch.ts +107 -0
  258. package/src/spec/parse.test.ts +123 -0
  259. package/src/spec/parse.ts +253 -0
  260. package/src/spec/pin.test.ts +88 -0
  261. package/src/spec/pin.ts +243 -0
  262. package/src/spec/pinned-names.json +18 -0
  263. package/src/spec/wasm.ts +283 -0
  264. package/src/validate-cli.ts +8 -0
  265. package/src/validate.ts +85 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * "Which policies cover this resource?" — answered from a build (epic #1645).
3
+ *
4
+ * `src/coverage.ts` next door asks a different question: is every schema
5
+ * declaration reachable from the *generated artifacts*. This one asks whether
6
+ * every schema declaration is reachable from the *policy set*, which is the
7
+ * authorization question. An entity type no policy can ever apply to is a
8
+ * resource nothing grants and nothing denies — under Cedar's default-deny it is
9
+ * inert, and that is either the intent or a hole.
10
+ *
11
+ * The answer comes from `getValidRequestEnvsPolicy`, Cedar's own resolver: it
12
+ * takes one policy and the schema and returns every (principal, action,
13
+ * resource) triple the policy could apply to, having already expanded `is`,
14
+ * `in`, action groups, and `appliesTo`. Reimplementing that against the parsed
15
+ * scope would be a second, worse Cedar; this is why the wasm package is a
16
+ * runtime dependency rather than a dev one.
17
+ */
18
+
19
+ import {
20
+ getValidRequestEnvsPolicy,
21
+ policySetTextToParts,
22
+ policyToJson,
23
+ } from "@cedar-policy/cedar-wasm/nodejs";
24
+ import { readSchemaSource, type ResolveSchemaOptions } from "../spec/fetch";
25
+ import { parseCedarSchema } from "../spec/parse";
26
+
27
+ export interface CedarPolicyCoverageItem {
28
+ /** `App::Document` or `App::Action::"read"`. */
29
+ name: string;
30
+ kind: "entity" | "action";
31
+ /** How many policies in the set can apply to it. */
32
+ policies: number;
33
+ /** Ids of the policies that can, in declaration order. */
34
+ policyIds: string[];
35
+ /** True when at least one of those policies is a `permit`. */
36
+ permitted: boolean;
37
+ /** True when at least one of those policies is a `forbid`. */
38
+ forbidden: boolean;
39
+ }
40
+
41
+ export interface CedarPolicyCoverageReport {
42
+ /** Where the schema came from. */
43
+ schemaPath: string;
44
+ isDefaultSchema: boolean;
45
+ /** Policies the set parsed into. */
46
+ policyCount: number;
47
+ entityTypes: number;
48
+ entityTypesCovered: number;
49
+ actions: number;
50
+ actionsCovered: number;
51
+ /** Declarations no policy in the set can apply to. */
52
+ uncovered: string[];
53
+ /** Declarations reachable only from a `forbid` — nothing ever grants them. */
54
+ forbidOnly: string[];
55
+ items: CedarPolicyCoverageItem[];
56
+ /** Policies the resolver could not place, with its reason. */
57
+ unresolved: Array<{ policyId: string; error: string }>;
58
+ /**
59
+ * Ids of policies whose valid request envelope is empty — no request can ever
60
+ * match them.
61
+ *
62
+ * A scope naming an entity type the schema does not declare lands here rather
63
+ * than in {@link unresolved}: `getValidRequestEnvsPolicy` answers "success,
64
+ * nothing" rather than failing, and a policy that can never fire is a real
65
+ * finding either way. It is usually a typo'd entity type.
66
+ */
67
+ inert: string[];
68
+ /**
69
+ * Why the policy set could not be split into policies, when it could not.
70
+ *
71
+ * Without this a set that fails to parse reports zero policies and every
72
+ * declaration uncovered — the same numbers as a project with no policies at
73
+ * all, and the opposite diagnosis.
74
+ */
75
+ parseErrors: string[];
76
+ }
77
+
78
+ export interface CedarPolicyCoverageOptions extends ResolveSchemaOptions {
79
+ /** The emitted `.cedar` policy set text. */
80
+ policySetText: string;
81
+ }
82
+
83
+ /**
84
+ * A policy's id and effect, read from Cedar's own parse rather than from the
85
+ * text.
86
+ *
87
+ * The regex version of this worked on the serializer's output and only on the
88
+ * serializer's output: a hand-formatted policy, an annotation whose value
89
+ * contains the word `forbid`, or an escaped quote inside `@id` each break it.
90
+ * `policyToJson` is already in the package, so there is no reason to guess.
91
+ *
92
+ * `index` supplies the fallback id, because a Cedar policy is not required to
93
+ * annotate one and a report keyed by "" would collapse them together.
94
+ */
95
+ function identify(text: string, index: number): { id: string; effect: "permit" | "forbid" } {
96
+ const parsed = policyToJson(text);
97
+ if (parsed.type !== "success") {
98
+ return { id: `policy-${index + 1}`, effect: "permit" };
99
+ }
100
+ const annotated = parsed.json.annotations?.id;
101
+ return {
102
+ id: typeof annotated === "string" && annotated.length > 0 ? annotated : `policy-${index + 1}`,
103
+ effect: parsed.json.effect === "forbid" ? "forbid" : "permit",
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Report which schema declarations the policy set can reach.
109
+ */
110
+ export function computePolicyCoverage(options: CedarPolicyCoverageOptions): CedarPolicyCoverageReport {
111
+ const source = readSchemaSource(options);
112
+ const { decls } = parseCedarSchema(source.text);
113
+
114
+ const parts = policySetTextToParts(options.policySetText);
115
+ const policies = parts.type === "success" ? parts.policies : [];
116
+ const parseErrors = parts.type === "success" ? [] : parts.errors.map((e) => e.message);
117
+
118
+ const hits = new Map<string, { ids: string[]; permitted: boolean; forbidden: boolean }>();
119
+ const unresolved: Array<{ policyId: string; error: string }> = [];
120
+ const inert: string[] = [];
121
+
122
+ policies.forEach((text, index) => {
123
+ const { id, effect } = identify(text, index);
124
+ const envs = getValidRequestEnvsPolicy(text, source.text);
125
+ if (envs.type !== "success") {
126
+ unresolved.push({ policyId: id, error: envs.error });
127
+ return;
128
+ }
129
+
130
+ const reached = [...envs.principals, ...envs.actions, ...envs.resources];
131
+ if (reached.length === 0) {
132
+ inert.push(id);
133
+ return;
134
+ }
135
+
136
+ for (const name of reached) {
137
+ const hit = hits.get(name) ?? { ids: [], permitted: false, forbidden: false };
138
+ if (!hit.ids.includes(id)) hit.ids.push(id);
139
+ if (effect === "permit") hit.permitted = true;
140
+ else hit.forbidden = true;
141
+ hits.set(name, hit);
142
+ }
143
+ });
144
+
145
+ const items: CedarPolicyCoverageItem[] = decls
146
+ .map((decl) => {
147
+ const hit = hits.get(decl.typeName);
148
+ return {
149
+ name: decl.typeName,
150
+ kind: decl.kind,
151
+ policies: hit?.ids.length ?? 0,
152
+ policyIds: hit?.ids ?? [],
153
+ permitted: hit?.permitted ?? false,
154
+ forbidden: hit?.forbidden ?? false,
155
+ };
156
+ })
157
+ .sort((a, b) => a.name.localeCompare(b.name));
158
+
159
+ const entities = items.filter((i) => i.kind === "entity");
160
+ const actions = items.filter((i) => i.kind === "action");
161
+
162
+ return {
163
+ schemaPath: source.path,
164
+ isDefaultSchema: source.isDefault,
165
+ policyCount: policies.length,
166
+ entityTypes: entities.length,
167
+ entityTypesCovered: entities.filter((i) => i.policies > 0).length,
168
+ actions: actions.length,
169
+ actionsCovered: actions.filter((i) => i.policies > 0).length,
170
+ uncovered: items.filter((i) => i.policies === 0).map((i) => i.name),
171
+ forbidOnly: items.filter((i) => i.policies > 0 && !i.permitted).map((i) => i.name),
172
+ items,
173
+ unresolved,
174
+ inert,
175
+ parseErrors,
176
+ };
177
+ }
178
+
179
+ /** Human-readable summary, for the CLI-shaped callers. */
180
+ export function formatPolicyCoverage(report: CedarPolicyCoverageReport): string {
181
+ const lines = [
182
+ `Cedar policy coverage — ${report.policyCount} policy/policies against ` +
183
+ `${report.isDefaultSchema ? "the bundled default schema" : report.schemaPath}`,
184
+ ` Entity types: ${report.entityTypesCovered}/${report.entityTypes}`,
185
+ ` Actions: ${report.actionsCovered}/${report.actions}`,
186
+ ];
187
+
188
+ for (const error of report.parseErrors) {
189
+ lines.push(` Parse error: ${error}`);
190
+ }
191
+ if (report.uncovered.length > 0) {
192
+ lines.push(` Uncovered: ${report.uncovered.join(", ")}`);
193
+ }
194
+ if (report.forbidOnly.length > 0) {
195
+ lines.push(` Forbid only: ${report.forbidOnly.join(", ")}`);
196
+ }
197
+ if (report.inert.length > 0) {
198
+ lines.push(` Never fires: ${report.inert.join(", ")}`);
199
+ }
200
+ for (const problem of report.unresolved) {
201
+ lines.push(` Unresolved: ${problem.policyId} — ${problem.error}`);
202
+ }
203
+
204
+ return lines.join("\n");
205
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Thin entry point for `npm run bundle` in lexicon-cedar.
4
+ *
5
+ * Unlike the other lexicons this does not generate `src/generated/` first —
6
+ * codegen is schema-driven and per-project, and lands in #1650. Until then
7
+ * the bundle carries the manifest, rules and integrity over an empty registry.
8
+ */
9
+ import { packageLexicon } from "./codegen/package";
10
+ import { writeBundleSpec } from "@intentius/chant/codegen/package";
11
+ import { join, dirname } from "path";
12
+ import { fileURLToPath } from "url";
13
+
14
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
15
+
16
+ const { spec, stats } = await packageLexicon({ verbose: true });
17
+
18
+ const distDir = join(pkgDir, "dist");
19
+ writeBundleSpec(spec, distDir);
20
+
21
+ console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
22
+ console.error(`dist/ written to ${distDir}`);
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isLexiconPlugin } from "@intentius/chant/lexicon";
3
+ import { cedarPlugin } from "./plugin";
4
+ import { cedarSerializer, CEDAR_POLICY_TYPE } from "./serializer";
5
+ import { AVP_OWNERSHIP_KEYS } from "./avp/ownership";
6
+
7
+ describe("cedar plugin", () => {
8
+ it("is a valid LexiconPlugin", () => {
9
+ expect(isLexiconPlugin(cedarPlugin)).toBe(true);
10
+ });
11
+
12
+ it("is named after the lexicon", () => {
13
+ expect(cedarPlugin.name).toBe("cedar");
14
+ });
15
+
16
+ it("exposes the cedar serializer", () => {
17
+ expect(cedarPlugin.serializer).toBe(cedarSerializer);
18
+ expect(cedarPlugin.serializer?.rulePrefix).toBe("CED");
19
+ });
20
+
21
+ it("registers the lifecycle methods core dispatches through", () => {
22
+ for (const method of ["generate", "validate", "coverage", "package", "docs"] as const) {
23
+ expect(typeof cedarPlugin[method], method).toBe("function");
24
+ }
25
+ });
26
+
27
+ it("registers the LSP providers", () => {
28
+ expect(typeof cedarPlugin.completionProvider).toBe("function");
29
+ expect(typeof cedarPlugin.hoverProvider).toBe("function");
30
+ });
31
+
32
+ it("declares at least one lint rule, all under the CED prefix", () => {
33
+ const rules = cedarPlugin.lintRules?.() ?? [];
34
+ expect(rules.length).toBeGreaterThan(0);
35
+ for (const rule of rules) {
36
+ expect(rule.id.startsWith("CED"), rule.id).toBe(true);
37
+ }
38
+ });
39
+
40
+ it("registers the agent-facing extensions", () => {
41
+ for (const method of ["skills", "mcpTools", "mcpResources", "initTemplates"] as const) {
42
+ expect(typeof cedarPlugin[method], method).toBe("function");
43
+ }
44
+ });
45
+
46
+ it("ships three skills whose frontmatter matches their registered name", () => {
47
+ const skills = cedarPlugin.skills?.() ?? [];
48
+ expect(skills).toHaveLength(3);
49
+
50
+ for (const skill of skills) {
51
+ // An empty body means the loader could not read the file — the skill
52
+ // registers, `chant init` copies nothing, and no test would notice.
53
+ expect(skill.content.length, `${skill.name} has no content`).toBeGreaterThan(0);
54
+ expect(skill.content).toContain(`skill: ${skill.name}`);
55
+ expect(skill.content).toContain("user-invocable: true");
56
+ expect(skill.description.length).toBeGreaterThan(0);
57
+ }
58
+
59
+ expect(skills.map((s) => s.name).sort()).toEqual([
60
+ "chant-cedar-authoring",
61
+ "chant-cedar-avp-embedding",
62
+ "chant-cedar-meta-policy",
63
+ ]);
64
+ });
65
+
66
+ it("contributes the conventional MCP pair plus the cedar-specific tool", () => {
67
+ expect(cedarPlugin.mcpTools?.().map((t) => t.name)).toEqual(["cedar:diff", "coverage"]);
68
+ expect(cedarPlugin.mcpResources?.().map((r) => r.uri)).toEqual(["cedar:resource-catalog"]);
69
+ });
70
+
71
+ it("scaffolds three distinct init templates", () => {
72
+ const names = ["default", "avp-embedding", "gateway-policy-set"] as const;
73
+ const sets = names.map((n) => cedarPlugin.initTemplates?.(n));
74
+
75
+ for (const set of sets) {
76
+ expect(set?.src["policies.ts"]).toContain("export const");
77
+ expect(set?.root?.["schema.cedarschema"]).toContain("namespace ");
78
+ }
79
+ expect(new Set(sets.map((s) => s?.src["policies.ts"])).size).toBe(3);
80
+ });
81
+
82
+ // ── AVP lifecycle (#1652) ────────────────────────────────────
83
+
84
+ it("registers the AVP lifecycle readers", () => {
85
+ for (const method of ["describeResources", "ambientKinds", "observeAmbient", "exportResources"] as const) {
86
+ expect(typeof cedarPlugin[method], method).toBe("function");
87
+ }
88
+ });
89
+
90
+ it("declares an ownership channel with complete keys", () => {
91
+ const channel = cedarPlugin.ownershipChannel;
92
+ expect(channel).toBeDefined();
93
+ expect(channel!.keys).toEqual(AVP_OWNERSHIP_KEYS);
94
+ for (const key of ["managedBy", "stack", "env"] as const) {
95
+ expect(typeof channel!.keys[key], key).toBe("string");
96
+ expect(channel!.keys[key].length).toBeGreaterThan(0);
97
+ }
98
+ });
99
+
100
+ it("declares a marker channel only on the paths it implements (chant #1348)", () => {
101
+ const channel = cedarPlugin.ownershipChannel!;
102
+ expect([...channel.reads].sort()).toEqual(["describeResources", "exportResources"]);
103
+ for (const path of channel.reads) {
104
+ expect(typeof cedarPlugin[path], path).toBe("function");
105
+ }
106
+ // Not declared, because it is not implemented — the whole point of the
107
+ // per-path declaration is that a caller learns this before asking.
108
+ expect(channel.reads).not.toContain("observeResourcesDeep");
109
+ expect(cedarPlugin.observeResourcesDeep).toBeUndefined();
110
+ });
111
+
112
+ it("can enumerate the one kind it deploys", () => {
113
+ expect(cedarPlugin.ambientKinds?.()).toEqual([CEDAR_POLICY_TYPE]);
114
+ });
115
+ });
package/src/plugin.ts ADDED
@@ -0,0 +1,312 @@
1
+ import type { LexiconPlugin, SkillDefinition, IntrinsicDef, InitTemplateSet } from "@intentius/chant/lexicon";
2
+ import type { LintRule } from "@intentius/chant/lint/rule";
3
+ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
4
+ import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
5
+ import type { McpToolContribution, McpResourceContribution } from "@intentius/chant/mcp/types";
6
+ import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
7
+ import { cedarSerializer } from "./serializer";
8
+ import { rules } from "./lint/rules";
9
+ import { postSynthChecks as cedarPostSynthChecks } from "./lint/post-synth";
10
+ import { cedarAuditCatalog } from "./lint/audit-catalog";
11
+ import { completions } from "./lsp/completions";
12
+ import { hover } from "./lsp/hover";
13
+ import { cedarMcpResources, cedarMcpTools } from "./mcp/index";
14
+ import { cedarInitTemplates } from "./init-templates";
15
+ import { cedarConfigSchema } from "./config";
16
+ import { detectTemplate as detectCedarTemplate } from "./detect";
17
+ import { CedarTemplateGenerator, CedarTemplateParser } from "./import/adapter";
18
+ // #1652 — the AVP ownership channel is data on the plugin, so its keys have to
19
+ // be a value at module load rather than a dynamic import.
20
+ import type { ResourceMetadata } from "@intentius/chant/lexicon";
21
+ import { AVP_OWNERSHIP_KEYS } from "./avp/ownership";
22
+ import { AVP_AMBIENT_KINDS } from "./avp/ambient";
23
+
24
+ /**
25
+ * cedar lexicon plugin.
26
+ *
27
+ * Implements all required LexiconPlugin lifecycle methods.
28
+ */
29
+ export const cedarPlugin: LexiconPlugin = {
30
+ name: "cedar",
31
+ serializer: cedarSerializer,
32
+
33
+ // ── Required lifecycle methods ────────────────────────────────
34
+
35
+ async generate(options?: { verbose?: boolean }): Promise<void> {
36
+ // Cedar's codegen input is the project's own schema, so the `cedar` config
37
+ // namespace is read here rather than being decoration (#1650).
38
+ const { generate, writeGeneratedFiles } = await import("./codegen/generate");
39
+ const { loadCedarConfig } = await import("./config");
40
+ const projectRoot = process.cwd();
41
+ const result = await generate({ ...options, projectRoot, config: await loadCedarConfig(projectRoot) });
42
+ writeGeneratedFiles(result);
43
+ },
44
+
45
+ async validate(options?: { verbose?: boolean }): Promise<void> {
46
+ const { validate } = await import("./validate");
47
+ const { printValidationResult } = await import("@intentius/chant/codegen/validate");
48
+ const result = await validate();
49
+ printValidationResult(result);
50
+ },
51
+
52
+ async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
53
+ const { analyzeCedarCoverage } = await import("./coverage");
54
+ const { loadCedarConfig } = await import("./config");
55
+ const projectRoot = process.cwd();
56
+ analyzeCedarCoverage({
57
+ projectRoot,
58
+ config: await loadCedarConfig(projectRoot),
59
+ verbose: options?.verbose,
60
+ minOverall: options?.minOverall,
61
+ });
62
+ },
63
+
64
+ async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
65
+ const { packageLexicon } = await import("./codegen/package");
66
+ const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
67
+ const { join, dirname } = await import("path");
68
+ const { fileURLToPath } = await import("url");
69
+
70
+ const { spec, stats } = await packageLexicon(options);
71
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
72
+ writeBundleSpec(spec, join(pkgDir, "dist"));
73
+
74
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
75
+ },
76
+
77
+ // ── Optional extensions ────────────────────────────────────
78
+
79
+ lintRules() {
80
+ return rules;
81
+ },
82
+
83
+ postSynthChecks() {
84
+ // Auto-discovered from lint/post-synth/ and committed as a static barrel
85
+ // by `npm run generate:barrels` — adding a check is dropping a file.
86
+ return cedarPostSynthChecks;
87
+ },
88
+
89
+ auditCatalog() {
90
+ return cedarAuditCatalog;
91
+ },
92
+
93
+ skills: createSkillsLoader(import.meta.url, [
94
+ {
95
+ file: "chant-cedar-authoring.md",
96
+ name: "chant-cedar-authoring",
97
+ description:
98
+ "Author Cedar policies as typed chant resources — schema to generated classes to .cedar and JSON outputs",
99
+ triggers: [
100
+ { type: "file-pattern", value: "**/*.cedarschema" },
101
+ { type: "file-pattern", value: "**/policies.ts" },
102
+ { type: "context", value: "cedar policy" },
103
+ { type: "context", value: "cedar schema" },
104
+ { type: "context", value: "authorization policy" },
105
+ ],
106
+ parameters: [],
107
+ examples: [
108
+ {
109
+ title: "Owner-scoped permit",
110
+ description: "Grant an action to the owner of a resource",
111
+ input: "Let users read documents they own",
112
+ output: `new Policy({ principal: { is: "App::User" }, action: { eq: ReadAction }, when: ["resource.owner == principal"] })`,
113
+ },
114
+ ],
115
+ },
116
+ {
117
+ file: "chant-cedar-avp-embedding.md",
118
+ name: "chant-cedar-avp-embedding",
119
+ description:
120
+ "Embed a typed cedar-lexicon policy into an AWS Verified Permissions policy resource instead of a hand-written string",
121
+ triggers: [
122
+ { type: "context", value: "verified permissions" },
123
+ { type: "context", value: "avp policy store" },
124
+ { type: "context", value: "cedar deployment" },
125
+ { type: "context", value: "cedar-agent" },
126
+ ],
127
+ parameters: [],
128
+ examples: [
129
+ {
130
+ title: "Policy destined for a policy store",
131
+ input: "Deploy this Cedar policy to Verified Permissions",
132
+ output: "Emit the .cedar text and reference it — the typed statement seam lands with #1652",
133
+ },
134
+ ],
135
+ },
136
+ {
137
+ file: "chant-cedar-meta-policy.md",
138
+ name: "chant-cedar-meta-policy",
139
+ description:
140
+ "Organizational policy over Cedar policy sets — the bare-permit wall, forbid conventions, and env-aware severity",
141
+ triggers: [
142
+ { type: "context", value: "cedar lint" },
143
+ { type: "context", value: "bare permit" },
144
+ { type: "context", value: "policy review" },
145
+ { type: "context", value: "meta-policy" },
146
+ ],
147
+ parameters: [],
148
+ examples: [
149
+ {
150
+ title: "The bare-permit wall",
151
+ input: "Is `permit (principal, action, resource);` acceptable?",
152
+ output: "Warn in dev and staging; fail the build in prod",
153
+ },
154
+ ],
155
+ },
156
+ ]),
157
+
158
+ initTemplates(template?: string): InitTemplateSet {
159
+ // The branch names are spelled out here rather than only in
160
+ // ./init-templates.ts because `chant dev check-lexicon` counts a lexicon's
161
+ // templates by scanning this file. The content lives next door.
162
+ if (template === "avp-embedding") return cedarInitTemplates("avp-embedding");
163
+ if (template === "gateway-policy-set") return cedarInitTemplates("gateway-policy-set");
164
+ return cedarInitTemplates(template);
165
+ },
166
+
167
+ mcpTools() {
168
+ return cedarMcpTools();
169
+ },
170
+
171
+ mcpResources() {
172
+ // `createCatalogResource` resolves the registry relative to the module URL
173
+ // it is handed, so it needs this file's — src/generated/, not src/mcp/.
174
+ return cedarMcpResources(import.meta.url);
175
+ },
176
+
177
+ detectTemplate(data: unknown) {
178
+ return detectCedarTemplate(data);
179
+ },
180
+
181
+ /** `.cedar` text and the JSON policy-set envelope, into the core IR (#1653). */
182
+ templateParser() {
183
+ return new CedarTemplateParser();
184
+ },
185
+
186
+ /** The IR back out as `new Policy({ … })` against the generated classes. */
187
+ templateGenerator() {
188
+ return new CedarTemplateGenerator();
189
+ },
190
+
191
+ completionProvider(ctx: CompletionContext) {
192
+ return completions(ctx);
193
+ },
194
+
195
+ hoverProvider(ctx: HoverContext) {
196
+ return hover(ctx);
197
+ },
198
+
199
+ async docs(options?) {
200
+ const { generateDocs } = await import("./codegen/docs");
201
+ return generateDocs(options);
202
+ },
203
+
204
+ /** The shape of the `cedar` key in `chant.config.ts` — see ./config.ts. */
205
+ configSchema: cedarConfigSchema,
206
+
207
+ /**
208
+ * The pinned upstream is the Cedar *grammar*, not a downloaded spec (#1650).
209
+ *
210
+ * `CEDAR_WASM_VERSION` is the package version the self-upgrade tooling bumps;
211
+ * `CEDAR_LANG_VERSION` beside it is the language version `generate()` asserts
212
+ * at runtime, since a package bump that leaves the language at 4.5 cannot
213
+ * change what parses. Both live in src/spec/pin.ts, which also carries the
214
+ * content pin over the bundled default schema.
215
+ */
216
+ upstreamPin: {
217
+ file: "src/spec/pin.ts",
218
+ pattern: /export const CEDAR_WASM_VERSION\s*=\s*"([^"]+)"/,
219
+ replace: (v: string, line: string) => line.replace(/= "[^"]+"/, `= "${v}"`),
220
+ upstream: { owner: "cedar-policy", repo: "cedar", kind: "releases" as const },
221
+ },
222
+
223
+ // ── AVP lifecycle (#1652) ──────────────────────────────────────
224
+
225
+ /**
226
+ * AVP policy stores are taggable; the policies inside them are not, so the
227
+ * per-policy channel is a marker encoded into `Definition.Static.Description`
228
+ * and the store's tags are the coarse channel beside it. Only the two paths
229
+ * that actually read the description are declared (chant #1348) — the full
230
+ * design record is src/avp/OWNERSHIP.md.
231
+ */
232
+ ownershipChannel: {
233
+ keys: AVP_OWNERSHIP_KEYS,
234
+ reads: ["describeResources", "exportResources"] as const,
235
+ },
236
+
237
+ /**
238
+ * Observe the declared policy set against a live AVP policy store.
239
+ *
240
+ * The tri-state carries more weight here than on most substrates: a declared
241
+ * policy reported absent becomes a `create`, and creating a permit that
242
+ * already exists duplicates a grant. Every path that did not look says so.
243
+ */
244
+ async describeResources(options: {
245
+ environment: string;
246
+ buildOutput: string;
247
+ entityNames: string[];
248
+ entities: Map<string, { entityType: string; props: Record<string, unknown> }>;
249
+ stack?: string;
250
+ region?: string;
251
+ owned?: boolean;
252
+ }) {
253
+ const { describeAvpResources } = await import("./avp/describe-resources");
254
+ return describeAvpResources({
255
+ environment: options.environment,
256
+ entityNames: options.entityNames,
257
+ entities: options.entities,
258
+ ...(options.owned !== undefined ? { owned: options.owned } : {}),
259
+ ...(options.region ? { client: { region: options.region } } : {}),
260
+ });
261
+ },
262
+
263
+ /** The one kind this lexicon deploys, and therefore the one it can enumerate. */
264
+ ambientKinds(): string[] {
265
+ return [...AVP_AMBIENT_KINDS];
266
+ },
267
+
268
+ /**
269
+ * Policies in the store that nothing declares — a standing grant nobody in
270
+ * the source tree can see. For an authorization store this is a security
271
+ * finding rather than housekeeping.
272
+ */
273
+ async observeAmbient(options: {
274
+ environment: string;
275
+ kinds: string[];
276
+ observed: Record<string, ResourceMetadata>;
277
+ stack?: string;
278
+ region?: string;
279
+ }) {
280
+ const { observeAvpAmbient } = await import("./avp/ambient");
281
+ return observeAvpAmbient({
282
+ environment: options.environment,
283
+ kinds: options.kinds,
284
+ observed: options.observed,
285
+ ...(options.region ? { client: { region: options.region } } : {}),
286
+ });
287
+ },
288
+
289
+ /**
290
+ * Full-fidelity read of the live policies, as import IR. Server-written
291
+ * fields are stripped to the declared shape unless `verbatim` is set — see
292
+ * the note in src/avp/live-export.ts for what counts as server-written when
293
+ * the only thing a user authors is the statement.
294
+ */
295
+ async exportResources(options: {
296
+ environment: string;
297
+ stack?: string;
298
+ region?: string;
299
+ selector?: { type?: string; name?: string };
300
+ owned?: boolean;
301
+ verbatim?: boolean;
302
+ }) {
303
+ const { exportAvpResources } = await import("./avp/live-export");
304
+ return exportAvpResources({
305
+ environment: options.environment,
306
+ ...(options.selector ? { selector: options.selector } : {}),
307
+ ...(options.owned !== undefined ? { owned: options.owned } : {}),
308
+ ...(options.verbatim !== undefined ? { verbatim: options.verbatim } : {}),
309
+ ...(options.region ? { client: { region: options.region } } : {}),
310
+ });
311
+ },
312
+ };