@intentius/chant-lexicon-cedar 0.124.30 → 0.124.32

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/plugin.js ADDED
@@ -0,0 +1,304 @@
1
+ import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
2
+ import { cedarSerializer } from "./serializer.js";
3
+ import { rules } from "./lint/rules/index.js";
4
+ import { postSynthChecks as cedarPostSynthChecks } from "./lint/post-synth/index.js";
5
+ import { cedarAuditCatalog } from "./lint/audit-catalog.js";
6
+ import { completions } from "./lsp/completions.js";
7
+ import { hover } from "./lsp/hover.js";
8
+ import { cedarMcpResources, cedarMcpTools } from "./mcp/index.js";
9
+ import { cedarInitTemplates } from "./init-templates.js";
10
+ import { cedarConfigSchema } from "./config.js";
11
+ import { detectTemplate as detectCedarTemplate } from "./detect.js";
12
+ import { CedarTemplateGenerator, CedarTemplateParser } from "./import/adapter.js";
13
+ import { AVP_OWNERSHIP_KEYS } from "./avp/ownership.js";
14
+ import { AVP_AMBIENT_KINDS } from "./avp/ambient.js";
15
+ import { cedarCommandGroup } from "./commands.js";
16
+ /**
17
+ * cedar lexicon plugin.
18
+ *
19
+ * Implements all required LexiconPlugin lifecycle methods.
20
+ */
21
+ export const cedarPlugin = {
22
+ name: "cedar",
23
+ serializer: cedarSerializer,
24
+ // ── Required lifecycle methods ────────────────────────────────
25
+ async generate(options) {
26
+ // Cedar's codegen input is the project's own schema, so the `cedar` config
27
+ // namespace is read here rather than being decoration (#1650).
28
+ //
29
+ // The output goes into the *project* (#1696): `cedar.outDir`, else
30
+ // `src/generated/cedar` under the config's directory. Only when the project
31
+ // is this package itself does it land in `src/generated/`.
32
+ const { generate, resolveGeneratedDir, writeGeneratedFiles } = await import("./codegen/generate.js");
33
+ const { loadCedarProject } = await import("./config.js");
34
+ const { projectRoot, config } = await loadCedarProject(process.cwd());
35
+ const result = await generate({ ...options, projectRoot, config });
36
+ const outDir = resolveGeneratedDir({ projectRoot, config });
37
+ writeGeneratedFiles(result, outDir);
38
+ if (options?.verbose)
39
+ console.error(`cedar: wrote generated classes to ${outDir}`);
40
+ },
41
+ async validate(options) {
42
+ const { validate } = await import("./validate.js");
43
+ const { printValidationResult } = await import("@intentius/chant/codegen/validate");
44
+ const result = await validate();
45
+ printValidationResult(result);
46
+ },
47
+ async coverage(options) {
48
+ const { analyzeCedarCoverage } = await import("./coverage.js");
49
+ const { resolveGeneratedDir } = await import("./codegen/generate.js");
50
+ const { loadCedarProject } = await import("./config.js");
51
+ const { projectRoot, config } = await loadCedarProject(process.cwd());
52
+ analyzeCedarCoverage({
53
+ projectRoot,
54
+ config,
55
+ // Coverage compares the project's schema with the project's generated
56
+ // artifacts, which live where generate put them (#1696).
57
+ generatedDir: resolveGeneratedDir({ projectRoot, config }),
58
+ verbose: options?.verbose,
59
+ minOverall: options?.minOverall,
60
+ });
61
+ },
62
+ async package(options) {
63
+ const { packageLexicon } = await import("./codegen/package.js");
64
+ const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
65
+ const { join, dirname } = await import("path");
66
+ const { fileURLToPath } = await import("url");
67
+ const { spec, stats } = await packageLexicon(options);
68
+ const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
69
+ writeBundleSpec(spec, join(pkgDir, "dist"));
70
+ console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
71
+ },
72
+ // ── Optional extensions ────────────────────────────────────
73
+ // #1697 — the project's `.cedarschema` joins the build as a `Cedar::Schema`
74
+ // entity, so it is emitted beside the policies and CEDE010 validates
75
+ // against it rather than reporting that it could not. Nothing is
76
+ // contributed when only the bundled default schema applies.
77
+ async buildRoots(ctx) {
78
+ const { schemaBuildRoot } = await import("./schema-artifact.js");
79
+ return schemaBuildRoot(ctx);
80
+ },
81
+ // `chant cedar generate` and `chant cedar coverage` (#1696, via #1078).
82
+ commands() {
83
+ return cedarCommandGroup();
84
+ },
85
+ lintRules() {
86
+ return rules;
87
+ },
88
+ postSynthChecks() {
89
+ // Auto-discovered from lint/post-synth/ and committed as a static barrel
90
+ // by `npm run generate:barrels` — adding a check is dropping a file.
91
+ return cedarPostSynthChecks;
92
+ },
93
+ auditCatalog() {
94
+ return cedarAuditCatalog;
95
+ },
96
+ skills: createSkillsLoader(import.meta.url, [
97
+ {
98
+ file: "chant-cedar-authoring.md",
99
+ name: "chant-cedar-authoring",
100
+ description: "Author Cedar policies as typed chant resources — schema to generated classes to .cedar and JSON outputs",
101
+ triggers: [
102
+ { type: "file-pattern", value: "**/*.cedarschema" },
103
+ { type: "file-pattern", value: "**/policies.ts" },
104
+ { type: "context", value: "cedar policy" },
105
+ { type: "context", value: "cedar schema" },
106
+ { type: "context", value: "authorization policy" },
107
+ ],
108
+ parameters: [],
109
+ examples: [
110
+ {
111
+ title: "Owner-scoped permit",
112
+ description: "Grant an action to the owner of a resource",
113
+ input: "Let users read documents they own",
114
+ output: `new Policy({ principal: { is: "App::User" }, action: { eq: ReadAction }, when: ["resource.owner == principal"] })`,
115
+ },
116
+ ],
117
+ },
118
+ {
119
+ file: "chant-cedar-avp-embedding.md",
120
+ name: "chant-cedar-avp-embedding",
121
+ description: "Embed a typed cedar-lexicon policy into an AWS Verified Permissions policy resource instead of a hand-written string",
122
+ triggers: [
123
+ { type: "context", value: "verified permissions" },
124
+ { type: "context", value: "avp policy store" },
125
+ { type: "context", value: "cedar deployment" },
126
+ { type: "context", value: "cedar-agent" },
127
+ ],
128
+ parameters: [],
129
+ examples: [
130
+ {
131
+ title: "Policy destined for a policy store",
132
+ input: "Deploy this Cedar policy to Verified Permissions",
133
+ output: "Emit the .cedar text and reference it — the typed statement seam lands with #1652",
134
+ },
135
+ ],
136
+ },
137
+ {
138
+ file: "chant-cedar-meta-policy.md",
139
+ name: "chant-cedar-meta-policy",
140
+ description: "Organizational policy over Cedar policy sets — the bare-permit wall, forbid conventions, and env-aware severity",
141
+ triggers: [
142
+ { type: "context", value: "cedar lint" },
143
+ { type: "context", value: "bare permit" },
144
+ { type: "context", value: "policy review" },
145
+ { type: "context", value: "meta-policy" },
146
+ ],
147
+ parameters: [],
148
+ examples: [
149
+ {
150
+ title: "The bare-permit wall",
151
+ input: "Is `permit (principal, action, resource);` acceptable?",
152
+ output: "Warn in dev and staging; fail the build in prod",
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ file: "chant-cedar-dogwood.md",
158
+ name: "chant-cedar-dogwood",
159
+ description: "Author dogwood temporal policies with the typed builders — parser primitives versus default-library macros, the callerPrincipal pin, the CLI-gated validation split, AgentCore embedding and trace replay",
160
+ triggers: [
161
+ { type: "file-pattern", value: "**/*.dw" },
162
+ { type: "file-pattern", value: "**/*.dwschema" },
163
+ { type: "context", value: "dogwood" },
164
+ { type: "context", value: "temporal policy" },
165
+ { type: "context", value: "agentcore policy" },
166
+ { type: "context", value: "rate limit policy" },
167
+ { type: "context", value: "policy replay" },
168
+ ],
169
+ parameters: [],
170
+ examples: [
171
+ {
172
+ title: "Approval before action",
173
+ description: "Allow a read only if a login happened inside the window",
174
+ input: "Only let them read if they logged in within the last hour",
175
+ output: `whenTemporal: [dogwood.formerly("1h", dogwood.predicate('Drupe::Action::"Login"', "response"))]`,
176
+ },
177
+ {
178
+ title: "The macro caveat",
179
+ input: "Use count_within for a rate limit",
180
+ output: "count_within is a default-library macro a --macros caller replaces — emit the call and ship the definition, or build it from `count for … where`",
181
+ },
182
+ ],
183
+ },
184
+ ]),
185
+ initTemplates(template) {
186
+ // The branch names are spelled out here rather than only in
187
+ // ./init-templates.ts because `chant dev check-lexicon` counts a lexicon's
188
+ // templates by scanning this file. The content lives next door.
189
+ if (template === "avp-embedding")
190
+ return cedarInitTemplates("avp-embedding");
191
+ if (template === "gateway-policy-set")
192
+ return cedarInitTemplates("gateway-policy-set");
193
+ return cedarInitTemplates(template);
194
+ },
195
+ mcpTools() {
196
+ return cedarMcpTools();
197
+ },
198
+ mcpResources() {
199
+ // `createCatalogResource` resolves the registry relative to the module URL
200
+ // it is handed, so it needs this file's — src/generated/, not src/mcp/.
201
+ return cedarMcpResources(import.meta.url);
202
+ },
203
+ detectTemplate(data) {
204
+ return detectCedarTemplate(data);
205
+ },
206
+ /** `.cedar` text and the JSON policy-set envelope, into the core IR (#1653). */
207
+ templateParser() {
208
+ return new CedarTemplateParser();
209
+ },
210
+ /** The IR back out as `new Policy({ … })` against the generated classes. */
211
+ templateGenerator() {
212
+ return new CedarTemplateGenerator();
213
+ },
214
+ completionProvider(ctx) {
215
+ return completions(ctx);
216
+ },
217
+ hoverProvider(ctx) {
218
+ return hover(ctx);
219
+ },
220
+ async docs(options) {
221
+ const { generateDocs } = await import("./codegen/docs.js");
222
+ return generateDocs(options);
223
+ },
224
+ /** The shape of the `cedar` key in `chant.config.ts` — see ./config.ts. */
225
+ configSchema: cedarConfigSchema,
226
+ /**
227
+ * The pinned upstream is the Cedar *grammar*, not a downloaded spec (#1650).
228
+ *
229
+ * `CEDAR_WASM_VERSION` is the package version the self-upgrade tooling bumps;
230
+ * `CEDAR_LANG_VERSION` beside it is the language version `generate()` asserts
231
+ * at runtime, since a package bump that leaves the language at 4.5 cannot
232
+ * change what parses. Both live in src/spec/pin.ts, which also carries the
233
+ * content pin over the bundled default schema.
234
+ */
235
+ upstreamPin: {
236
+ file: "src/spec/pin.ts",
237
+ pattern: /export const CEDAR_WASM_VERSION\s*=\s*"([^"]+)"/,
238
+ replace: (v, line) => line.replace(/= "[^"]+"/, `= "${v}"`),
239
+ upstream: { owner: "cedar-policy", repo: "cedar", kind: "releases" },
240
+ },
241
+ // ── AVP lifecycle (#1652) ──────────────────────────────────────
242
+ /**
243
+ * AVP policy stores are taggable; the policies inside them are not, so the
244
+ * per-policy channel is a marker encoded into `Definition.Static.Description`
245
+ * and the store's tags are the coarse channel beside it. Only the two paths
246
+ * that actually read the description are declared (chant #1348) — the full
247
+ * design record is src/avp/OWNERSHIP.md.
248
+ */
249
+ ownershipChannel: {
250
+ keys: AVP_OWNERSHIP_KEYS,
251
+ reads: ["describeResources", "exportResources"],
252
+ },
253
+ /**
254
+ * Observe the declared policy set against a live AVP policy store.
255
+ *
256
+ * The tri-state carries more weight here than on most substrates: a declared
257
+ * policy reported absent becomes a `create`, and creating a permit that
258
+ * already exists duplicates a grant. Every path that did not look says so.
259
+ */
260
+ async describeResources(options) {
261
+ const { describeAvpResources } = await import("./avp/describe-resources.js");
262
+ return describeAvpResources({
263
+ environment: options.environment,
264
+ entityNames: options.entityNames,
265
+ entities: options.entities,
266
+ ...(options.owned !== undefined ? { owned: options.owned } : {}),
267
+ ...(options.region ? { client: { region: options.region } } : {}),
268
+ });
269
+ },
270
+ /** The one kind this lexicon deploys, and therefore the one it can enumerate. */
271
+ ambientKinds() {
272
+ return [...AVP_AMBIENT_KINDS];
273
+ },
274
+ /**
275
+ * Policies in the store that nothing declares — a standing grant nobody in
276
+ * the source tree can see. For an authorization store this is a security
277
+ * finding rather than housekeeping.
278
+ */
279
+ async observeAmbient(options) {
280
+ const { observeAvpAmbient } = await import("./avp/ambient.js");
281
+ return observeAvpAmbient({
282
+ environment: options.environment,
283
+ kinds: options.kinds,
284
+ observed: options.observed,
285
+ ...(options.region ? { client: { region: options.region } } : {}),
286
+ });
287
+ },
288
+ /**
289
+ * Full-fidelity read of the live policies, as import IR. Server-written
290
+ * fields are stripped to the declared shape unless `verbatim` is set — see
291
+ * the note in src/avp/live-export.ts for what counts as server-written when
292
+ * the only thing a user authors is the statement.
293
+ */
294
+ async exportResources(options) {
295
+ const { exportAvpResources } = await import("./avp/live-export.js");
296
+ return exportAvpResources({
297
+ environment: options.environment,
298
+ ...(options.selector ? { selector: options.selector } : {}),
299
+ ...(options.owned !== undefined ? { owned: options.owned } : {}),
300
+ ...(options.verbatim !== undefined ? { verbatim: options.verbatim } : {}),
301
+ ...(options.region ? { client: { region: options.region } } : {}),
302
+ });
303
+ },
304
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Cedar policy-text rendering, shared by the `.cedar` serializer, the AVP
3
+ * statement renderer, and the dogwood dialect's `.dw` leg.
4
+ *
5
+ * Upstream's dogwood policy grammar is Cedar's shape — `annotation* effect
6
+ * "(" scope ")" cond* ";"` — and only the `cond` rule differs, gaining
7
+ * `guardrails { … }` and `temporal { … }` forms. So everything above the first
8
+ * clause is common, and it lives here rather than being copied into
9
+ * `./dogwood/serialize.ts`, where a change to how a scope constraint is
10
+ * written would have had to be made twice.
11
+ *
12
+ * `resolvePolicyId` is here for the same reason it was extracted from
13
+ * `serialize()` by #1652: it is the only rule linking a chant entity to a
14
+ * policy id, the AVP observation matches against it, and the `.dw` leg has to
15
+ * derive ids the same way the `.cedar` leg does. One copy, three callers.
16
+ *
17
+ * This module is a leaf on purpose: `./serializer.ts` and `./dogwood/*` both
18
+ * import it, and neither imports the other's rendering.
19
+ */
20
+ import { isResourceDeclarable } from "@intentius/chant/declarable";
21
+ /** A plain object — not null, not an array. */
22
+ export function isRecord(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ /** The `props` of a resource-kind Declarable, or `{}` for anything else. */
26
+ export function getProps(entity) {
27
+ if (isResourceDeclarable(entity) && isRecord(entity.props)) {
28
+ return entity.props;
29
+ }
30
+ return {};
31
+ }
32
+ /** Escape a string for a double-quoted Cedar literal. */
33
+ export function escapeCedarString(value) {
34
+ return value
35
+ .replace(/\\/g, "\\\\")
36
+ .replace(/"/g, '\\"')
37
+ .replace(/\n/g, "\\n")
38
+ .replace(/\r/g, "\\r")
39
+ .replace(/\t/g, "\\t");
40
+ }
41
+ /**
42
+ * Derive a policy id from a logical name: `allowAdminRead` → `allow-admin-read`.
43
+ * Cedar ids are free-form strings; kebab-case keeps them readable in the
44
+ * `@id` annotation and stable across a rename-free refactor.
45
+ */
46
+ export function policyIdFromLogicalName(name) {
47
+ return name
48
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
49
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
50
+ .replace(/[\s_]+/g, "-")
51
+ .toLowerCase();
52
+ }
53
+ /**
54
+ * The Cedar id for a policy: an explicit `annotations.id` when the author gave
55
+ * one, else derived from the logical name.
56
+ *
57
+ * This is the only rule that links a chant entity to a policy in a live AVP
58
+ * store (#1652) — the observation resolves the same id from the same props and
59
+ * matches it against the `@id` annotation the statement carries — and it is
60
+ * also what gives a `.dw` policy the id its `.cedar` sibling would have had.
61
+ * Two copies of this rule would be a mapping that drifts silently.
62
+ */
63
+ export function resolvePolicyId(logicalName, props) {
64
+ const explicit = isRecord(props.annotations) ? props.annotations.id : undefined;
65
+ return typeof explicit === "string" && explicit.length > 0
66
+ ? explicit
67
+ : policyIdFromLogicalName(logicalName);
68
+ }
69
+ function refText(value) {
70
+ return String(value);
71
+ }
72
+ /** `principal`, `principal == User::"alice"`, `action in [ … ]`, `resource is Photo`. */
73
+ export function renderScope(variable, scope) {
74
+ if (!isRecord(scope))
75
+ return variable;
76
+ const parts = [variable];
77
+ if (typeof scope.is === "string")
78
+ parts.push(`is ${scope.is}`);
79
+ if (scope.eq !== undefined)
80
+ parts.push(`== ${refText(scope.eq)}`);
81
+ if (scope.in !== undefined) {
82
+ parts.push(Array.isArray(scope.in)
83
+ ? `in [${scope.in.map(refText).join(", ")}]`
84
+ : `in ${refText(scope.in)}`);
85
+ }
86
+ return parts.join(" ");
87
+ }
88
+ /** A `when`/`unless` prop as a list of clause bodies — one string, or several. */
89
+ export function conditionStrings(value) {
90
+ if (value === undefined || value === null)
91
+ return [];
92
+ if (Array.isArray(value))
93
+ return value.map(refText);
94
+ return [refText(value)];
95
+ }
96
+ /**
97
+ * Everything above the first clause: the annotations, the effect, and the
98
+ * three scope positions.
99
+ */
100
+ export function renderPolicyHead(id, props) {
101
+ const lines = [];
102
+ // @id first, then the author's own annotations in declaration order.
103
+ const annotations = isRecord(props.annotations) ? props.annotations : {};
104
+ lines.push(`@id("${escapeCedarString(id)}")`);
105
+ for (const [key, value] of Object.entries(annotations)) {
106
+ if (key === "id" || value === undefined || value === null)
107
+ continue;
108
+ lines.push(`@${key}("${escapeCedarString(refText(value))}")`);
109
+ }
110
+ const effect = props.effect === "forbid" ? "forbid" : "permit";
111
+ lines.push(`${effect} (`);
112
+ lines.push(` ${renderScope("principal", props.principal)},`);
113
+ lines.push(` ${renderScope("action", props.action)},`);
114
+ lines.push(` ${renderScope("resource", props.resource)}`);
115
+ lines.push(")");
116
+ return lines;
117
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The project schema as a build artifact (#1697).
3
+ *
4
+ * CEDE010 can only validate a policy set against a schema the build emitted
5
+ * beside it, and until now nothing emitted one: the `.cedarschema` drove
6
+ * codegen and then stayed in the source tree, so every consumer build got the
7
+ * parse-only advisory. This module closes that gap from two directions.
8
+ *
9
+ * `Schema` is a declarable a project can author by hand when it wants a
10
+ * particular filename or a schema that is not the one in `chant.config.ts`.
11
+ * `schemaBuildRoot` is the automatic path: the plugin's `buildRoots` hook
12
+ * contributes one `Schema` entity per build, holding the resolved project
13
+ * schema, so a project with a `cedar.schema` gets `dist/schema.cedarschema`
14
+ * and full validation without declaring anything.
15
+ *
16
+ * The bundled default schema is never emitted. A project without its own
17
+ * schema has policies the default may well reject, and CEDE010 failing a
18
+ * build against a schema the project never wrote would be a worse surprise
19
+ * than the advisory it replaces.
20
+ */
21
+ import { isPropertyDeclarable } from "@intentius/chant/declarable";
22
+ import { createResource } from "@intentius/chant/runtime";
23
+ import { getProps } from "./policy-text.js";
24
+ import { resolveSchemaPath } from "./spec/fetch.js";
25
+ import { readFileSync } from "fs";
26
+ import { basename } from "path";
27
+ /** The entity type of a {@link Schema}. */
28
+ export const CEDAR_SCHEMA_TYPE = "Cedar::Schema";
29
+ /** The logical name the build-root contribution uses. */
30
+ export const CEDAR_SCHEMA_ENTITY_NAME = "cedarSchema";
31
+ /** Filename the schema is emitted under when `filename` is unset. */
32
+ export const CEDAR_SCHEMA_FILENAME = "schema.cedarschema";
33
+ /** A `.cedarschema` emitted with the policy set, so post-synth validation has something to validate against. */
34
+ export const Schema = createResource(CEDAR_SCHEMA_TYPE, "cedar", {});
35
+ /** Every `Cedar::Schema` in the entity map, with its props read. */
36
+ export function schemaEntities(entities) {
37
+ const files = [];
38
+ for (const [name, entity] of entities) {
39
+ if (isPropertyDeclarable(entity))
40
+ continue;
41
+ if (entity.entityType !== CEDAR_SCHEMA_TYPE)
42
+ continue;
43
+ const props = getProps(entity);
44
+ if (typeof props.text !== "string")
45
+ continue;
46
+ files.push({
47
+ name,
48
+ filename: typeof props.filename === "string" ? props.filename : CEDAR_SCHEMA_FILENAME,
49
+ text: props.text.endsWith("\n") ? props.text : props.text + "\n",
50
+ });
51
+ }
52
+ return files;
53
+ }
54
+ /**
55
+ * The `buildRoots` contribution: the project's resolved schema as one
56
+ * {@link Schema} entity, or nothing when only the bundled default applies.
57
+ */
58
+ export async function schemaBuildRoot(ctx) {
59
+ const config = ctx.config.cedar ?? {};
60
+ const source = resolveSchemaPath({ projectRoot: ctx.projectRoot, config });
61
+ if (source.isDefault)
62
+ return { entities: new Map() };
63
+ const text = readFileSync(source.path, "utf-8");
64
+ const entity = new Schema({ text, filename: basename(source.path) });
65
+ return { entities: new Map([[CEDAR_SCHEMA_ENTITY_NAME, entity]]) };
66
+ }