@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.
- package/README.md +190 -0
- package/dist/avp/ambient.d.ts +54 -0
- package/dist/avp/ambient.d.ts.map +1 -0
- package/dist/avp/client.d.ts +127 -0
- package/dist/avp/client.d.ts.map +1 -0
- package/dist/avp/describe-resources.d.ts +42 -0
- package/dist/avp/describe-resources.d.ts.map +1 -0
- package/dist/avp/embed.d.ts +120 -0
- package/dist/avp/embed.d.ts.map +1 -0
- package/dist/avp/live-export.d.ts +94 -0
- package/dist/avp/live-export.d.ts.map +1 -0
- package/dist/avp/ownership.d.ts +97 -0
- package/dist/avp/ownership.d.ts.map +1 -0
- package/dist/avp/statement.d.ts +22 -0
- package/dist/avp/statement.d.ts.map +1 -0
- package/dist/avp/store.d.ts +82 -0
- package/dist/avp/store.d.ts.map +1 -0
- package/dist/avp/testdata/mock-transport.d.ts +55 -0
- package/dist/avp/testdata/mock-transport.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +26 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/emit.d.ts +85 -0
- package/dist/codegen/emit.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +37 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +48 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +10 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/deny-by-default-set.d.ts +58 -0
- package/dist/composites/deny-by-default-set.d.ts.map +1 -0
- package/dist/composites/index.d.ts +12 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/owner-can-manage.d.ts +48 -0
- package/dist/composites/owner-can-manage.d.ts.map +1 -0
- package/dist/config.d.ts +102 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/coverage.d.ts +59 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/detect.d.ts +18 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/generated/index.d.ts +251 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +2 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/import/adapter.d.ts +21 -0
- package/dist/import/adapter.d.ts.map +1 -0
- package/dist/import/clause-text.d.ts +38 -0
- package/dist/import/clause-text.d.ts.map +1 -0
- package/dist/import/generator.d.ts +53 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/parser.d.ts +57 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +32 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +22 -0
- package/dist/lint/audit-catalog.d.ts +29 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/cedar-helpers.d.ts +97 -0
- package/dist/lint/post-synth/cedar-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc010.d.ts +17 -0
- package/dist/lint/post-synth/cedc010.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc011.d.ts +17 -0
- package/dist/lint/post-synth/cedc011.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc012.d.ts +21 -0
- package/dist/lint/post-synth/cedc012.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc013.d.ts +20 -0
- package/dist/lint/post-synth/cedc013.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc014.d.ts +19 -0
- package/dist/lint/post-synth/cedc014.d.ts.map +1 -0
- package/dist/lint/post-synth/cede010.d.ts +34 -0
- package/dist/lint/post-synth/cede010.d.ts.map +1 -0
- package/dist/lint/post-synth/cede011.d.ts +22 -0
- package/dist/lint/post-synth/cede011.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds010.d.ts +24 -0
- package/dist/lint/post-synth/ceds010.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds011.d.ts +19 -0
- package/dist/lint/post-synth/ceds011.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds012.d.ts +17 -0
- package/dist/lint/post-synth/ceds012.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/wasm-helpers.d.ts +108 -0
- package/dist/lint/post-synth/wasm-helpers.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +10 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/policy-shape.d.ts +31 -0
- package/dist/lint/rules/policy-shape.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +24 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +11 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/lsp/registry.d.ts +45 -0
- package/dist/lsp/registry.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/mcp/index.d.ts +34 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/policy-coverage.d.ts +81 -0
- package/dist/mcp/policy-coverage.d.ts.map +1 -0
- package/dist/meta.json +699 -0
- package/dist/okf/index.md +37 -0
- package/dist/okf/rules/CEDC001.md +11 -0
- package/dist/okf/rules/CEDC010.md +11 -0
- package/dist/okf/rules/CEDC011.md +15 -0
- package/dist/okf/rules/CEDC012.md +11 -0
- package/dist/okf/rules/CEDC013.md +15 -0
- package/dist/okf/rules/CEDC014.md +15 -0
- package/dist/okf/rules/CEDE010.md +15 -0
- package/dist/okf/rules/CEDE011.md +15 -0
- package/dist/okf/rules/CEDS010.md +15 -0
- package/dist/okf/rules/CEDS011.md +11 -0
- package/dist/okf/rules/CEDS012.md +15 -0
- package/dist/okf/types/AdminAction.md +14 -0
- package/dist/okf/types/Application.md +14 -0
- package/dist/okf/types/ApproveAction.md +13 -0
- package/dist/okf/types/CommentAction.md +14 -0
- package/dist/okf/types/CreateAction.md +14 -0
- package/dist/okf/types/DeleteAction.md +14 -0
- package/dist/okf/types/Document.md +18 -0
- package/dist/okf/types/Folder.md +15 -0
- package/dist/okf/types/Group.md +14 -0
- package/dist/okf/types/ListAction.md +14 -0
- package/dist/okf/types/Policy.md +29 -0
- package/dist/okf/types/ReadAction.md +14 -0
- package/dist/okf/types/ServiceAccount.md +15 -0
- package/dist/okf/types/ShareAction.md +14 -0
- package/dist/okf/types/Team.md +14 -0
- package/dist/okf/types/User.md +18 -0
- package/dist/okf/types/WriteAction.md +14 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/rules/cedar-helpers.ts +209 -0
- package/dist/rules/cedc010.ts +80 -0
- package/dist/rules/cedc011.ts +47 -0
- package/dist/rules/cedc012.ts +64 -0
- package/dist/rules/cedc013.ts +68 -0
- package/dist/rules/cedc014.ts +73 -0
- package/dist/rules/cede010.ts +89 -0
- package/dist/rules/cede011.ts +58 -0
- package/dist/rules/ceds010.ts +57 -0
- package/dist/rules/ceds011.ts +42 -0
- package/dist/rules/ceds012.ts +50 -0
- package/dist/rules/policy-shape.ts +148 -0
- package/dist/rules/wasm-helpers.ts +315 -0
- package/dist/serializer.d.ts +138 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/spec/fetch.d.ts +71 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +113 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/pin.d.ts +116 -0
- package/dist/spec/pin.d.ts.map +1 -0
- package/dist/spec/pinned-names.json +18 -0
- package/dist/spec/wasm.d.ts +143 -0
- package/dist/spec/wasm.d.ts.map +1 -0
- package/dist/types/index.d.ts +219 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +25 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +75 -0
- package/src/avp/OWNERSHIP.md +125 -0
- package/src/avp/ambient.test.ts +113 -0
- package/src/avp/ambient.ts +124 -0
- package/src/avp/client.ts +310 -0
- package/src/avp/describe-resources.test.ts +316 -0
- package/src/avp/describe-resources.ts +215 -0
- package/src/avp/embed.test.ts +114 -0
- package/src/avp/embed.ts +190 -0
- package/src/avp/live-export.test.ts +232 -0
- package/src/avp/live-export.ts +185 -0
- package/src/avp/ownership.test.ts +101 -0
- package/src/avp/ownership.ts +170 -0
- package/src/avp/statement.ts +50 -0
- package/src/avp/store.ts +152 -0
- package/src/avp/testdata/mock-transport.ts +147 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +873 -0
- package/src/codegen/emit.ts +496 -0
- package/src/codegen/generate-cli.ts +18 -0
- package/src/codegen/generate.test.ts +128 -0
- package/src/codegen/generate.ts +123 -0
- package/src/codegen/naming.ts +101 -0
- package/src/codegen/package.ts +52 -0
- package/src/composites/composites.test.ts +206 -0
- package/src/composites/deny-by-default-set.ts +98 -0
- package/src/composites/index.ts +13 -0
- package/src/composites/owner-can-manage.ts +80 -0
- package/src/config-namespace.test.ts +44 -0
- package/src/config.test.ts +82 -0
- package/src/config.ts +119 -0
- package/src/coverage.test.ts +61 -0
- package/src/coverage.ts +166 -0
- package/src/detect.test.ts +64 -0
- package/src/detect.ts +59 -0
- package/src/generated/index.d.ts +219 -0
- package/src/generated/index.ts +279 -0
- package/src/generated/lexicon-cedar.json +699 -0
- package/src/generated/runtime.ts +2 -0
- package/src/import/adapter.ts +63 -0
- package/src/import/clause-text.ts +178 -0
- package/src/import/generator.test.ts +133 -0
- package/src/import/generator.ts +219 -0
- package/src/import/parser.test.ts +265 -0
- package/src/import/parser.ts +352 -0
- package/src/import/roundtrip.test.ts +127 -0
- package/src/import/testdata/full.cedar +36 -0
- package/src/import/testdata/full.cedar.json +238 -0
- package/src/import/testdata/realistic.cedar +35 -0
- package/src/import/testdata/simple.cedar +6 -0
- package/src/index.ts +94 -0
- package/src/init-templates.test.ts +158 -0
- package/src/init-templates.ts +358 -0
- package/src/lint/audit-catalog.ts +130 -0
- package/src/lint/post-synth/cedar-helpers.ts +209 -0
- package/src/lint/post-synth/cedc010.ts +80 -0
- package/src/lint/post-synth/cedc011.ts +47 -0
- package/src/lint/post-synth/cedc012.ts +64 -0
- package/src/lint/post-synth/cedc013.ts +68 -0
- package/src/lint/post-synth/cedc014.ts +73 -0
- package/src/lint/post-synth/cede010.ts +89 -0
- package/src/lint/post-synth/cede011.ts +58 -0
- package/src/lint/post-synth/ceds010.ts +57 -0
- package/src/lint/post-synth/ceds011.ts +42 -0
- package/src/lint/post-synth/ceds012.ts +50 -0
- package/src/lint/post-synth/index.ts +25 -0
- package/src/lint/post-synth/post-synth.test.ts +474 -0
- package/src/lint/post-synth/wasm-helpers.ts +315 -0
- package/src/lint/rules/index.ts +12 -0
- package/src/lint/rules/policy-shape.test.ts +90 -0
- package/src/lint/rules/policy-shape.ts +148 -0
- package/src/lsp/completions.test.ts +111 -0
- package/src/lsp/completions.ts +55 -0
- package/src/lsp/hover.test.ts +82 -0
- package/src/lsp/hover.ts +75 -0
- package/src/lsp/registry.ts +74 -0
- package/src/mcp/index.ts +103 -0
- package/src/mcp/policy-coverage.test.ts +176 -0
- package/src/mcp/policy-coverage.ts +205 -0
- package/src/package-cli.ts +22 -0
- package/src/plugin.test.ts +115 -0
- package/src/plugin.ts +312 -0
- package/src/serializer.test.ts +502 -0
- package/src/serializer.ts +335 -0
- package/src/skills/chant-cedar-authoring.md +180 -0
- package/src/skills/chant-cedar-avp-embedding.md +125 -0
- package/src/skills/chant-cedar-meta-policy.md +119 -0
- package/src/spec/default-schema.cedarschema +108 -0
- package/src/spec/fetch.ts +107 -0
- package/src/spec/parse.test.ts +123 -0
- package/src/spec/parse.ts +253 -0
- package/src/spec/pin.test.ts +88 -0
- package/src/spec/pin.ts +243 -0
- package/src/spec/pinned-names.json +18 -0
- package/src/spec/wasm.ts +283 -0
- package/src/validate-cli.ts +8 -0
- 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
|
+
});
|