@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,119 @@
1
+ ---
2
+ skill: chant-cedar-meta-policy
3
+ description: Organizational policy over Cedar policy sets — the bare-permit wall, forbid conventions, and env-aware severity
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Policy About Policy
8
+
9
+ ## The ruling this sits under
10
+
11
+ Cedar is a target, never chant's gate. There is no `cedarGate()` and there will
12
+ not be one: organizational policy in chant is TypeScript post-synth checks, and
13
+ a second policy engine would duplicate the lint engine and break "TypeScript is
14
+ the one language."
15
+
16
+ So the rules below are ordinary `PostSynthCheck`s and lint rules under the `CED`
17
+ prefix. They read the emitted policy set the way any other check reads emitted
18
+ YAML. Cedar's own validator runs beside them, in-process, through
19
+ `@cedar-policy/cedar-wasm` — no CLI on PATH, no Docker.
20
+
21
+ ## The bare-permit wall
22
+
23
+ ```cedar
24
+ permit (principal, action, resource);
25
+ ```
26
+
27
+ Every scope position unconstrained, no `when`, no `unless`. It grants every
28
+ principal every action on every resource. It is legal Cedar, it validates
29
+ clean, and it is the single most damaging line a policy set can contain.
30
+
31
+ The rule is **env-aware, not absolute**:
32
+
33
+ | Environment | Verdict |
34
+ |---|---|
35
+ | dev / local | warn — a scratch policy set during development is a normal state |
36
+ | staging | warn, and expect it fixed before promotion |
37
+ | **prod** | **fail the build** |
38
+
39
+ Env-aware because an absolute rule gets suppressed the first time it fires
40
+ during development, and a suppressed rule protects nothing. Failing only where
41
+ the blast radius is real keeps the wall credible.
42
+
43
+ A permit with any of the following is not bare and does not trip the wall:
44
+
45
+ - a constrained scope position (`is`, `in`, `==`) on principal, action, or
46
+ resource, or
47
+ - at least one `when` or `unless` clause.
48
+
49
+ ## Schema-absent references
50
+
51
+ A policy naming an entity type or action the schema does not declare fails
52
+ **everywhere** — dev included. There is no environment where that is intended.
53
+ Two distinct failure modes hide behind it:
54
+
55
+ 1. **The typo.** `App::Documnt`. With generated classes this is a compile error
56
+ before any check runs, which is the point of the codegen. It survives only in
57
+ hand-written expression strings inside `when`/`unless`.
58
+ 2. **The silent no-op.** A scope naming an entity type outside the schema
59
+ *parses*, and `getValidRequestEnvsPolicy` answers "success, nothing" — an
60
+ empty request envelope. The policy is well-formed, deployed, and can never
61
+ fire. `cedar:coverage` reports these under `inert`.
62
+
63
+ ## Forbid conventions
64
+
65
+ Cedar is default-deny, so a `forbid` is never about the absence of a grant. It
66
+ is about the presence of a bad one: a forbid beats every permit in the set
67
+ unconditionally, which makes it the only construct that survives a later, wider
68
+ grant somebody adds next quarter.
69
+
70
+ Three conventions follow:
71
+
72
+ **Every forbid carries a guard.** An unguarded `forbid (principal, action,
73
+ resource)` denies everything and no permit can lift it — the policy set
74
+ authorizes nothing. `DenyByDefaultSet` throws on an empty `when` for this
75
+ reason.
76
+
77
+ **A forbid and the permits it governs ship together.** Separated, the forbid
78
+ gets deleted during a refactor and the permits keep working, wider than anyone
79
+ intended. `DenyByDefaultSet({ policies, when })` returns both from one call.
80
+
81
+ **Sensitive resources get an explicit floor, not just an absent permit.**
82
+ "Nobody wrote a permit for it" and "a forbid says no" look identical at
83
+ evaluation time and completely different in review. `cedar:coverage`'s
84
+ `forbidOnly` list is how you find declarations that have a floor; its
85
+ `uncovered` list is how you find the ones relying on absence.
86
+
87
+ ## Cross-domain checks
88
+
89
+ This is the thing no Cedar tool can express, because Cedar only ever sees
90
+ policies. In a chant build, policies and the infrastructure they govern are in
91
+ one entity graph, so a post-synth check can span both:
92
+
93
+ - an entity id in a policy references an actual resource declaration
94
+ - every declared bucket is covered by at least one `forbid`
95
+ - no schema action lacks any policy
96
+
97
+ These are ordinary post-synth checks. They are the differentiator, and they only
98
+ work because the policy set and the estate are built together.
99
+
100
+ ## Severity, by environment
101
+
102
+ | Finding | dev | staging | prod |
103
+ |---|---|---|---|
104
+ | Bare permit | warn | warn | fail |
105
+ | Entity type or action absent from schema | fail | fail | fail |
106
+ | Unguarded forbid | fail | fail | fail |
107
+ | Policy with an empty request envelope (inert) | warn | warn | fail |
108
+ | Schema action no policy covers | info | warn | warn |
109
+ | Entity type reachable only from a forbid | info | info | info |
110
+
111
+ The last row is deliberately never a failure. A resource nothing grants access
112
+ to is frequently correct.
113
+
114
+ ## Where these live
115
+
116
+ Post-synth checks and their catalog entries are **INTENTIUS/chant#1651**'s
117
+ scope. This skill describes the conventions the checks encode; if you are
118
+ writing a check, put it in `src/lint/post-synth/` with a `CED` id and an
119
+ `auditCatalog()` entry, not in a policy file.
@@ -0,0 +1,108 @@
1
+ // The schema `chant generate` falls back to when a project declares none.
2
+ //
3
+ // Cedar's codegen input is the project's own schema, not one global upstream
4
+ // (epic #1645), so there is no spec to fetch. That leaves `generate()` with
5
+ // nothing to do in a checkout that has not written a schema yet — which is
6
+ // every fresh clone, every `just check-lexicons` run, and this lexicon's own
7
+ // examples. This file is that fallback: a small, realistic application
8
+ // authorization model, generated from whenever `cedar.schema` is unset or
9
+ // points at a file that is not there.
10
+ //
11
+ // Point `cedar.schema` in chant.config.ts at your own `.cedarschema` and this
12
+ // file stops being read.
13
+
14
+ namespace App {
15
+ // Common types are resolved away by the codegen — they exist here because
16
+ // real schemas use them, and the resolver has to collapse them.
17
+ type Level = Long;
18
+ type TagSet = Set<String>;
19
+
20
+ entity Group = {
21
+ "name": String,
22
+ "description"?: String,
23
+ };
24
+
25
+ entity Team in [Group] = {
26
+ "name": String,
27
+ "costCenter"?: String,
28
+ };
29
+
30
+ entity User in [Group, Team] = {
31
+ "email": String,
32
+ "department": String,
33
+ "level": Level,
34
+ "manager"?: User,
35
+ "roles": TagSet,
36
+ "mfaEnabled": Bool,
37
+ };
38
+
39
+ entity ServiceAccount in [Group] = {
40
+ "name": String,
41
+ "owner": User,
42
+ "rotatedAt"?: String,
43
+ };
44
+
45
+ entity Folder = {
46
+ "name": String,
47
+ "owner": User,
48
+ "tags": TagSet,
49
+ };
50
+
51
+ entity Document in [Folder] = {
52
+ "title": String,
53
+ "owner": User,
54
+ "folder": Folder,
55
+ "public": Bool,
56
+ "tags": TagSet,
57
+ "classification": String,
58
+ };
59
+
60
+ entity Application = {
61
+ "name": String,
62
+ "owner": Team,
63
+ };
64
+
65
+ action read, list appliesTo {
66
+ principal: [User, ServiceAccount],
67
+ resource: [Document, Folder],
68
+ context: {
69
+ "mfa": Bool,
70
+ "sourceIp"?: String,
71
+ }
72
+ };
73
+
74
+ action write, create, comment appliesTo {
75
+ principal: [User],
76
+ resource: [Document, Folder],
77
+ context: {
78
+ "mfa": Bool,
79
+ "reason"?: String,
80
+ }
81
+ };
82
+
83
+ action delete, share appliesTo {
84
+ principal: [User],
85
+ resource: [Document],
86
+ context: {
87
+ "mfa": Bool,
88
+ "approvedBy"?: User,
89
+ }
90
+ };
91
+
92
+ action approve appliesTo {
93
+ principal: [User],
94
+ resource: [Document],
95
+ context: {
96
+ "mfa": Bool,
97
+ }
98
+ };
99
+
100
+ action admin appliesTo {
101
+ principal: [User],
102
+ resource: [Application],
103
+ context: {
104
+ "mfa": Bool,
105
+ "breakGlass"?: Bool,
106
+ }
107
+ };
108
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * "Fetch" the Cedar schema — which for this lexicon means reading a file.
3
+ *
4
+ * Every other lexicon downloads one global upstream spec. Cedar's interesting
5
+ * spec is the *project's* schema (epic #1645), so there is nothing to fetch,
6
+ * nothing to cache, and no network at all. What replaces it is a small
7
+ * resolution order:
8
+ *
9
+ * 1. `cedar.schema` from the project's `chant.config.ts`
10
+ * 2. `schema.cedarschema` in the project root
11
+ * 3. the schema bundled at `./default-schema.cedarschema`
12
+ *
13
+ * Step 3 is the one that needs defending. Without it `generate()` has nothing
14
+ * to read in a checkout that has not written a schema yet — which is every
15
+ * fresh clone, `just check-lexicons`, and this lexicon's own examples — and a
16
+ * lexicon whose generate step only works after the user does something is a
17
+ * lexicon whose generate step is never gated. The default is a real
18
+ * application-authorization schema, not a stub, so the surface it produces is
19
+ * worth exercising.
20
+ *
21
+ * `requireProjectSchema` turns step 3 off for projects that have moved past it.
22
+ */
23
+
24
+ import { existsSync, readFileSync } from "fs";
25
+ import { dirname, isAbsolute, join, resolve } from "path";
26
+ import { fileURLToPath } from "url";
27
+ import { CEDAR_DEFAULT_SCHEMA_PATH } from "../config";
28
+
29
+ /** Filename the bundled fallback schema ships under. */
30
+ export const DEFAULT_SCHEMA_FILENAME = "default-schema.cedarschema";
31
+
32
+ /** Map key used for the single schema entry, so `parseSchema` can tell them apart. */
33
+ export const DEFAULT_SCHEMA_KEY = "<bundled-default>";
34
+
35
+ const specDir = dirname(fileURLToPath(import.meta.url));
36
+
37
+ /** Absolute path to the schema bundled with this package. */
38
+ export function defaultSchemaPath(): string {
39
+ return join(specDir, DEFAULT_SCHEMA_FILENAME);
40
+ }
41
+
42
+ export interface SchemaSource {
43
+ /** Absolute path the text came from. */
44
+ path: string;
45
+ /** The `.cedarschema` text. */
46
+ text: string;
47
+ /** True when the bundled fallback was used rather than a project schema. */
48
+ isDefault: boolean;
49
+ }
50
+
51
+ export interface ResolveSchemaOptions {
52
+ /** Project root to resolve a relative `cedar.schema` against. Defaults to `process.cwd()`. */
53
+ projectRoot?: string;
54
+ /** Already-loaded `cedar` config namespace, if the caller has one. */
55
+ config?: { schema?: string; validation?: { requireProjectSchema?: boolean } };
56
+ }
57
+
58
+ /**
59
+ * Resolve which schema `generate()` should read, without loading it.
60
+ *
61
+ * Separated from {@link fetchSchemas} so tests and `coverage()` can ask the
62
+ * question without reading the file.
63
+ */
64
+ export function resolveSchemaPath(options: ResolveSchemaOptions = {}): { path: string; isDefault: boolean } {
65
+ const root = options.projectRoot ?? process.cwd();
66
+ const configured = options.config?.schema;
67
+
68
+ const candidates = configured
69
+ ? [isAbsolute(configured) ? configured : resolve(root, configured)]
70
+ : [resolve(root, CEDAR_DEFAULT_SCHEMA_PATH)];
71
+
72
+ for (const candidate of candidates) {
73
+ if (existsSync(candidate)) return { path: candidate, isDefault: false };
74
+ }
75
+
76
+ if (options.config?.validation?.requireProjectSchema) {
77
+ throw new Error(
78
+ `cedar: no schema found at ${candidates.join(", ")}. ` +
79
+ "`cedar.validation.requireProjectSchema` is on, so the bundled default schema is not used.",
80
+ );
81
+ }
82
+
83
+ return { path: defaultSchemaPath(), isDefault: true };
84
+ }
85
+
86
+ /** Read the resolved schema. */
87
+ export function readSchemaSource(options: ResolveSchemaOptions = {}): SchemaSource {
88
+ const { path, isDefault } = resolveSchemaPath(options);
89
+ return { path, text: readFileSync(path, "utf-8"), isDefault };
90
+ }
91
+
92
+ /**
93
+ * The `fetchSchemas` callback for `generatePipeline`.
94
+ *
95
+ * Keyed by `<bundled-default>` or by the project schema's absolute path, which
96
+ * is how `parseSchema` knows whether the content pin applies.
97
+ */
98
+ export async function fetchSchemas(options?: {
99
+ force?: boolean;
100
+ projectRoot?: string;
101
+ config?: ResolveSchemaOptions["config"];
102
+ }): Promise<Map<string, Buffer>> {
103
+ const source = readSchemaSource({ projectRoot: options?.projectRoot, config: options?.config });
104
+ return new Map([
105
+ [source.isDefault ? DEFAULT_SCHEMA_KEY : source.path, Buffer.from(source.text, "utf-8")],
106
+ ]);
107
+ }
@@ -0,0 +1,123 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseCedarSchema, resolveTypeRef, type CedarEntityDecl, type CedarActionDecl } from "./parse";
3
+ import { resolveSchema } from "./wasm";
4
+
5
+ const SCHEMA = `
6
+ namespace Chant {
7
+ type Level = Long;
8
+ type Tags = Set<String>;
9
+
10
+ entity Group;
11
+
12
+ entity User in [Group] = {
13
+ "department": String,
14
+ "level": Level,
15
+ "manager"?: User,
16
+ "tags": Tags,
17
+ "active": Bool,
18
+ };
19
+
20
+ entity Env enum ["prod", "dev"];
21
+
22
+ action read appliesTo {
23
+ principal: [User],
24
+ resource: [Group],
25
+ context: { "mfa": Bool, "ip"?: ipaddr }
26
+ };
27
+ }
28
+ `;
29
+
30
+ function entity(name: string): CedarEntityDecl {
31
+ const decl = parseCedarSchema(SCHEMA).decls.find((d) => d.typeName === name);
32
+ if (!decl || decl.kind !== "entity") throw new Error(`no entity ${name}`);
33
+ return decl;
34
+ }
35
+
36
+ describe("parseCedarSchema", () => {
37
+ it("produces one declaration per entity type and action", () => {
38
+ const names = parseCedarSchema(SCHEMA).decls.map((d) => d.typeName);
39
+ // Entity types first, then actions, each group sorted — the order the
40
+ // resolved JSON already hands back, which is what keeps codegen stable.
41
+ expect(names).toEqual([
42
+ "Chant::Env",
43
+ "Chant::Group",
44
+ "Chant::User",
45
+ 'Chant::Action::"read"',
46
+ ]);
47
+ });
48
+
49
+ it("resolves common types away", () => {
50
+ const attrs = Object.fromEntries(entity("Chant::User").attributes.map((a) => [a.name, a.type]));
51
+ // `level` is declared as the common type `Level`; nothing downstream should
52
+ // ever see the alias.
53
+ expect(attrs.level).toEqual({ kind: "primitive", cedar: "Long", ts: "number" });
54
+ expect(attrs.tags).toEqual({
55
+ kind: "set",
56
+ element: { kind: "primitive", cedar: "String", ts: "string" },
57
+ });
58
+ });
59
+
60
+ it("keeps entity references as references, not strings", () => {
61
+ const manager = entity("Chant::User").attributes.find((a) => a.name === "manager");
62
+ expect(manager?.type).toEqual({ kind: "entity", entityType: "Chant::User" });
63
+ });
64
+
65
+ it("treats a missing `required` as required, the opposite of JSON Schema", () => {
66
+ const attrs = entity("Chant::User").attributes;
67
+ expect(attrs.find((a) => a.name === "department")?.required).toBe(true);
68
+ expect(attrs.find((a) => a.name === "manager")?.required).toBe(false);
69
+ });
70
+
71
+ it("carries enum entity values through", () => {
72
+ expect(entity("Chant::Env").enumValues).toEqual(["prod", "dev"]);
73
+ });
74
+
75
+ it("reads an action's appliesTo and context", () => {
76
+ const decl = parseCedarSchema(SCHEMA).decls.find((d) => d.kind === "action") as CedarActionDecl;
77
+ expect(decl.actionId).toBe("read");
78
+ expect(decl.principalTypes).toEqual(["Chant::User"]);
79
+ expect(decl.resourceTypes).toEqual(["Chant::Group"]);
80
+ expect(decl.context.map((a) => a.name)).toEqual(["ip", "mfa"]);
81
+ expect(decl.context.find((a) => a.name === "ip")?.type).toEqual({ kind: "extension", name: "ipaddr" });
82
+ });
83
+
84
+ it("handles the empty namespace", () => {
85
+ const decls = parseCedarSchema(`entity Thing = { "n": Long };`).decls;
86
+ expect(decls.map((d) => d.typeName)).toEqual(["Thing"]);
87
+ });
88
+
89
+ it("reports a malformed schema with Cedar's own message", () => {
90
+ expect(() => parseCedarSchema("entity User = {")).toThrow(/schema did not resolve/);
91
+ });
92
+
93
+ it("does not treat a parseable empty schema as a schema", () => {
94
+ // `checkParseSchema("")` succeeds — an empty schema is legal Cedar (#1648
95
+ // §1) — so "did it parse" can never stand in for "is there a schema".
96
+ expect(parseCedarSchema("").decls).toEqual([]);
97
+ });
98
+ });
99
+
100
+ describe("resolveTypeRef", () => {
101
+ it("survives a self-referential common type instead of spinning", () => {
102
+ const commonTypes = { Loop: { type: "Chant::Loop" } };
103
+ expect(resolveTypeRef({ type: "Chant::Loop" }, commonTypes, "Chant")).toEqual({
104
+ kind: "opaque",
105
+ name: "Chant::Loop",
106
+ });
107
+ });
108
+ });
109
+
110
+ describe("resolveSchema", () => {
111
+ it("refuses a non-string rather than trapping the wasm", () => {
112
+ // A JS object here is `memory access out of bounds`, not a failure answer
113
+ // (#1648 §5.4) — the guard is what keeps that from reaching the caller.
114
+ const result = resolveSchema({ Chant: {} });
115
+ expect(result.ok).toBe(false);
116
+ expect(result.ok === false && result.error).toMatch(/as a string/);
117
+ });
118
+
119
+ it("leaves the wasm usable after a rejected call", () => {
120
+ resolveSchema(123);
121
+ expect(resolveSchema(SCHEMA).ok).toBe(true);
122
+ });
123
+ });