@intentius/chant-lexicon-cedar 0.124.31 → 0.124.33

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 (101) hide show
  1. package/dist/agentcore/embed.js +250 -0
  2. package/dist/agentcore/enforcement.js +81 -0
  3. package/dist/agentcore/scan.js +93 -0
  4. package/dist/avp/ambient.js +100 -0
  5. package/dist/avp/client.js +318 -0
  6. package/dist/avp/describe-resources.js +144 -0
  7. package/dist/avp/embed.js +125 -0
  8. package/dist/avp/live-export.js +151 -0
  9. package/dist/avp/ownership.js +144 -0
  10. package/dist/avp/statement.js +45 -0
  11. package/dist/avp/store.js +105 -0
  12. package/dist/avp/testdata/mock-transport.js +100 -0
  13. package/dist/codegen/docs-cli.js +6 -0
  14. package/dist/codegen/docs.js +133 -0
  15. package/dist/codegen/emit.js +331 -0
  16. package/dist/codegen/generate-cli.js +18 -0
  17. package/dist/codegen/generate.js +122 -0
  18. package/dist/codegen/naming.js +90 -0
  19. package/dist/codegen/package.js +33 -0
  20. package/dist/commands.js +86 -0
  21. package/dist/composites/deny-by-default-set.js +51 -0
  22. package/dist/composites/index.js +9 -0
  23. package/dist/composites/owner-can-manage.js +48 -0
  24. package/dist/config.js +159 -0
  25. package/dist/coverage.js +112 -0
  26. package/dist/detect.js +61 -0
  27. package/dist/dogwood/cli.js +477 -0
  28. package/dist/dogwood/event-schema.js +219 -0
  29. package/dist/dogwood/index.js +32 -0
  30. package/dist/dogwood/macros.js +175 -0
  31. package/dist/dogwood/policy.js +58 -0
  32. package/dist/dogwood/replay-activity.js +313 -0
  33. package/dist/dogwood/replay-op.js +134 -0
  34. package/dist/dogwood/scan.js +234 -0
  35. package/dist/dogwood/serialize.js +199 -0
  36. package/dist/dogwood/temporal.js +354 -0
  37. package/dist/dogwood/trace.js +269 -0
  38. package/dist/dogwood/upstream.js +40 -0
  39. package/dist/dogwood/window.js +82 -0
  40. package/dist/gate-policy-pack.js +189 -0
  41. package/dist/gate-policy.js +168 -0
  42. package/dist/gate-schema.js +122 -0
  43. package/dist/generated/index.js +94 -0
  44. package/dist/generated/runtime.js +2 -0
  45. package/dist/import/adapter.js +54 -0
  46. package/dist/import/clause-text.js +166 -0
  47. package/dist/import/generator.js +178 -0
  48. package/dist/import/parser.js +273 -0
  49. package/dist/index.js +79 -0
  50. package/dist/init-templates.js +330 -0
  51. package/dist/integrity.json +2 -2
  52. package/dist/lint/audit-catalog.js +65 -0
  53. package/dist/lint/audit-lineage.js +23 -0
  54. package/dist/lint/post-synth/cedar-helpers.js +123 -0
  55. package/dist/lint/post-synth/cedc010.js +59 -0
  56. package/dist/lint/post-synth/cedc011.js +29 -0
  57. package/dist/lint/post-synth/cedc012.js +34 -0
  58. package/dist/lint/post-synth/cedc013.js +45 -0
  59. package/dist/lint/post-synth/cedc014.js +54 -0
  60. package/dist/lint/post-synth/cede010.js +51 -0
  61. package/dist/lint/post-synth/cede011.js +36 -0
  62. package/dist/lint/post-synth/ceds010.js +31 -0
  63. package/dist/lint/post-synth/ceds011.js +22 -0
  64. package/dist/lint/post-synth/ceds012.js +31 -0
  65. package/dist/lint/post-synth/dogwood-helpers.js +94 -0
  66. package/dist/lint/post-synth/dwdc010.js +38 -0
  67. package/dist/lint/post-synth/dwdc011.js +34 -0
  68. package/dist/lint/post-synth/dwdc012.js +24 -0
  69. package/dist/lint/post-synth/dwdc013.js +29 -0
  70. package/dist/lint/post-synth/dwde010.js +84 -0
  71. package/dist/lint/post-synth/dwde011.js +67 -0
  72. package/dist/lint/post-synth/dwds010.js +21 -0
  73. package/dist/lint/post-synth/index.js +36 -0
  74. package/dist/lint/post-synth/wasm-helpers.js +234 -0
  75. package/dist/lint/rules/index.js +9 -0
  76. package/dist/lint/rules/policy-shape.js +114 -0
  77. package/dist/lsp/completions.js +52 -0
  78. package/dist/lsp/hover.js +56 -0
  79. package/dist/lsp/registry.d.ts.map +1 -1
  80. package/dist/lsp/registry.js +51 -0
  81. package/dist/manifest.json +1 -1
  82. package/dist/mcp/index.js +82 -0
  83. package/dist/mcp/policy-coverage.js +134 -0
  84. package/dist/op/activities/index.js +16 -0
  85. package/dist/package-cli.js +18 -0
  86. package/dist/plugin.js +304 -0
  87. package/dist/policy-text.js +117 -0
  88. package/dist/schema-artifact.js +66 -0
  89. package/dist/serializer.js +203 -0
  90. package/dist/spec/fetch.js +96 -0
  91. package/dist/spec/parse.js +164 -0
  92. package/dist/spec/pin.js +170 -0
  93. package/dist/spec/wasm.js +194 -0
  94. package/dist/validate-cli.js +7 -0
  95. package/dist/validate.js +74 -0
  96. package/package.json +7 -7
  97. package/src/codegen/package.ts +2 -2
  98. package/src/coverage.ts +2 -2
  99. package/src/import/generator.ts +2 -2
  100. package/src/lsp/registry.ts +2 -1
  101. package/src/spec/fetch.ts +2 -2
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ // Plugin
2
+ export { cedarPlugin } from "./plugin.js";
3
+ // Serializer
4
+ export { cedarSerializer, policyIdFromLogicalName, resolvePolicyId, cedarPolicyRecords } from "./serializer.js";
5
+ export { CEDAR_POLICY_TYPE, CEDAR_JSON_FILENAME } from "./serializer.js";
6
+ // Gate approval policy (chant#2508) — a cedar policy set as a gate's `approval.policy`.
7
+ export { gatePolicy, evaluateGatePolicy, GATE_HUMAN_TYPE, GATE_AGENT_TYPE, GATE_ROLE_TYPE, GATE_RESOURCE_TYPE, PASS_GATE_ACTION, } from "./gate-policy.js";
8
+ // Gate policy over the plan (chant#3182): the request's Cedar schema, with the
9
+ // plan summary core puts in `context.plan`, and a starter policy pack over it.
10
+ export { GATE_CEDAR_SCHEMA, GATE_PLAN_SUMMARY_CEDAR_TYPES, gateCedarSchema, validateGatePolicy, } from "./gate-schema.js";
11
+ export { STATEFUL_TYPES, allowRegions, capDeletes, capReplacements, permitLowRiskAgents, permitTagOnlyAgents, protectStateful, requireCreateTags, starterGatePolicies, } from "./gate-policy-pack.js";
12
+ // AVP embedding (#1652) — the typed statement an `AWS::VerifiedPermissions::Policy`
13
+ // carries, without a dependency on the aws lexicon. See src/avp/embed.ts.
14
+ export { avpStatement, avpStatementJSON, avpPolicyDefinition, avpPolicyResource, avpPolicySet, } from "./avp/embed.js";
15
+ // The AVP ownership channel (#1652, chant #1348). Design record: src/avp/OWNERSHIP.md.
16
+ export { AVP_OWNERSHIP_KEYS, AVP_POLICY_ID_KEY, AVP_DESCRIPTION_MAX, encodeOwnershipDescription, decodeOwnershipDescription, ownershipFromDescription, ownershipFromStoreTags, descriptionIsOwned, storeOwnershipTags, } from "./avp/ownership.js";
17
+ // AVP lifecycle readers (#1652).
18
+ export { describeAvpResources } from "./avp/describe-resources.js";
19
+ export { observeAvpAmbient, AVP_AMBIENT_KINDS } from "./avp/ambient.js";
20
+ export { exportAvpResources, policyToResourceIR } from "./avp/live-export.js";
21
+ export { resolvePolicyStoreId, AVP_POLICY_STORE_ENV } from "./avp/store.js";
22
+ export { policySetJSON, escapeCedarString } from "./serializer.js";
23
+ // Import / reconcile (#1653) — `.cedar` text and the JSON policy-set envelope
24
+ // in, authoring TypeScript out.
25
+ export { CedarParser, isPolicySetEnvelope } from "./import/parser.js";
26
+ export { CedarGenerator, loadActionConstants, sanitizeName, CEDAR_PACKAGE } from "./import/generator.js";
27
+ export { CedarTemplateParser, CedarTemplateGenerator } from "./import/adapter.js";
28
+ export { detectTemplate } from "./detect.js";
29
+ // AgentCore embedding (#1660) — the typed statement an
30
+ // `AWS::BedrockAgentCore::Policy` carries on either arm of its Definition
31
+ // union, and the EnforcementMode dial that stages the rollout. Same
32
+ // no-dependency rule as the AVP seam above. See src/agentcore/embed.ts.
33
+ export { agentCoreStatement, agentCorePolicyDefinition, agentCorePolicyResource, agentCorePolicySet, agentCoreStagedPolicy, agentCorePolicyName, AGENTCORE_NAME_PATTERN, AGENTCORE_STATEMENT_MIN, AGENTCORE_STATEMENT_MAX, } from "./agentcore/embed.js";
34
+ export { AGENTCORE_ENFORCEMENT, describeStage, enforcementMode, enforcementStage, isEnforcing, } from "./agentcore/enforcement.js";
35
+ export { embeddedAgentCoreDefinitions, embeddedAgentCorePolicyStatements } from "./agentcore/scan.js";
36
+ // The dogwood temporal dialect (#1658) — typed temporal builders, the `.dw`
37
+ // serializer leg, `.dwschema` event schemas. Pre-release; see ./dogwood.
38
+ //
39
+ // Namespaced rather than flattened: the temporal sub-language's builders are
40
+ // named after its own grammar (`and`, `not`, `count`, `sum`, `str`, `field`,
41
+ // `record`, `window`), and spilling those into the package root would collide
42
+ // with a schema-generated entity class the moment someone declares one. The
43
+ // three entity classes are re-exported flat because that is what a policy file
44
+ // names.
45
+ export * as dogwood from "./dogwood/index.js";
46
+ export { DOGWOOD_EVENT_SCHEMA_FILENAME, DOGWOOD_EVENT_SCHEMA_TYPE, DOGWOOD_MACRO_FILENAME, DOGWOOD_MACRO_LIBRARY_TYPE, DOGWOOD_POLICY_FILENAME, DOGWOOD_POLICY_TYPE, TemporalEventSchema, TemporalMacroLibrary, TemporalPolicy, } from "./dogwood/policy.js";
47
+ export { DOGWOOD_UPSTREAM } from "./dogwood/upstream.js";
48
+ // The replay Op composite and its typed step builders (#1661). Flat, like
49
+ // fly's `flyDeploy`: an Op factory is what a project's `ops/*.op.ts` names,
50
+ // and it depends on no lexicon but this one — see ./dogwood/replay-op.ts for
51
+ // why the composite ships from cedar.
52
+ export { DEFAULT_REPLAY_REPORT_PATH, PolicyReplayOp, dogwoodReplayReportStep, dogwoodReplayStep, } from "./dogwood/replay-op.js";
53
+ // Lint rules
54
+ export { rules as cedarLintRules } from "./lint/rules/index.js";
55
+ // Packaging pipeline (for external tooling)
56
+ export { packageLexicon } from "./codegen/package.js";
57
+ // The `cedar` config namespace (#1344). Importing this package is what brings
58
+ // the key into ChantConfig, so a `chant.config.ts` that sets it compiles.
59
+ export { cedarConfigSchema, loadCedarConfig, loadCedarProject, CEDAR_DEFAULT_SCHEMA_PATH, CEDAR_DEFAULT_OUT_DIR } from "./config.js";
60
+ // Where `chant cedar generate` writes for a project (#1696).
61
+ export { resolveGeneratedDir } from "./codegen/generate.js";
62
+ // The project schema as a build artifact (#1697): authored by hand as
63
+ // `new Schema({ text })`, or contributed automatically from `cedar.schema`.
64
+ export { Schema, CEDAR_SCHEMA_TYPE, CEDAR_SCHEMA_FILENAME } from "./schema-artifact.js";
65
+ // Upstream pin — the cedar-wasm package version and the Cedar language
66
+ // version it implements (#1650).
67
+ export { CEDAR_WASM_VERSION, CEDAR_LANG_VERSION } from "./spec/pin.js";
68
+ // Schema coverage — is every schema declaration reachable from the generated
69
+ // artifacts? Policy coverage below asks the other half.
70
+ export { computeCedarCoverage, formatCedarCoverage, analyzeCedarCoverage } from "./coverage.js";
71
+ // Policy coverage — which schema declarations the built policy set can apply
72
+ // to. Backs the `cedar:coverage` MCP tool.
73
+ export { computePolicyCoverage, formatPolicyCoverage } from "./mcp/policy-coverage.js";
74
+ // Composites — the repeated policy shapes Cedar itself has nowhere to put.
75
+ export * from "./composites/index.js";
76
+ // `chant init --lexicon cedar --template <name>` scaffolds.
77
+ export { cedarInitTemplates, CEDAR_INIT_TEMPLATES } from "./init-templates.js";
78
+ // Generated entity types, actions and the Policy authoring class (#1650).
79
+ export * from "./generated/index.js";
@@ -0,0 +1,330 @@
1
+ /**
2
+ * `chant init --lexicon cedar --template <name>` scaffolds.
3
+ *
4
+ * Every template ships a `schema.cedarschema` at the project root. That is not
5
+ * decoration: cedar's codegen input is the project's own schema (epic #1645),
6
+ * and a scaffold without one generates against the bundled default — a project
7
+ * whose typed classes describe somebody else's entity model.
8
+ *
9
+ * The filename is fixed rather than configured because `schema.cedarschema` in
10
+ * the project root is step 2 of the resolution order in `spec/fetch.ts`, so a
11
+ * scaffolded project needs no `cedar.schema` key at all. It also has to be
12
+ * fixed: core writes `chant.config.ts` before a plugin's root files and
13
+ * `writeIfNotExists` keeps the first one, so a template that shipped its own
14
+ * config would be silently discarded. The README each template ships is where
15
+ * `requireProjectSchema` gets recommended, which is the honest place for advice
16
+ * a scaffold cannot enforce.
17
+ *
18
+ * The policy files use `entityType`-string scopes (`{ is: "App::Document" }`)
19
+ * and generated action constants, so a scaffolded project compiles the moment
20
+ * `chant cedar generate` has run against its own schema (#1696).
21
+ */
22
+ /** Filename every template writes its schema to — see spec/fetch.ts step 2. */
23
+ const SCHEMA_FILE = "schema.cedarschema";
24
+ function readme(title, body) {
25
+ return `# ${title}
26
+
27
+ \`\`\`bash
28
+ npm install
29
+ npx chant cedar generate # schema.cedarschema -> src/generated/cedar/
30
+ npx chant build # emits .cedar text, policies.cedar.json, and the schema
31
+ \`\`\`
32
+
33
+ Generate first. The classes and action constants \`src/policies.ts\` imports
34
+ from \`./generated/cedar\` do not exist until \`chant cedar generate\` has read
35
+ \`${SCHEMA_FILE}\`. The output is yours: it lives in this project, not in
36
+ \`node_modules\`, so commit it (or regenerate it in CI before \`chant build\`)
37
+ and re-run generate whenever the schema changes. \`cedar.outDir\` in
38
+ \`chant.config.ts\` moves it.
39
+
40
+ ${body}
41
+
42
+ ## Before this is real
43
+
44
+ Add \`requireProjectSchema\` once you are past the scaffold:
45
+
46
+ \`\`\`typescript
47
+ // chant.config.ts
48
+ export default {
49
+ lexicons: ["cedar"],
50
+ cedar: {
51
+ validation: { mode: "strict", requireProjectSchema: true },
52
+ },
53
+ } satisfies ChantConfig;
54
+ \`\`\`
55
+
56
+ Without it, deleting or renaming \`${SCHEMA_FILE}\` falls back to the schema
57
+ bundled with the lexicon and the build still succeeds — against an entity model
58
+ this project never declared.
59
+
60
+ ## What comes out
61
+
62
+ | File | Who reads it |
63
+ |------|--------------|
64
+ | \`dist/*.cedar\` | Every Cedar evaluator — AVP, cedar-agent, an embedded cedar-wasm |
65
+ | \`dist/policies.cedar.json\` | The Cedar JSON policy format; also the parse source for import |
66
+ | \`dist/${SCHEMA_FILE}\` | Your schema, beside the policies, so the build validates against it and a bundle can carry it |
67
+ `;
68
+ }
69
+ // ── default ───────────────────────────────────────────────────────
70
+ const DEFAULT_SCHEMA = `// The entity model your policies are typed against.
71
+ //
72
+ // \`chant cedar generate\` turns every declaration below into a
73
+ // TypeScript class or constant, so a renamed entity type becomes a
74
+ // compiler-guided refactor rather than a runtime validation failure.
75
+
76
+ namespace App {
77
+ entity User = {
78
+ "email": String,
79
+ "department": String,
80
+ };
81
+
82
+ entity Document = {
83
+ "title": String,
84
+ "owner": User,
85
+ "classification": String,
86
+ };
87
+
88
+ action read, write appliesTo {
89
+ principal: [User],
90
+ resource: [Document],
91
+ context: {
92
+ "mfa": Bool,
93
+ }
94
+ };
95
+ }
96
+ `;
97
+ const DEFAULT_POLICIES = `import { Policy, ReadAction, WriteAction } from "./generated/cedar";
98
+
99
+ /**
100
+ * A permit and a forbid — the smallest policy set worth deploying.
101
+ *
102
+ * Cedar is default-deny, so the permit is what grants anything at all. The
103
+ * forbid is not the absence of a grant: it beats every permit in the set
104
+ * unconditionally, which is what makes it survive a wider grant added later.
105
+ */
106
+
107
+ /** Owners read and write their own documents. */
108
+ export const ownerAccess = new Policy({
109
+ effect: "permit",
110
+ principal: { is: "App::User" },
111
+ action: { in: [ReadAction, WriteAction] },
112
+ resource: { is: "App::Document" },
113
+ when: ["resource.owner == principal"],
114
+ });
115
+
116
+ /** Nothing confidential moves without MFA, owner or not. */
117
+ export const requireMfaOnConfidential = new Policy({
118
+ effect: "forbid",
119
+ principal: { is: "App::User" },
120
+ resource: { is: "App::Document" },
121
+ when: ['resource.classification == "confidential"'],
122
+ unless: ["context.mfa == true"],
123
+ });
124
+ `;
125
+ // ── avp-embedding ─────────────────────────────────────────────────
126
+ const AVP_SCHEMA = `// The entity model behind an Amazon Verified Permissions policy store.
127
+ //
128
+ // AVP is one deployment vehicle for Cedar; the schema and the policies are
129
+ // ordinary Cedar either way, and the same emitted \`.cedar\` file is read by any
130
+ // other evaluator with no AWS involved.
131
+
132
+ namespace Store {
133
+ entity Tenant = {
134
+ "name": String,
135
+ };
136
+
137
+ entity User in [Tenant] = {
138
+ "email": String,
139
+ "tenant": Tenant,
140
+ "admin": Bool,
141
+ };
142
+
143
+ entity Record in [Tenant] = {
144
+ "tenant": Tenant,
145
+ "owner": User,
146
+ "sensitive": Bool,
147
+ };
148
+
149
+ action view, edit appliesTo {
150
+ principal: [User],
151
+ resource: [Record],
152
+ context: {
153
+ "mfa": Bool,
154
+ }
155
+ };
156
+ }
157
+ `;
158
+ const AVP_POLICIES = `import { Policy, EditAction, ViewAction } from "./generated/cedar";
159
+
160
+ /**
161
+ * Policies destined for an AVP policy store.
162
+ *
163
+ * The statement each of these serializes to is what goes in a
164
+ * \`AWS::VerifiedPermissions::Policy\`'s \`definition.static.statement\` — the
165
+ * same bytes as the emitted \`.cedar\` file, so the deployed policy and the
166
+ * reviewed file cannot disagree.
167
+ *
168
+ * The typed handoff (a VerifiedPermissionsPolicy whose statement accepts a
169
+ * Policy value directly, plus the policy-store lifecycle and the ownership
170
+ * channel) is landing with INTENTIUS/chant#1652. Until it does, emit the
171
+ * \`.cedar\` file and reference its text; do not hand-type a statement string.
172
+ */
173
+
174
+ /** Tenancy: nobody touches a record outside their own tenant. */
175
+ export const tenantIsolation = new Policy({
176
+ effect: "forbid",
177
+ principal: { is: "Store::User" },
178
+ resource: { is: "Store::Record" },
179
+ unless: ["resource.tenant == principal.tenant"],
180
+ });
181
+
182
+ /** Members view records in their tenant. */
183
+ export const tenantView = new Policy({
184
+ effect: "permit",
185
+ principal: { is: "Store::User" },
186
+ action: { eq: ViewAction },
187
+ resource: { is: "Store::Record" },
188
+ when: ["resource.tenant == principal.tenant"],
189
+ });
190
+
191
+ /** Owners and tenant admins edit; sensitive records also demand MFA. */
192
+ export const ownerEdit = new Policy({
193
+ effect: "permit",
194
+ principal: { is: "Store::User" },
195
+ action: { eq: EditAction },
196
+ resource: { is: "Store::Record" },
197
+ when: ["resource.owner == principal || principal.admin == true"],
198
+ unless: ["resource.sensitive == true && context.mfa == false"],
199
+ });
200
+ `;
201
+ // ── gateway-policy-set ────────────────────────────────────────────
202
+ const GATEWAY_SCHEMA = `// An API gateway's authorization model.
203
+ //
204
+ // The principal is a caller identity, the resource is a route, and the action
205
+ // is an HTTP method. Modelling routes as entities rather than strings is what
206
+ // lets a policy say \`resource.public == true\` instead of matching paths.
207
+
208
+ namespace Gateway {
209
+ entity Scope = {
210
+ "name": String,
211
+ };
212
+
213
+ entity Caller in [Scope] = {
214
+ "clientId": String,
215
+ "internal": Bool,
216
+ };
217
+
218
+ entity Route = {
219
+ "path": String,
220
+ "public": Bool,
221
+ "deprecated": Bool,
222
+ };
223
+
224
+ action get, post, delete appliesTo {
225
+ principal: [Caller],
226
+ resource: [Route],
227
+ context: {
228
+ "sourceIp": String,
229
+ "authenticated": Bool,
230
+ }
231
+ };
232
+ }
233
+ `;
234
+ const GATEWAY_POLICIES = `import { DenyByDefaultSet } from "@intentius/chant-lexicon-cedar";
235
+ import { DeleteAction, GetAction, Policy, PostAction } from "./generated/cedar";
236
+
237
+ /**
238
+ * A gateway policy set with an explicit deny floor.
239
+ *
240
+ * \`DenyByDefaultSet\` returns the forbid and the permits it governs from one
241
+ * call, so the floor cannot be deleted in a refactor while the grants survive.
242
+ * A forbid beats every permit unconditionally — that is the whole reason to
243
+ * write one under a default-deny engine.
244
+ */
245
+
246
+ /** Anonymous callers reach public routes, read-only. */
247
+ const publicRead = new Policy({
248
+ effect: "permit",
249
+ principal: { is: "Gateway::Caller" },
250
+ action: { eq: GetAction },
251
+ resource: { is: "Gateway::Route" },
252
+ when: ["resource.public == true"],
253
+ });
254
+
255
+ /** Authenticated callers may write. */
256
+ const authenticatedWrite = new Policy({
257
+ effect: "permit",
258
+ principal: { is: "Gateway::Caller" },
259
+ action: { in: [PostAction, DeleteAction] },
260
+ resource: { is: "Gateway::Route" },
261
+ when: ["context.authenticated == true"],
262
+ });
263
+
264
+ /** Deprecated routes are closed to everyone but internal callers. */
265
+ const guarded = DenyByDefaultSet({
266
+ policies: [publicRead, authenticatedWrite],
267
+ entityType: "Gateway::Route",
268
+ when: ["resource.deprecated == true"],
269
+ unless: ["principal.internal == true"],
270
+ annotations: { id: "deprecated-route-floor" },
271
+ });
272
+
273
+ export const deprecatedRouteFloor = guarded.floor;
274
+ export const publicReadGrant = guarded.members[0];
275
+ export const authenticatedWriteGrant = guarded.members[1];
276
+ `;
277
+ // ── The template set ──────────────────────────────────────────────
278
+ /**
279
+ * Scaffolds for `chant init`.
280
+ *
281
+ * An unrecognized name falls through to the default rather than throwing —
282
+ * the same behaviour every other lexicon's `initTemplates` has.
283
+ */
284
+ export function cedarInitTemplates(template) {
285
+ const scripts = {
286
+ generate: "chant cedar generate",
287
+ build: "chant build",
288
+ };
289
+ if (template === "avp-embedding") {
290
+ return {
291
+ src: { "policies.ts": AVP_POLICIES },
292
+ root: {
293
+ [SCHEMA_FILE]: AVP_SCHEMA,
294
+ "README.md": readme("Cedar policies for a Verified Permissions store", "The `Store` namespace models a multi-tenant record store. `tenantIsolation` is a\n" +
295
+ "`forbid`, deliberately: a forbid beats every permit in the set unconditionally,\n" +
296
+ "so a tenancy boundary written that way survives a wider grant added later.\n\n" +
297
+ "The statement each policy serializes to is what goes in an\n" +
298
+ "`AWS::VerifiedPermissions::Policy`. The typed handoff is landing with\n" +
299
+ "INTENTIUS/chant#1652 — until then, reference the emitted `.cedar` text rather\n" +
300
+ "than hand-typing a statement string."),
301
+ },
302
+ scripts,
303
+ };
304
+ }
305
+ if (template === "gateway-policy-set") {
306
+ return {
307
+ src: { "policies.ts": GATEWAY_POLICIES },
308
+ root: {
309
+ [SCHEMA_FILE]: GATEWAY_SCHEMA,
310
+ "README.md": readme("Cedar policies for an API gateway", "Routes are entities, not path strings, which is what lets a policy say\n" +
311
+ "`resource.public == true` instead of matching globs. The `DenyByDefaultSet`\n" +
312
+ "composite returns the deprecated-route floor and the grants it governs from one\n" +
313
+ "call, so a refactor cannot delete the floor and leave the grants behind."),
314
+ },
315
+ scripts,
316
+ };
317
+ }
318
+ return {
319
+ src: { "policies.ts": DEFAULT_POLICIES },
320
+ root: {
321
+ [SCHEMA_FILE]: DEFAULT_SCHEMA,
322
+ "README.md": readme("Cedar policies", "One permit and one forbid. Cedar is default-deny, so the permit is what grants\n" +
323
+ "anything at all; the forbid is not the absence of a grant but an override that\n" +
324
+ "beats every permit in the set."),
325
+ },
326
+ scripts,
327
+ };
328
+ }
329
+ /** Every template name this lexicon scaffolds, for docs and tests. */
330
+ export const CEDAR_INIT_TEMPLATES = ["default", "avp-embedding", "gateway-policy-set"];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "92062690a5a6c1ee64bfb4b22e508b34c15ce90e3ce13759bbd40d16407d3799",
4
+ "manifest.json": "e15b3f13896309b2742a1d8851e6478a85dc043443e82c4f4aff2baa76fed6ed",
5
5
  "meta.json": "b218779260169a193882aeee76e5ac559588f1682657a001fb0982cffb5843fc",
