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