@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,873 @@
1
+ /**
2
+ * The cedar lexicon's Starlight site.
3
+ *
4
+ * Pages are declared as `extraPages` rather than left as hand-written files in
5
+ * docs/. The pipeline rebuilds the sidebar from the pages it knows about on
6
+ * every run, and Starlight does not auto-discover, so a page the config has
7
+ * never heard of exists on disk and is reachable only by typing its URL
8
+ * (chant #1312).
9
+ *
10
+ * Two more pages come out of the pipeline itself: the generated rules table
11
+ * (`rules`) and the serialization reference. Both are linked from the sidebar
12
+ * automatically.
13
+ *
14
+ * Cross-namespace links are written as full `/chant/...` paths. This site's
15
+ * base is `/chant/lexicons/cedar/`, so a bare `/guide/...` would be rewritten
16
+ * to `/chant/lexicons/cedar/guide/...`; the rehype plugin's idempotency check
17
+ * leaves an already-project-rooted path alone. Sibling pages use `../slug/`,
18
+ * because `./slug` from an MDX body resolves as a child.
19
+ */
20
+
21
+ import { dirname, join } from "path";
22
+ import { fileURLToPath } from "url";
23
+ import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
24
+
25
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
26
+
27
+ // ── index ─────────────────────────────────────────────────────────
28
+
29
+ const overview = `The **cedar** lexicon is the typed authoring layer above [Cedar](https://www.cedarpolicy.com/), the vendor-neutral authorization policy language that joined the CNCF as a Sandbox project in December 2025.
30
+
31
+ Cedar is deliberately abstraction-free: no variables, no modules, no loops, and templates carrying exactly two slots (\`?principal\`, \`?resource\`). Its own toolchain validates and evaluates — it checks policies after they are written and decides requests at runtime. Everything upstream of the policy text is unowned, which is where this lexicon lives.
32
+
33
+ \`\`\`bash
34
+ npm install --save-dev @intentius/chant-lexicon-cedar
35
+ \`\`\`
36
+
37
+ ## Quick start
38
+
39
+ \`\`\`typescript
40
+ import { Policy, ReadAction } from "@intentius/chant-lexicon-cedar";
41
+
42
+ export const ownerRead = new Policy({
43
+ effect: "permit",
44
+ principal: { is: "App::User" },
45
+ action: { eq: ReadAction },
46
+ resource: { is: "App::Document" },
47
+ when: ["resource.owner == principal"],
48
+ });
49
+ \`\`\`
50
+
51
+ \`ReadAction\` and \`"App::User"\` are generated from *your* Cedar schema, so a
52
+ renamed entity type is a compiler-guided refactor and a typo'd action is a
53
+ compile error — not a validation failure after the text is written.
54
+
55
+ ## What comes out
56
+
57
+ | File | Who reads it |
58
+ |------|--------------|
59
+ | \`<name>.cedar\` | Every Cedar evaluator — Amazon Verified Permissions, cedar-agent, an embedded \`cedar-wasm\` |
60
+ | \`policies.cedar.json\` | The Cedar JSON policy format; also the parse source for import |
61
+
62
+ chant appears in neither. An emitted policy set walks away and is consumed by
63
+ any evaluator with chant nowhere in sight.
64
+
65
+ ## Cedar is a target, never a gate
66
+
67
+ There is no \`cedarGate()\` and there will not be one.
68
+ [Organizational policy](/chant/guide/organizational-policy/) in chant is
69
+ TypeScript post-synth checks; a second policy engine would duplicate the lint
70
+ engine. chant compiles *to* Cedar; it is not governed *by* Cedar.`;
71
+
72
+ // ── getting-started ───────────────────────────────────────────────
73
+
74
+ const gettingStarted = `Three steps, in this order. The second one is the one people skip.
75
+
76
+ ## 1. Scaffold
77
+
78
+ \`\`\`bash
79
+ npx chant init --lexicon cedar --template default my-authz
80
+ cd my-authz && npm install
81
+ \`\`\`
82
+
83
+ Three templates ship: \`default\` (a permit/forbid pair), \`avp-embedding\` (a
84
+ multi-tenant store bound for Verified Permissions), and \`gateway-policy-set\`
85
+ (API routes as entities, behind a deny floor). Each writes a
86
+ \`schema.cedarschema\` at the project root and a \`src/policies.ts\` typed
87
+ against it.
88
+
89
+ ## 2. Generate
90
+
91
+ \`\`\`bash
92
+ npx chant generate --lexicon cedar
93
+ \`\`\`
94
+
95
+ This is not optional and it is not a one-time setup step. The classes and
96
+ action constants \`policies.ts\` imports **do not exist** until generate has read
97
+ your schema. Re-run it whenever the schema changes.
98
+
99
+ What it produces, per declaration:
100
+
101
+ | Schema | Generated |
102
+ |---|---|
103
+ | \`entity Document in [Folder] = { … }\` | \`Document\`, \`DocumentAttributes\`, \`DocumentUid\` |
104
+ | \`action read appliesTo { … }\` | \`ReadAction\`, \`ReadContext\` |
105
+ | — | \`Policy\`, \`EntityTypeName\`, \`ActionUid\`, \`PolicyScope\`, \`ALL_ACTIONS\`, \`ALL_ENTITY_TYPES\` |
106
+
107
+ ## 3. Build
108
+
109
+ \`\`\`bash
110
+ npx chant build
111
+ \`\`\`
112
+
113
+ Both artifacts land in \`dist/\`. Every emitted policy set is validated against
114
+ your schema by \`cedar-wasm\` — the real Cedar validator, running in-process.
115
+ No CLI on PATH, no Docker.
116
+
117
+ ## Then
118
+
119
+ \`\`\`bash
120
+ npx chant coverage --lexicon cedar # is every schema declaration generated?
121
+ \`\`\`
122
+
123
+ The MCP tool \`cedar:coverage\` answers the harder question — which schema
124
+ declarations the *policy set* actually reaches. See [Lint Rules](../lint-rules/).
125
+
126
+ ## The example projects
127
+
128
+ Two ship with the lexicon:
129
+
130
+ - \`examples/getting-started\` — one permit, one forbid, against the bundled default schema.
131
+ - \`examples/basic-policies\` — a project-local \`schema.cedarschema\` and a four-policy set.
132
+
133
+ Both are exercised in CI: the emitted \`.cedar\` text is handed straight to
134
+ \`cedar-wasm\`, so a policy set chant is happy with but Cedar rejects fails the
135
+ build rather than the deploy.
136
+ `;
137
+
138
+ // ── schema ────────────────────────────────────────────────────────
139
+
140
+ const schemaPage = `Unlike every other lexicon, cedar's interesting spec is not one global upstream. It is **your** schema.
141
+
142
+ The pinned upstream here is the Cedar *grammar* — \`@cedar-policy/cedar-wasm\`,
143
+ whose language version is asserted before anything is emitted. The schema is
144
+ the input.
145
+
146
+ ## Where it is looked for
147
+
148
+ 1. \`cedar.schema\` in \`chant.config.ts\`
149
+ 2. \`schema.cedarschema\` in the project root
150
+ 3. the schema bundled with the lexicon
151
+
152
+ Step 3 exists so \`generate()\` has something to read in a fresh clone — a
153
+ generate step that only works after the user does something is a generate step
154
+ nothing gates. It is a real application-authorization model, not a stub.
155
+
156
+ **Turn it off once you have your own:**
157
+
158
+ \`\`\`typescript
159
+ // chant.config.ts
160
+ import type { ChantConfig } from "@intentius/chant";
161
+ import "@intentius/chant-lexicon-cedar";
162
+
163
+ export default {
164
+ lexicons: ["cedar"],
165
+ cedar: {
166
+ schema: "authz/app.cedarschema",
167
+ validation: {
168
+ mode: "strict",
169
+ warnings: "warn",
170
+ requireProjectSchema: true,
171
+ },
172
+ },
173
+ } satisfies ChantConfig;
174
+ \`\`\`
175
+
176
+ Without \`requireProjectSchema\`, a typo in the path is the difference between
177
+ "your entity types" and "the bundled default" — and the build succeeds either
178
+ way.
179
+
180
+ ## Writing one
181
+
182
+ \`\`\`
183
+ namespace App {
184
+ type Level = Long;
185
+ type TagSet = Set<String>;
186
+
187
+ entity Group = { "name": String };
188
+
189
+ entity User in [Group] = {
190
+ "email": String,
191
+ "level": Level,
192
+ "manager"?: User,
193
+ "roles": TagSet,
194
+ };
195
+
196
+ entity Document = {
197
+ "title": String,
198
+ "owner": User,
199
+ "classification": String,
200
+ };
201
+
202
+ action read, write appliesTo {
203
+ principal: [User],
204
+ resource: [Document],
205
+ context: { "mfa": Bool }
206
+ };
207
+ }
208
+ \`\`\`
209
+
210
+ Common types (\`type Level = Long\`) are resolved away by the codegen — real
211
+ schemas use them, so the resolver collapses them rather than pretending they
212
+ are absent.
213
+
214
+ ## Config keys
215
+
216
+ | Key | Meaning |
217
+ |-----|---------|
218
+ | \`schema\` | Path to the \`.cedarschema\`, relative to the project root |
219
+ | \`validation.mode\` | \`"strict"\`. The only mode cedar-wasm 4.12 accepts |
220
+ | \`validation.warnings\` | \`"ignore"\`, \`"warn"\`, or \`"error"\` — the validator reports "policy is impossible" here, separately from errors |
221
+ | \`validation.requireProjectSchema\` | Refuse to fall back to the bundled default |
222
+
223
+ The namespace is a \`strictObject\`, nested levels included: a typo inside
224
+ \`validation\` is a config error, not a silently ignored key.
225
+
226
+ ## Both syntaxes
227
+
228
+ Cedar's schema grammar has a human-readable form and a JSON form. The codegen
229
+ consumes the human-readable one; \`cedar-wasm\` converts between them
230
+ (\`schemaToJson\`, \`schemaToText\`) if you need the other.
231
+
232
+ ## The pin
233
+
234
+ The language version is pinned separately from the package version. A package
235
+ bump that leaves the language at 4.x cannot change what parses, so
236
+ \`CEDAR_WASM_VERSION\` is what the self-upgrade tooling moves and
237
+ \`CEDAR_LANG_VERSION\` is what \`generate()\` asserts at runtime. Both live in
238
+ \`src/spec/pin.ts\`, beside a content pin over the bundled default schema.
239
+ `;
240
+
241
+ // ── resources ─────────────────────────────────────────────────────
242
+
243
+ const resourcesPage = `Everything below \`Policy\` is generated from your schema, so the exact list depends on it. What follows is the shape.
244
+
245
+ ## Policy
246
+
247
+ The one declaration this lexicon serializes. One \`Policy\` per policy.
248
+
249
+ \`\`\`typescript
250
+ import { Policy } from "@intentius/chant-lexicon-cedar";
251
+
252
+ export const ownerRead = new Policy({ /* … */ });
253
+ \`\`\`
254
+
255
+ Its props are covered in [Policies](../policies/).
256
+
257
+ ## Entity classes
258
+
259
+ Each \`entity T = { … }\` in the schema produces three things:
260
+
261
+ | Generated | Kind | What it is |
262
+ |---|---|---|
263
+ | \`Document\` | resource class | The entity type, for declaring entities |
264
+ | \`DocumentAttributes\` | property class | Its attribute record, usable standalone |
265
+ | \`DocumentUid\` | type | \`` + "`" + `App::Document::"\${string}"` + "`" + `\` — a template-literal type |
266
+
267
+ \`DocumentUid\` is the one that earns its keep. It makes a mistyped namespace a
268
+ compile error:
269
+
270
+ \`\`\`typescript
271
+ import type { DocumentUid } from "@intentius/chant-lexicon-cedar";
272
+
273
+ const contract: DocumentUid = 'App::Document::"contract-2026"'; // ok
274
+ const typo: DocumentUid = 'App::Docmnt::"contract-2026"'; // compile error
275
+ \`\`\`
276
+
277
+ ## Action constants
278
+
279
+ Each action produces a \`const\` (not a class — an action UID is a value, not a
280
+ constructor) and a context record type:
281
+
282
+ \`\`\`typescript
283
+ import { ReadAction, type ReadContextProps } from "@intentius/chant-lexicon-cedar";
284
+
285
+ // ReadAction === 'App::Action::"read"'
286
+ \`\`\`
287
+
288
+ ## Schema-wide types
289
+
290
+ | Name | What it holds |
291
+ |---|---|
292
+ | \`EntityTypeName\` | Union of every entity type name, as Cedar writes it |
293
+ | \`EntityUid\` | Union of every entity UID type |
294
+ | \`ActionUid\` | Union of every action UID |
295
+ | \`PolicyRef\` | \`EntityUid \\| ActionUid\` — anything a scope may name |
296
+ | \`PolicyScope\` | One scope position |
297
+ | \`ALL_ACTIONS\` | Every action constant, for exhaustive iteration |
298
+ | \`ALL_ENTITY_TYPES\` | Every entity type name |
299
+
300
+ \`ALL_ACTIONS\` is what a cross-domain post-synth check iterates when asking "is
301
+ any action uncovered".
302
+
303
+ ## Naming
304
+
305
+ Names are derived from the schema and de-duplicated against a single pool, so a
306
+ schema declaring an entity type literally called \`UserAttributes\` beside a
307
+ \`User\` does not get its name stolen by the derived record type. Two
308
+ declarations in the same namespace reducing to the same short name are both
309
+ qualified rather than one silently overwriting the other.
310
+
311
+ ## Coverage
312
+
313
+ \`\`\`bash
314
+ npx chant coverage --lexicon cedar --verbose
315
+ \`\`\`
316
+
317
+ Reports whether every entity type and every action in the schema is reachable
318
+ from the generated artifacts. A gap is a defect: an action with no generated
319
+ constant is one a policy can only name as a hand-typed string, which is the
320
+ failure mode this lexicon exists to remove.
321
+ `;
322
+
323
+ // ── policies ──────────────────────────────────────────────────────
324
+
325
+ const policiesPage = `A policy is a \`Policy\` value. The serializer turns each one into a \`.cedar\` block and an entry in the JSON policy set.
326
+
327
+ ## Props
328
+
329
+ | Prop | Meaning |
330
+ |------|---------|
331
+ | \`effect\` | \`"permit"\` or \`"forbid"\`. Defaults to \`permit\` |
332
+ | \`principal\`, \`action\`, \`resource\` | Scope constraints. Omit for unconstrained |
333
+ | \`when\` | Cedar expression strings, one \`when { … }\` clause each |
334
+ | \`unless\` | Cedar expression strings, one \`unless { … }\` clause each |
335
+ | \`annotations\` | \`Record<string, string>\`, emitted as \`@key("value")\` |
336
+
337
+ ## Scope forms
338
+
339
+ The scope type mirrors the grammar exactly:
340
+
341
+ | Written | Emitted |
342
+ |---|---|
343
+ | \`{}\` or omitted | \`principal\` |
344
+ | \`{ eq: X }\` | \`principal == X\` |
345
+ | \`{ in: X }\` | \`principal in X\` |
346
+ | \`{ in: [X, Y] }\` | \`principal in [X, Y]\` |
347
+ | \`{ is: "App::User" }\` | \`principal is App::User\` |
348
+ | \`{ is: "App::User", in: X }\` | \`principal is App::User in X\` |
349
+
350
+ \`is\` takes an \`EntityTypeName\`; \`eq\` and \`in\` take a \`PolicyRef\`. Both are
351
+ schema-derived unions, so an entity type the schema never declared does not
352
+ compile.
353
+
354
+ ## Conditions
355
+
356
+ \`when\` and \`unless\` carry Cedar expression **text**:
357
+
358
+ \`\`\`typescript
359
+ export const ownerWrite = new Policy({
360
+ effect: "permit",
361
+ principal: { is: "App::User" },
362
+ action: { eq: WriteAction },
363
+ resource: { is: "App::Document" },
364
+ when: ["resource.owner == principal", "context.mfa == true"],
365
+ unless: ['resource.classification == "confidential"'],
366
+ });
367
+ \`\`\`
368
+
369
+ Each array element becomes its own clause. They stay strings on purpose:
370
+ Cedar's expression grammar *is* the policy language, and typing it in
371
+ TypeScript is [import and reconcile](../importing/)'s problem, not codegen's.
372
+
373
+ ## Policy ids
374
+
375
+ The id comes from the export's logical name, kebab-cased — \`allowAdminRead\`
376
+ becomes \`@id("allow-admin-read")\`. Set \`annotations.id\` to pin one:
377
+
378
+ \`\`\`typescript
379
+ export const anything = new Policy({
380
+ annotations: { id: "tenant-isolation", owner: "platform" },
381
+ // …
382
+ });
383
+ \`\`\`
384
+
385
+ An explicit \`id\` wins. \`@id\` is emitted first, then your annotations in
386
+ declaration order.
387
+
388
+ ## permit and forbid
389
+
390
+ Cedar is default-deny, so a \`permit\` is what grants anything at all. A
391
+ \`forbid\` is not the absence of a grant — it beats every \`permit\` in the set
392
+ unconditionally, which makes it the only construct that survives a wider grant
393
+ somebody adds next quarter.
394
+
395
+ Two consequences worth internalizing:
396
+
397
+ - **A forbid with no guard denies everything**, and no permit can lift it. The
398
+ \`DenyByDefaultSet\` [composite](../composites/) throws rather than emit one.
399
+ - **"Nobody wrote a permit" and "a forbid says no" evaluate identically** and
400
+ read completely differently in review. Give sensitive resources an explicit
401
+ floor.
402
+
403
+ ## The JSON companion
404
+
405
+ Every build writes \`policies.cedar.json\` beside the \`.cedar\` text — the same
406
+ policy set in Cedar's JSON policy format, produced from the same structured
407
+ model rather than by re-parsing the text.
408
+
409
+ One deliberate gap: condition bodies are expression *ASTs* in that format, and
410
+ the model carries expression text. They are written as \`{ "__expr": "<text>" }\`,
411
+ Cedar's own escape for source-given expressions, so the file stays
412
+ machine-readable instead of inventing a private key. Producing real trees means
413
+ parsing Cedar expression text, which lands with import.
414
+ `;
415
+
416
+ // ── composites ────────────────────────────────────────────────────
417
+
418
+ const compositesPage = `Cedar has no functions, no modules, and no loops, so every repeated policy shape is copy-paste in \`.cedar\` text. A TypeScript factory is the only place the abstraction can live.
419
+
420
+ These follow chant's general
421
+ [composite resources](/chant/guide/composite-resources/) pattern: a function
422
+ returning declared resources, discovered like any other export.
423
+
424
+ \`\`\`typescript
425
+ import { OwnerCanManage, DenyByDefaultSet } from "@intentius/chant-lexicon-cedar";
426
+ \`\`\`
427
+
428
+ ## OwnerCanManage
429
+
430
+ "The owner of a thing may act on it" — the most-repeated shape in any policy
431
+ set, and where the two classic mistakes get made: the \`when\` guard names an
432
+ attribute the schema spells differently, or the grant is written wide and the
433
+ scoping clause is forgotten.
434
+
435
+ \`\`\`typescript
436
+ import { ReadAction, WriteAction } from "@intentius/chant-lexicon-cedar";
437
+
438
+ export const docOwner = OwnerCanManage({
439
+ entityType: "App::Document",
440
+ actions: [ReadAction, WriteAction],
441
+ principal: "App::User",
442
+ });
443
+ \`\`\`
444
+
445
+ Emits:
446
+
447
+ \`\`\`cedar
448
+ @id("doc-owner")
449
+ @composite("OwnerCanManage")
450
+ @scopedTo("App::Document")
451
+ permit (
452
+ principal is App::User,
453
+ action in [App::Action::"read", App::Action::"write"],
454
+ resource is App::Document
455
+ )
456
+ when { resource.owner == principal };
457
+ \`\`\`
458
+
459
+ | Option | Default | Notes |
460
+ |---|---|---|
461
+ | \`entityType\` | required | \`EntityTypeName\` — schema-checked |
462
+ | \`actions\` | unconstrained | One action emits \`==\`, several emit \`in [ … ]\` |
463
+ | \`ownerAttribute\` | \`"owner"\` | The attribute holding the owner |
464
+ | \`principal\` | unconstrained | A bare type string becomes \`is T\`; a full scope passes through |
465
+ | \`when\` | — | Appended after the ownership test |
466
+ | \`unless\` | — | Omitted entirely when not asked for |
467
+ | \`annotations\` | — | Merged over the generated ones; an explicit \`id\` wins |
468
+
469
+ Leaving \`actions\` off produces a wide grant, so it has to be asked for by
470
+ omitting the field rather than arriving by accident.
471
+
472
+ ## DenyByDefaultSet
473
+
474
+ A guarded \`forbid\` and the permits it governs, returned from one call. The
475
+ pattern teams write by hand is a forbid at the top of a file and a pile of
476
+ permits under it with nothing tying the two together — delete the forbid and
477
+ the permits keep working, wider than anyone intended.
478
+
479
+ \`\`\`typescript
480
+ import { DeleteAction } from "@intentius/chant-lexicon-cedar";
481
+
482
+ const guarded = DenyByDefaultSet({
483
+ policies: [docOwner],
484
+ entityType: "App::Document",
485
+ actions: DeleteAction,
486
+ when: ['resource.classification == "confidential"'],
487
+ unless: ['principal == App::User::"archivist"'],
488
+ });
489
+
490
+ export const confidentialFloor = guarded.floor;
491
+ export const documentOwnerGrant = guarded.members[0];
492
+ \`\`\`
493
+
494
+ | Returned | What it is |
495
+ |---|---|
496
+ | \`floor\` | The \`forbid\` policy |
497
+ | \`members\` | The permits, unchanged, in the order given |
498
+ | \`all\` | \`[floor, ...members]\` |
499
+
500
+ \`when\` is required. An unguarded forbid overrides every permit in the set, so
501
+ the result would authorize nothing — the composite throws rather than emit it.
502
+
503
+ ## Where composites go in a build
504
+
505
+ They return \`Declarable\` values like any other resource, so exporting them
506
+ from a discovered file is all that is needed:
507
+
508
+ \`\`\`typescript
509
+ export const [floor, grant] = DenyByDefaultSet({ /* … */ }).all;
510
+ \`\`\`
511
+
512
+ The floor is emitted first. Cedar's evaluation is order-independent — a forbid
513
+ wins wherever it sits — but a file that reads floor-first matches how the set
514
+ is reasoned about.
515
+ `;
516
+
517
+ // ── lint-rules ────────────────────────────────────────────────────
518
+
519
+ const lintRulesPage = `Rules under the \`CED\` prefix. The complete generated table is on [All Rules](../rules/); this page is the reasoning.
520
+
521
+ ## The two engines, and why there is only one
522
+
523
+ Cedar's own validator runs in-process through \`@cedar-policy/cedar-wasm\` — the
524
+ real thing, not a reimplementation. It answers "is this policy well-formed
525
+ against this schema".
526
+
527
+ Everything else is a TypeScript check. There is no \`cedarGate()\`, because
528
+ organizational policy in chant is post-synth checks and a second policy engine
529
+ would duplicate the lint engine.
530
+
531
+ ## The bare-permit wall
532
+
533
+ \`\`\`cedar
534
+ permit (principal, action, resource);
535
+ \`\`\`
536
+
537
+ Every scope unconstrained, no conditions. Legal Cedar, validates clean, grants
538
+ everything to everyone. The rule is **env-aware**:
539
+
540
+ | Environment | Verdict |
541
+ |---|---|
542
+ | dev / local | warn |
543
+ | staging | warn |
544
+ | prod | **fail** |
545
+
546
+ Env-aware rather than absolute because an absolute rule gets suppressed the
547
+ first time it fires during development, and a suppressed rule protects nothing.
548
+ A permit with any constrained scope position, or any \`when\`/\`unless\`, is not
549
+ bare.
550
+
551
+ ## Schema-absent references
552
+
553
+ A policy naming an entity type or action the schema does not declare fails
554
+ **everywhere**, dev included. Two failure modes hide behind it:
555
+
556
+ - **The typo** — \`App::Documnt\`. Generated classes make this a compile error
557
+ before any check runs; it survives only inside \`when\`/\`unless\` expression
558
+ strings.
559
+ - **The silent no-op** — a scope naming an absent entity type *parses*, and
560
+ Cedar's request-envelope resolver answers "success, nothing". The policy is
561
+ well-formed, deployable, and can never fire.
562
+
563
+ ## Cross-domain checks
564
+
565
+ The differentiator, and the thing no Cedar tool can express: Cedar only ever
566
+ sees policies, while a chant build holds the policies *and* the infrastructure
567
+ they govern in one entity graph. So a post-synth check can span both:
568
+
569
+ - an entity id in a policy references an actual resource declaration
570
+ - every declared bucket is covered by at least one \`forbid\`
571
+ - no schema action lacks any policy
572
+
573
+ These are ordinary post-synth checks. Nothing exotic — they just need both
574
+ halves in the same build, which is the arrangement chant already has.
575
+
576
+ ## Coverage, two ways
577
+
578
+ \`\`\`bash
579
+ npx chant coverage --lexicon cedar # schema -> generated artifacts
580
+ \`\`\`
581
+
582
+ The MCP tool asks the other half:
583
+
584
+ \`\`\`
585
+ cedar:coverage { "path": ".", "format": "text" }
586
+ \`\`\`
587
+
588
+ It builds the policy set, hands each policy to Cedar's own
589
+ \`getValidRequestEnvsPolicy\` alongside the schema, and reports:
590
+
591
+ | Field | Meaning |
592
+ |---|---|
593
+ | \`uncovered\` | Declarations no policy can apply to |
594
+ | \`forbidOnly\` | Declarations reachable only from a \`forbid\` — nothing grants them |
595
+ | \`inert\` | Policies whose request envelope is empty; they can never fire |
596
+ | \`unresolved\` | Policies the resolver rejected outright |
597
+ | \`parseErrors\` | Why the set would not split into policies |
598
+
599
+ Container entity types — the ones that appear in no action's \`appliesTo\` and
600
+ exist only to be \`in\` — show as uncovered even under a bare permit. That is
601
+ the resolver telling the truth: no request can name them.
602
+
603
+ ## What is not here yet
604
+
605
+ The post-synth checks encoding all of the above are
606
+ [INTENTIUS/chant#1651](https://github.com/INTENTIUS/chant/issues/1651). Today
607
+ the lexicon ships the source-level rule set and the validation plumbing; the
608
+ checks that span policies and estate land there.
609
+ `;
610
+
611
+ // ── importing ─────────────────────────────────────────────────────
612
+
613
+ const importingPage = `Bringing an existing Cedar policy set into typed source, and pulling console edits back.
614
+
615
+ ## The round trip
616
+
617
+ \`\`\`
618
+ .cedar text -> JSON policy format -> TypeScript -> .cedar text
619
+ \`\`\`
620
+
621
+ The JSON policy format is the parse source, not the \`.cedar\` text. That is why
622
+ the serializer produces it from the same structured model rather than by
623
+ re-parsing what it just wrote: the two views cannot drift, and the import path
624
+ reads a format with an actual grammar rather than doing a second parse of the
625
+ surface syntax.
626
+
627
+ \`cedar-wasm\` converts in both directions — \`policyToJson\`, \`policyToText\`,
628
+ \`policySetTextToParts\` — so a set that exists only as \`.cedar\` text is one
629
+ call away from the importable form.
630
+
631
+ ## What round-tripping has to preserve
632
+
633
+ | Carried | Notes |
634
+ |---|---|
635
+ | Effect | \`permit\` / \`forbid\` |
636
+ | All three scope positions | Including \`is T in E\` |
637
+ | \`when\` / \`unless\` clauses | In order |
638
+ | Annotations | Including \`@id\`, which becomes the export name's override |
639
+
640
+ The one asymmetry today is condition bodies. The JSON policy format wants an
641
+ expression *tree*; the model carries expression *text*, written as
642
+ \`{ "__expr": "…" }\` — Cedar's own escape for a source-given expression.
643
+ Producing real trees means parsing Cedar expression text, which is precisely
644
+ the work the import path has to do anyway.
645
+
646
+ ## Reconcile
647
+
648
+ The interesting case is not the initial import. It is the policy somebody edited
649
+ in a console: a \`ReconcileOp\` pulls it back into source, and the diff is
650
+ reviewable.
651
+
652
+ An **ambient permit** — one found in a policy store that no source file declares
653
+ — is not housekeeping. It is a standing grant somebody made outside review, and
654
+ it is a security finding.
655
+
656
+ ## Ownership
657
+
658
+ AVP policy *stores* are taggable; individual policies are not. The ownership
659
+ channel is store-scoped until finer granularity is proven, and no channel is
660
+ declared until its read paths are implemented — \`chant dev check-lexicon\` has
661
+ a tier-2 gate for exactly the failure of declaring a marker channel on a path
662
+ the plugin does not implement.
663
+
664
+ ## Status
665
+
666
+ The JSON policy-format parser, the TypeScript generator, and the
667
+ \`ReconcileOp\` example are
668
+ [INTENTIUS/chant#1653](https://github.com/INTENTIUS/chant/issues/1653). The
669
+ serializer already emits the format they read, which is why it exists as a
670
+ first-class output rather than a debugging aid.
671
+ `;
672
+
673
+ // ── avp ───────────────────────────────────────────────────────────
674
+
675
+ const avpPage = `Amazon Verified Permissions is one deployment vehicle for Cedar. It is not the only one, and the lexicon does not privilege it.
676
+
677
+ ## The seam
678
+
679
+ chant already ships the deployment half. \`AWS::VerifiedPermissions::Policy\` in
680
+ the [aws lexicon](/chant/lexicons/aws/) carries its policy text in
681
+ \`definition.static.statement\`, typed \`CedarPolicy\` — which is to say,
682
+ \`string\`. That string is the seam.
683
+
684
+ \`\`\`
685
+ cedar lexicon aws lexicon
686
+ schema -> Policy -> .cedar text -> VerifiedPermissionsPolicy.definition.static.statement
687
+ \`\`\`
688
+
689
+ Everything upstream of the string — the schema, entity types, actions, scope
690
+ constraints — belongs to this lexicon. Everything downstream — the policy
691
+ store, the CloudFormation \`ApplyOp\`, the IAM around it — belongs to the aws
692
+ lexicon and already works.
693
+
694
+ ## What ships today
695
+
696
+ A policy's statement text is what the serializer emits for that one entity —
697
+ the same bytes as the \`.cedar\` file, so the deployed policy and the reviewed
698
+ file cannot disagree.
699
+
700
+ \`\`\`typescript
701
+ import { Policy, ReadAction } from "@intentius/chant-lexicon-cedar";
702
+
703
+ export const ownerRead = new Policy({
704
+ effect: "permit",
705
+ principal: { is: "App::User" },
706
+ action: { eq: ReadAction },
707
+ resource: { is: "App::Document" },
708
+ when: ["resource.owner == principal"],
709
+ });
710
+ \`\`\`
711
+
712
+ The \`avp-embedding\` init template scaffolds a multi-tenant store's schema and
713
+ a three-policy set shaped for one.
714
+
715
+ ## What is deferred
716
+
717
+ A typed handoff — a \`VerifiedPermissionsPolicy\` whose \`statement\` accepts a
718
+ \`Policy\` value directly rather than a string a caller assembled — is landing
719
+ with [INTENTIUS/chant#1652](https://github.com/INTENTIUS/chant/issues/1652),
720
+ along with \`describeResources()\`/\`observeAmbient()\` against a live policy
721
+ store and the ownership-channel design.
722
+
723
+ Until it does:
724
+
725
+ - **Do not hand-type a statement string.** A prose
726
+ \`"permit(principal, action, resource);"\` inside an AVP resource is exactly
727
+ what this lexicon exists to remove, and the bare-permit wall fails it in a
728
+ prod build.
729
+ - **Do not tag individual policies for ownership.** Stores are taggable;
730
+ policies are not.
731
+ - **Treat an ambient permit as a finding.** A permit in a store that no source
732
+ file declares is a standing grant made outside review.
733
+
734
+ ## The other evaluators
735
+
736
+ If the target is not AVP there is no embedding step at all — emit the files and
737
+ ship them.
738
+
739
+ | Target | How |
740
+ |---|---|
741
+ | cedar-agent | Point it at the emitted \`.cedar\` and an entity store |
742
+ | Embedded \`cedar-wasm\` | Load the policy text in-process, call \`isAuthorized\` |
743
+ | Edge / Cloudflare-style | The same file, read at the edge |
744
+
745
+ ## Cedar for Kubernetes
746
+
747
+ The CNCF push includes Cedar as a Kubernetes authorizer with policies as CRDs.
748
+ Those kinds belong to the [k8s lexicon](/chant/lexicons/k8s/)'s CRD sources —
749
+ the same rule that kept \`helm.cattle.io\` out of k3s. What this lexicon does
750
+ there is lint the policy text embedded in those kinds, the pattern the ARGO
751
+ rules already use.
752
+ `;
753
+
754
+ // ── Output format, for the generated serialization page ───────────
755
+
756
+ const outputFormat = `The cedar lexicon emits two views of one policy set.
757
+
758
+ **\`<name>.cedar\`** — the primary output, and the surface every Cedar evaluator reads.
759
+
760
+ \`\`\`cedar
761
+ @id("owner-read")
762
+ @doc("Owners always read their own documents.")
763
+ permit (
764
+ principal is App::User,
765
+ action in [App::Action::"read", App::Action::"list"],
766
+ resource is App::Document
767
+ )
768
+ when { resource.owner == principal };
769
+ \`\`\`
770
+
771
+ **\`policies.cedar.json\`** — the Cedar JSON policy format, written alongside.
772
+
773
+ \`\`\`json
774
+ {
775
+ "staticPolicies": {
776
+ "owner-read": {
777
+ "effect": "permit",
778
+ "principal": { "op": "is", "entity_type": "App::User" },
779
+ "action": { "op": "in", "entities": [{ "type": "App::Action", "id": "read" }] },
780
+ "resource": { "op": "is", "entity_type": "App::Document" },
781
+ "conditions": [{ "kind": "when", "body": { "__expr": "resource.owner == principal" } }],
782
+ "annotations": { "id": "owner-read" }
783
+ }
784
+ },
785
+ "templates": {},
786
+ "templateLinks": []
787
+ }
788
+ \`\`\`
789
+
790
+ Both come from the same structured model in one pass, so they cannot drift. The
791
+ JSON form is also the parse source for import — see [Importing](../importing/).
792
+ `;
793
+
794
+ /**
795
+ * Generate the docs site for the cedar lexicon.
796
+ */
797
+ export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
798
+ const config: DocsConfig = {
799
+ name: "cedar",
800
+ displayName: "Cedar",
801
+ description: "Typed authoring for Cedar authorization policies",
802
+ distDir: join(pkgDir, "dist"),
803
+ outDir: join(pkgDir, "docs"),
804
+ srcDir: join(pkgDir, "src"),
805
+ basePath: process.env.DOCS_BASE_PATH ?? "/chant/lexicons/cedar/",
806
+ overview,
807
+ outputFormat,
808
+ // Cedar declarations are namespaced `App::Document` and
809
+ // `App::Action::"read"`, so the first segment is the namespace — which is
810
+ // the only grouping a Cedar schema has.
811
+ serviceFromType: (type: string) => type.split("::")[0] ?? type,
812
+ extraPages: [
813
+ {
814
+ slug: "getting-started",
815
+ title: "Getting Started",
816
+ description: "Scaffold, generate, build — in that order.",
817
+ content: gettingStarted,
818
+ },
819
+ {
820
+ slug: "schema",
821
+ title: "Schema",
822
+ description: "The .cedarschema this lexicon's codegen reads, and how it is resolved.",
823
+ content: schemaPage,
824
+ },
825
+ {
826
+ slug: "resources",
827
+ title: "Resources",
828
+ description: "Policy, and the entity and action declarations generated from your schema.",
829
+ content: resourcesPage,
830
+ },
831
+ {
832
+ slug: "policies",
833
+ title: "Policies",
834
+ description: "Policy props, scope forms, conditions, annotations, and ids.",
835
+ content: policiesPage,
836
+ },
837
+ {
838
+ slug: "composites",
839
+ title: "Composites",
840
+ description: "OwnerCanManage and DenyByDefaultSet — the shapes Cedar has nowhere to put.",
841
+ content: compositesPage,
842
+ },
843
+ {
844
+ slug: "lint-rules",
845
+ title: "Lint Rules",
846
+ description: "The bare-permit wall, schema-absent references, and cross-domain checks.",
847
+ content: lintRulesPage,
848
+ },
849
+ {
850
+ slug: "importing",
851
+ title: "Importing",
852
+ description: "The JSON policy format round trip, reconcile, and ownership.",
853
+ content: importingPage,
854
+ },
855
+ {
856
+ slug: "avp",
857
+ title: "Verified Permissions",
858
+ description: "The AVP statement seam, and the other Cedar evaluators.",
859
+ content: avpPage,
860
+ },
861
+ ],
862
+ };
863
+
864
+ const result = docsPipeline(config);
865
+ writeDocsSite(config, result);
866
+
867
+ if (options?.verbose) {
868
+ console.error(
869
+ `Generated docs: ${result.pages.size} pages, ${result.stats.resources} resources, ` +
870
+ `${result.stats.properties} property types, ${result.stats.rules} rules`,
871
+ );
872
+ }
873
+ }