6
6
  "types/index.d.ts": "9e53a1cb3c9ee38f9de2f3c0db9cc28a0b67c3649be410c341e2451b9b0076cd",
7
7
  "rules/policy-shape.ts": "11377265f05ede594b77c2b6cc3d977a688e5e5712e7a706e7aa6fd6ea05d027",
@@ -30,5 +30,5 @@
30
30
  "skills/chant-cedar-meta-policy.md": "050b310a87196a48c827f1129698e4c6bbcd96dc963782e22bf1a640bbc20a1a",
31
31
  "skills/chant-cedar-dogwood.md": "7e52d3f045c1c34e00fdef76014a05483fb9dd58cc41037621a76bc298776d8f"
32
32
  },
33
- "composite": "427f8f620d89e894bc54d4f3ef51f7b6dbe789237ad8cd218de5f8f977804e85"
33
+ "composite": "a0c78e9e1937670abb1534998510a9ae61e684b9d2d6d2380bed9e88af49000e"
34
34
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The cedar lexicon's `chant audit` catalog — metadata for every CED check,
3
+ * contributed via `cedarPlugin.auditCatalog()` (#687, #1346).
4
+ *
5
+ * A check's own definition says what it looks for. It does not say how severe
6
+ * the finding is, whether the fix is mechanical, what backs the rule, or what
7
+ * to title it in a report. `resolveAuditCatalog` merges this map over core's
8
+ * static catalog to answer all four — and it skips a lexicon that omits the
9
+ * method *silently*, which is how fly's and temporal's checks reached the
10
+ * report with no metadata at all. A repo-level test fails on a gap in either
11
+ * direction, so this map and the shipped checks move together.
12
+ *
13
+ * Every entry here is `yamlBased: true`: each check reads `ctx.outputs` — the
14
+ * emitted policy set — and nothing reads `ctx.entities`, so all of them fire
15
+ * on an audit of a standalone `policies.cedar.json` exactly as they do on a
16
+ * `chant build`. `auditRule()` hardcodes that, so it is used throughout rather
17
+ * than constructing `RuleMeta` by hand the way fly and fountain must.
18
+ *
19
+ * `authority` is left off deliberately. The catalog test treats a citation as
20
+ * proof that a finding is `security`, and Cedar's own docs are a language
21
+ * reference rather than a benchmark like CIS or OSSF Scorecard; citing them
22
+ * would be dressing up chant's own opinion. The one place a citation would be
23
+ * honest — the bare-permit wall — is chant's convention, not upstream's, and
24
+ * the verification report is explicit that Cedar's validator says nothing
25
+ * about it.
26
+ */
27
+ import { auditRule, applyLineage } from "@intentius/chant/audit/catalog";
28
+ import { cedarAuditLineage } from "./audit-lineage.js";
29
+ export const cedarAuditCatalog = {
30
+ // ── Pre-synth lint rule ────────────────────────────────────────
31
+ //
32
+ // Not a post-synth check, and so not required by the coverage test — but a
33
+ // reader who hits CEDC001 wants the same metadata as one who hits CEDC010,
34
+ // which is the reason fountain catalogs FTN001.
35
+ CEDC001: auditRule("CEDC001", "merge-worthy", "guidance", "Cedar policy has an unusable effect or an empty guard", 'Use effect: "permit" or "forbid" — anything else is emitted as a permit — and remove empty when/unless entries.', { category: "correctness" }),
36
+ // ── Correctness ────────────────────────────────────────────────
37
+ CEDC010: auditRule("CEDC010", "merge-worthy", "guidance", "Cedar policy set does not parse", "Fix the policy set so Cedar's own parser accepts it — no evaluator can load it as it stands.", { category: "correctness" }),
38
+ CEDC011: auditRule("CEDC011", "merge-worthy", "guidance", "Cedar condition expression does not parse", "Correct the when/unless expression source named in the finding; the rest of the policy set cannot load until it parses.", { category: "correctness" }),
39
+ CEDC012: auditRule("CEDC012", "merge-worthy", "guidance", "Duplicate Cedar policy id", "Rename one of the colliding declarations, or give it an explicit and distinct @id annotation — the JSON policy set keeps only one of them.", { category: "correctness" }),
40
+ CEDC013: auditRule("CEDC013", "report-only", "guidance", "Cedar policy annotation is empty or disagrees with the policy id", "Make @id match the policy's id (or drop it and let chant derive one), and give every other annotation a real value.", { category: "best-practice" }),
41
+ CEDC014: auditRule("CEDC014", "merge-worthy", "guidance", "Cedar scope constraint names a bare type instead of an entity", 'Write the reference as a full UID — Namespace::Type::"id" — so it resolves to an entity rather than degrading to a type name with an empty id.', { category: "correctness" }),
42
+ // ── Evaluability (the validator) ───────────────────────────────
43
+ CEDE010: auditRule("CEDE010", "merge-worthy", "guidance", "Cedar policy fails validation against the schema", "Fix the entity type, action, or attribute the validator names — a policy the validator rejects can never grant anything. If the finding is the advisory, emit a .cedarschema beside the policies so validation can run at all.", { category: "correctness" }),
44
+ CEDE011: auditRule("CEDE011", "report-only", "guidance", "Cedar validator warns about a policy", "Read the validator's warning — an impossible policy evaluates to false for every valid request, so the access it was written to grant does not exist.", { category: "correctness" }),
45
+ // ── Security ───────────────────────────────────────────────────
46
+ CEDS010: auditRule("CEDS010", "merge-worthy", "guidance", "Bare permit(principal, action, resource) grants everything", "Constrain a scope or add a when/unless guard. Cedar's validator does not flag this policy; a production build fails on it.", { category: "security" }),
47
+ CEDS011: auditRule("CEDS011", "report-only", "guidance", "Cedar policy set has no forbid", "Add a forbid for whatever the set must never allow — a forbid beats every permit, so it is the only invariant a later grant cannot widen.", { category: "security" }),
48
+ CEDS012: auditRule("CEDS012", "report-only", "guidance", "Cedar permit leaves the action scope unconstrained", 'Name the actions the grant needs (action == Action::"read", or action in [ … ]) so it does not widen as the schema gains actions.', { category: "security" }),
49
+ // ── The dogwood dialect (DWD) ──────────────────────────────────
50
+ //
51
+ // A second id family under the same lexicon, declared on the serializer as
52
+ // `extraRulePrefixes` (#1349's mechanism, the way k8s owns ARGO and FLUX).
53
+ // Dogwood is a dialect of Cedar rather than a peer language — a `.dw` file
54
+ // stripped of Cedar semantics is meaningless — so its checks ship here, and
55
+ // its ids are their own family so a reader can tell which surface bit them.
56
+ DWDC010: auditRule("DWDC010", "merge-worthy", "guidance", "Dogwood temporal predicate names an undeclared event kind", "Declare the event kind in the emitted .dwschema, or correct the predicate to a kind the schema derives — upstream rejects the policy set otherwise.", { category: "correctness" }),
57
+ DWDC011: auditRule("DWDC011", "merge-worthy", "guidance", "Dogwood temporal window exceeds the event schema's max_window", "Shorten the window, or raise max_window in the event schema. With no schema emitted the cap is upstream's 24h default.", { category: "correctness" }),
58
+ DWDC012: auditRule("DWDC012", "merge-worthy", "guidance", "Dogwood formerly/previous/since is missing its window", "Give the operator a `within <n><s|m|h|d>` window — all three past-only operators require one. The typed builders take it as an argument.", { category: "correctness" }),
59
+ DWDC013: auditRule("DWDC013", "report-only", "guidance", "AgentCore policy embeds temporal text with no event schema emitted", "Declare a TemporalEventSchema so the .dwschema ships with the policy, or record where the service schema is registered — AWS::BedrockAgentCore::Policy has no property to carry it, and until the engine has it every temporal predicate in the statement matches nothing.", { category: "correctness" }),
60
+ DWDE010: auditRule("DWDE010", "merge-worthy", "guidance", "Dogwood policy set fails `dogwood validate`", "Fix what upstream's frontend names — a macro, a temporal type error, or a Cedar body the action schema rejects. If the finding is the advisory, install the dogwood binary (or set cedar.dogwood.binary) so full .dw validation can run at all; without it only the DWDC walls apply.", { category: "correctness" }),
61
+ DWDE011: auditRule("DWDE011", "merge-worthy", "guidance", "Cedar lowered from a dogwood policy set fails Cedar validation", "Fix the entity type, action, or attribute Cedar's validator names in the lowered body — the temporal clauses became context slots, so everything the finding points at is ordinary Cedar the policy wrote.", { category: "correctness" }),
62
+ DWDS010: auditRule("DWDS010", "report-only", "guidance", "Dogwood event schema pins nothing, widening every temporal predicate", "Pin a field to the deciding request (pin callerPrincipal: principalType(A) = principal), or record that cross-principal correlation is the intent — supplying any event schema opts out of upstream's default pin.", { category: "security" }),
63
+ };
64
+ // Prior art credits live beside the rules in ./audit-lineage.ts (see core audit/prior-art.ts).
65
+ applyLineage(cedarAuditCatalog, cedarAuditLineage);
@@ -0,0 +1,23 @@
1
+ export const cedarAuditLineage = {
2
+ CEDC001: [
3
+ { tool: "cedar-docs", rule: "effect", url: "https://docs.cedarpolicy.com/policies/json-format.html#effect", relation: "overlaps" },
4
+ ],
5
+ CEDC010: [
6
+ { tool: "cedar-cli", rule: "check-parse", url: "https://github.com/cedar-policy/cedar/tree/main/cedar-policy-cli", relation: "equivalent" },
7
+ ],
8
+ CEDC011: [
9
+ { tool: "cedar-cli", rule: "check-parse", url: "https://github.com/cedar-policy/cedar/tree/main/cedar-policy-cli", relation: "overlaps" },
10
+ ],
11
+ CEDC012: [
12
+ { tool: "cedar-policy-crate", rule: "PolicySetError::AlreadyDefined", url: "https://docs.rs/cedar-policy/latest/cedar_policy/enum.PolicySetError.html#variant.AlreadyDefined", relation: "overlaps" },
13
+ ],
14
+ CEDE010: [
15
+ { tool: "cedar-validator", rule: "supported-validation-checks (errors)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "equivalent" },
16
+ ],
17
+ CEDE011: [
18
+ { tool: "cedar-validator", rule: "supported-validation-checks (warnings)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "equivalent" },
19
+ ],
20
+ DWDE011: [
21
+ { tool: "cedar-validator", rule: "supported-validation-checks (errors)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "overlaps" },
22
+ ],
23
+ };
@@ -0,0 +1,123 @@
1
+ /** Files carrying a Cedar JSON policy set end with this. */
2
+ export const CEDAR_JSON_SUFFIX = ".cedar.json";
3
+ /** The lexicon name stamped on every diagnostic from this directory. */
4
+ export const CEDAR_LEXICON = "cedar";
5
+ // ── Narrowing ─────────────────────────────────────────────────────
6
+ export function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ /** `permit` / `forbid` / whatever else the document claims. */
10
+ export function effectOf(policy) {
11
+ return typeof policy.effect === "string" ? policy.effect : undefined;
12
+ }
13
+ /** The three scope positions, in Cedar's own order. */
14
+ export function scopesOf(policy) {
15
+ const out = [];
16
+ for (const variable of ["principal", "action", "resource"]) {
17
+ const scope = policy[variable];
18
+ if (isRecord(scope))
19
+ out.push({ variable, scope: scope });
20
+ }
21
+ return out;
22
+ }
23
+ /** True when a scope position is Cedar's unconstrained "any" (`op: "All"`). */
24
+ export function scopeIsAll(scope) {
25
+ return isRecord(scope) && scope.op === "All";
26
+ }
27
+ /** Every `when`/`unless` clause, or `[]` when the policy is unconditioned. */
28
+ export function conditionsOf(policy) {
29
+ if (!Array.isArray(policy.conditions))
30
+ return [];
31
+ return policy.conditions.filter(isRecord);
32
+ }
33
+ /** The policy's annotation map, narrowed to the string-valued entries Cedar allows. */
34
+ export function annotationsOf(policy) {
35
+ return isRecord(policy.annotations) ? policy.annotations : {};
36
+ }
37
+ /** The `@id` annotation, when it is a string. */
38
+ export function annotatedId(policy) {
39
+ const id = annotationsOf(policy).id;
40
+ return typeof id === "string" ? id : undefined;
41
+ }
42
+ /**
43
+ * Whether a build's `env` (from `--env` or the project's `ownership.env`, see
44
+ * `PostSynthContext#env`) is production-like, for the checks whose severity is
45
+ * env-gated. Same seam and same vocabulary as the aws lexicon's
46
+ * `isFullTierEnv`: an unset or unrecognized env is treated as non-production,
47
+ * so a project that never passes `--env` is never unexpectedly hard-failed.
48
+ */
49
+ export function isProdLikeEnv(env) {
50
+ return env === "prod" || env === "production" || env === "full";
51
+ }
52
+ // ── Reading policy sets out of the build output ───────────────────
53
+ /** Every candidate policy-set document in the build, parsed or not. */
54
+ export function policySets(ctx) {
55
+ const sets = [];
56
+ for (const [lexicon, output] of ctx.outputs) {
57
+ if (typeof output === "string") {
58
+ // A bare string output. `createPostSynthContext` and `chant audit` over a
59
+ // standalone file both land here; only JSON is a policy set, `.cedar`
60
+ // text is the human-facing twin and carries nothing the JSON does not.
61
+ //
62
+ // Every lexicon's output shares this map, and several of them are JSON,
63
+ // so "is it JSON" is not enough to claim one: take it when it came from
64
+ // the cedar output (where even unparseable content is a cedar finding —
65
+ // CEDC010 reports it) or when it is unmistakably a policy-set envelope.
66
+ if (!looksLikeJson(output))
67
+ continue;
68
+ const set = readSet(lexicon, lexicon, output);
69
+ if (lexicon === CEDAR_LEXICON || isPolicySetEnvelope(set.doc))
70
+ sets.push(set);
71
+ continue;
72
+ }
73
+ for (const [filename, content] of Object.entries(output.files ?? {})) {
74
+ if (!filename.endsWith(CEDAR_JSON_SUFFIX))
75
+ continue;
76
+ if (typeof content !== "string")
77
+ continue;
78
+ sets.push(readSet(lexicon, filename, content));
79
+ }
80
+ }
81
+ return sets;
82
+ }
83
+ /** The subset of {@link policySets} that parsed as JSON. */
84
+ export function parsedPolicySets(ctx) {
85
+ return policySets(ctx).filter((set) => set.doc !== undefined);
86
+ }
87
+ function looksLikeJson(text) {
88
+ return text.trimStart().startsWith("{");
89
+ }
90
+ /** The `PolicySet` envelope has one of these two keys; nothing else does. */
91
+ function isPolicySetEnvelope(doc) {
92
+ return doc !== undefined && (isRecord(doc.staticPolicies) || isRecord(doc.templates));
93
+ }
94
+ function readSet(lexicon, source, raw) {
95
+ let doc;
96
+ let parseError;
97
+ try {
98
+ const parsed = JSON.parse(raw);
99
+ if (isRecord(parsed))
100
+ doc = parsed;
101
+ else
102
+ parseError = "policy set is not a JSON object";
103
+ }
104
+ catch (err) {
105
+ parseError = err instanceof Error ? err.message : String(err);
106
+ }
107
+ return { lexicon, source, raw, doc, parseError, entries: doc ? flatten(doc) : [] };
108
+ }
109
+ function flatten(doc) {
110
+ const entries = [];
111
+ for (const [group, template] of [
112
+ [doc.staticPolicies, false],
113
+ [doc.templates, true],
114
+ ]) {
115
+ if (!isRecord(group))
116
+ continue;
117
+ for (const [key, policy] of Object.entries(group)) {
118
+ if (isRecord(policy))
119
+ entries.push({ key, policy: policy, template });
120
+ }
121
+ }
122
+ return entries;
123
+ }