@intentius/chant-lexicon-cedar 0.44.8 → 0.44.9
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 +58 -0
- package/dist/codegen/package.d.ts.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/dogwood/cli.d.ts +206 -0
- package/dist/dogwood/cli.d.ts.map +1 -0
- package/dist/dogwood/event-schema.d.ts +161 -0
- package/dist/dogwood/event-schema.d.ts.map +1 -0
- package/dist/dogwood/index.d.ts +33 -0
- package/dist/dogwood/index.d.ts.map +1 -0
- package/dist/dogwood/macros.d.ts +96 -0
- package/dist/dogwood/macros.d.ts.map +1 -0
- package/dist/dogwood/policy.d.ts +120 -0
- package/dist/dogwood/policy.d.ts.map +1 -0
- package/dist/dogwood/scan.d.ts +109 -0
- package/dist/dogwood/scan.d.ts.map +1 -0
- package/dist/dogwood/serialize.d.ts +46 -0
- package/dist/dogwood/serialize.d.ts.map +1 -0
- package/dist/dogwood/temporal.d.ts +259 -0
- package/dist/dogwood/temporal.d.ts.map +1 -0
- package/dist/dogwood/upstream.d.ts +41 -0
- package/dist/dogwood/upstream.d.ts.map +1 -0
- package/dist/dogwood/window.d.ts +73 -0
- package/dist/dogwood/window.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +13 -3
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/dogwood-helpers.d.ts +63 -0
- package/dist/lint/post-synth/dogwood-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc010.d.ts +25 -0
- package/dist/lint/post-synth/dwdc010.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc011.d.ts +19 -0
- package/dist/lint/post-synth/dwdc011.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc012.d.ts +21 -0
- package/dist/lint/post-synth/dwdc012.d.ts.map +1 -0
- package/dist/lint/post-synth/dwde010.d.ts +32 -0
- package/dist/lint/post-synth/dwde010.d.ts.map +1 -0
- package/dist/lint/post-synth/dwde011.d.ts +33 -0
- package/dist/lint/post-synth/dwde011.d.ts.map +1 -0
- package/dist/lint/post-synth/dwds010.d.ts +24 -0
- package/dist/lint/post-synth/dwds010.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +6 -0
- package/dist/okf/rules/DWDC010.md +11 -0
- package/dist/okf/rules/DWDC011.md +11 -0
- package/dist/okf/rules/DWDC012.md +11 -0
- package/dist/okf/rules/DWDE010.md +11 -0
- package/dist/okf/rules/DWDE011.md +11 -0
- package/dist/okf/rules/DWDS010.md +11 -0
- package/dist/policy-text.d.ts +53 -0
- package/dist/policy-text.d.ts.map +1 -0
- package/dist/rules/dogwood-helpers.ts +139 -0
- package/dist/rules/dwdc010.ts +62 -0
- package/dist/rules/dwdc011.ts +61 -0
- package/dist/rules/dwdc012.ts +46 -0
- package/dist/rules/dwde010.ts +130 -0
- package/dist/rules/dwde011.ts +108 -0
- package/dist/rules/dwds010.ts +46 -0
- package/dist/serializer.d.ts +10 -18
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-cedar-authoring.md +180 -0
- package/dist/skills/chant-cedar-avp-embedding.md +125 -0
- package/dist/skills/chant-cedar-meta-policy.md +119 -0
- package/package.json +2 -2
- package/src/codegen/package.ts +3 -2
- package/src/config.test.ts +12 -0
- package/src/config.ts +28 -0
- package/src/dogwood/cli.test.ts +392 -0
- package/src/dogwood/cli.ts +545 -0
- package/src/dogwood/event-schema.test.ts +218 -0
- package/src/dogwood/event-schema.ts +318 -0
- package/src/dogwood/index.ts +198 -0
- package/src/dogwood/macros.test.ts +104 -0
- package/src/dogwood/macros.ts +229 -0
- package/src/dogwood/policy.test.ts +94 -0
- package/src/dogwood/policy.ts +141 -0
- package/src/dogwood/scan.ts +287 -0
- package/src/dogwood/serialize.test.ts +331 -0
- package/src/dogwood/serialize.ts +209 -0
- package/src/dogwood/temporal.test.ts +272 -0
- package/src/dogwood/temporal.ts +592 -0
- package/src/dogwood/testdata/custom-kinds.dwschema +17 -0
- package/src/dogwood/testdata/default-macros.dw +23 -0
- package/src/dogwood/testdata/lowered-read-after-login.json +13 -0
- package/src/dogwood/testdata/max-window-raised.dwschema +25 -0
- package/src/dogwood/testdata/pinned.dwschema +31 -0
- package/src/dogwood/testdata/read-after-login.cedarschema +20 -0
- package/src/dogwood/testdata/read-after-login.dw +17 -0
- package/src/dogwood/testdata/temporal-policies.dw +53 -0
- package/src/dogwood/upstream.ts +41 -0
- package/src/dogwood/window.ts +124 -0
- package/src/index.ts +24 -0
- package/src/lint/audit-catalog.ts +56 -0
- package/src/lint/post-synth/dogwood-helpers.ts +139 -0
- package/src/lint/post-synth/dwd-post-synth.test.ts +256 -0
- package/src/lint/post-synth/dwdc010.ts +62 -0
- package/src/lint/post-synth/dwdc011.ts +61 -0
- package/src/lint/post-synth/dwdc012.ts +46 -0
- package/src/lint/post-synth/dwde-post-synth.test.ts +368 -0
- package/src/lint/post-synth/dwde010.ts +130 -0
- package/src/lint/post-synth/dwde011.ts +108 -0
- package/src/lint/post-synth/dwds010.ts +46 -0
- package/src/lint/post-synth/index.ts +12 -0
- package/src/lint/post-synth/post-synth.test.ts +7 -3
- package/src/policy-text.ts +128 -0
- package/src/serializer.ts +71 -109
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { describe, test, expect } from "vitest";
|
|
13
13
|
import { createPostSynthContext, makePostSynthCtxFromFiles } from "@intentius/chant-test-utils";
|
|
14
14
|
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
15
|
-
import { CEDAR_JSON_FILENAME } from "../../serializer";
|
|
15
|
+
import { CEDAR_JSON_FILENAME, cedarSerializer } from "../../serializer";
|
|
16
16
|
import { postSynthChecks } from ".";
|
|
17
17
|
import { cedc010 } from "./cedc010";
|
|
18
18
|
import { cedc011 } from "./cedc011";
|
|
@@ -103,10 +103,14 @@ function ctxWithSchema(
|
|
|
103
103
|
// ── Barrel ─────────────────────────────────────────────────────────
|
|
104
104
|
|
|
105
105
|
describe("the cedar post-synth barrel", () => {
|
|
106
|
-
test("ships every check exactly once,
|
|
106
|
+
test("ships every check exactly once, under a declared prefix", () => {
|
|
107
|
+
// Two id families, both declared on the serializer: CED for Cedar itself,
|
|
108
|
+
// DWD for the dogwood temporal dialect that ships inside this lexicon
|
|
109
|
+
// (#1658). An id outside both is what `chant dev check-lexicon` fails on.
|
|
110
|
+
const declared = [cedarSerializer.rulePrefix, ...(cedarSerializer.extraRulePrefixes ?? [])];
|
|
107
111
|
const ids = postSynthChecks.map((c) => c.id);
|
|
108
112
|
expect(new Set(ids).size).toBe(ids.length);
|
|
109
|
-
expect(ids.
|
|
113
|
+
expect(ids.filter((id) => !declared.some((p) => id.startsWith(p)))).toEqual([]);
|
|
110
114
|
expect(ids.length).toBeGreaterThanOrEqual(10);
|
|
111
115
|
});
|
|
112
116
|
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
|
|
21
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
22
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
23
|
+
|
|
24
|
+
/** A plain object — not null, not an array. */
|
|
25
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The `props` of a resource-kind Declarable, or `{}` for anything else. */
|
|
30
|
+
export function getProps(entity: Declarable): Record<string, unknown> {
|
|
31
|
+
if (isResourceDeclarable(entity) && isRecord(entity.props)) {
|
|
32
|
+
return entity.props;
|
|
33
|
+
}
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Escape a string for a double-quoted Cedar literal. */
|
|
38
|
+
export function escapeCedarString(value: string): string {
|
|
39
|
+
return value
|
|
40
|
+
.replace(/\\/g, "\\\\")
|
|
41
|
+
.replace(/"/g, '\\"')
|
|
42
|
+
.replace(/\n/g, "\\n")
|
|
43
|
+
.replace(/\r/g, "\\r")
|
|
44
|
+
.replace(/\t/g, "\\t");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Derive a policy id from a logical name: `allowAdminRead` → `allow-admin-read`.
|
|
49
|
+
* Cedar ids are free-form strings; kebab-case keeps them readable in the
|
|
50
|
+
* `@id` annotation and stable across a rename-free refactor.
|
|
51
|
+
*/
|
|
52
|
+
export function policyIdFromLogicalName(name: string): string {
|
|
53
|
+
return name
|
|
54
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
55
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
56
|
+
.replace(/[\s_]+/g, "-")
|
|
57
|
+
.toLowerCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The Cedar id for a policy: an explicit `annotations.id` when the author gave
|
|
62
|
+
* one, else derived from the logical name.
|
|
63
|
+
*
|
|
64
|
+
* This is the only rule that links a chant entity to a policy in a live AVP
|
|
65
|
+
* store (#1652) — the observation resolves the same id from the same props and
|
|
66
|
+
* matches it against the `@id` annotation the statement carries — and it is
|
|
67
|
+
* also what gives a `.dw` policy the id its `.cedar` sibling would have had.
|
|
68
|
+
* Two copies of this rule would be a mapping that drifts silently.
|
|
69
|
+
*/
|
|
70
|
+
export function resolvePolicyId(logicalName: string, props: Record<string, unknown>): string {
|
|
71
|
+
const explicit = isRecord(props.annotations) ? props.annotations.id : undefined;
|
|
72
|
+
return typeof explicit === "string" && explicit.length > 0
|
|
73
|
+
? explicit
|
|
74
|
+
: policyIdFromLogicalName(logicalName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function refText(value: unknown): string {
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** `principal`, `principal == User::"alice"`, `action in [ … ]`, `resource is Photo`. */
|
|
82
|
+
export function renderScope(variable: string, scope: unknown): string {
|
|
83
|
+
if (!isRecord(scope)) return variable;
|
|
84
|
+
|
|
85
|
+
const parts = [variable];
|
|
86
|
+
if (typeof scope.is === "string") parts.push(`is ${scope.is}`);
|
|
87
|
+
if (scope.eq !== undefined) parts.push(`== ${refText(scope.eq)}`);
|
|
88
|
+
if (scope.in !== undefined) {
|
|
89
|
+
parts.push(
|
|
90
|
+
Array.isArray(scope.in)
|
|
91
|
+
? `in [${scope.in.map(refText).join(", ")}]`
|
|
92
|
+
: `in ${refText(scope.in)}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return parts.join(" ");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A `when`/`unless` prop as a list of clause bodies — one string, or several. */
|
|
99
|
+
export function conditionStrings(value: unknown): string[] {
|
|
100
|
+
if (value === undefined || value === null) return [];
|
|
101
|
+
if (Array.isArray(value)) return value.map(refText);
|
|
102
|
+
return [refText(value)];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Everything above the first clause: the annotations, the effect, and the
|
|
107
|
+
* three scope positions.
|
|
108
|
+
*/
|
|
109
|
+
export function renderPolicyHead(id: string, props: Record<string, unknown>): string[] {
|
|
110
|
+
const lines: string[] = [];
|
|
111
|
+
|
|
112
|
+
// @id first, then the author's own annotations in declaration order.
|
|
113
|
+
const annotations = isRecord(props.annotations) ? props.annotations : {};
|
|
114
|
+
lines.push(`@id("${escapeCedarString(id)}")`);
|
|
115
|
+
for (const [key, value] of Object.entries(annotations)) {
|
|
116
|
+
if (key === "id" || value === undefined || value === null) continue;
|
|
117
|
+
lines.push(`@${key}("${escapeCedarString(refText(value))}")`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const effect = props.effect === "forbid" ? "forbid" : "permit";
|
|
121
|
+
lines.push(`${effect} (`);
|
|
122
|
+
lines.push(` ${renderScope("principal", props.principal)},`);
|
|
123
|
+
lines.push(` ${renderScope("action", props.action)},`);
|
|
124
|
+
lines.push(` ${renderScope("resource", props.resource)}`);
|
|
125
|
+
lines.push(")");
|
|
126
|
+
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
package/src/serializer.ts
CHANGED
|
@@ -19,14 +19,28 @@
|
|
|
19
19
|
* (#1650) generates typed classes onto exactly this shape, so nothing here
|
|
20
20
|
* changes when it lands — the guards become typed expressions rather than
|
|
21
21
|
* the opaque Cedar-expression strings they are today.
|
|
22
|
+
*
|
|
23
|
+
* A third view arrives when the build holds dogwood entities (#1658): `.dw`
|
|
24
|
+
* policy text and its `.dwschema`/`macros.dw` companions, rendered by
|
|
25
|
+
* `./dogwood/serialize.ts` and returned in the same result. Both legs share
|
|
26
|
+
* `./policy-text.ts` rather than a copy of it, because a `.dw` policy's head
|
|
27
|
+
* *is* a Cedar policy's head.
|
|
22
28
|
*/
|
|
23
29
|
|
|
24
30
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
25
|
-
import { isPropertyDeclarable
|
|
31
|
+
import { isPropertyDeclarable } from "@intentius/chant/declarable";
|
|
26
32
|
import type { Serializer, SerializerResult } from "@intentius/chant/serializer";
|
|
27
33
|
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
28
34
|
import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
|
|
29
35
|
import { policyToJson, splitPolicySet, templateToJson, type PolicyJson } from "./spec/wasm";
|
|
36
|
+
import { serializeDogwood } from "./dogwood/serialize";
|
|
37
|
+
import {
|
|
38
|
+
conditionStrings,
|
|
39
|
+
getProps,
|
|
40
|
+
isRecord,
|
|
41
|
+
renderPolicyHead,
|
|
42
|
+
resolvePolicyId,
|
|
43
|
+
} from "./policy-text";
|
|
30
44
|
|
|
31
45
|
// ── The policy entity model ───────────────────────────────────────
|
|
32
46
|
|
|
@@ -74,40 +88,22 @@ export interface CedarPolicyProps {
|
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
// ── Helpers ───────────────────────────────────────────────────────
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.replace(/"/g, '\\"')
|
|
94
|
-
.replace(/\n/g, "\\n")
|
|
95
|
-
.replace(/\r/g, "\\r")
|
|
96
|
-
.replace(/\t/g, "\\t");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Derive a policy id from a logical name: `allowAdminRead` → `allow-admin-read`.
|
|
101
|
-
* Cedar ids are free-form strings; kebab-case keeps them readable in the
|
|
102
|
-
* `@id` annotation and stable across a rename-free refactor.
|
|
103
|
-
*/
|
|
104
|
-
export function policyIdFromLogicalName(name: string): string {
|
|
105
|
-
return name
|
|
106
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
107
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
108
|
-
.replace(/[\s_]+/g, "-")
|
|
109
|
-
.toLowerCase();
|
|
110
|
-
}
|
|
91
|
+
//
|
|
92
|
+
// Rendering the dogwood dialect shares lives in `./policy-text.ts`, along with
|
|
93
|
+
// `resolvePolicyId` — the one rule linking a chant entity to a policy id, which
|
|
94
|
+
// #1652 extracted for the AVP observation and the `.dw` leg needs for the same
|
|
95
|
+
// reason. Re-exported here so this module stays the one import a caller needs.
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
conditionStrings,
|
|
99
|
+
escapeCedarString,
|
|
100
|
+
getProps,
|
|
101
|
+
isRecord,
|
|
102
|
+
policyIdFromLogicalName,
|
|
103
|
+
renderPolicyHead,
|
|
104
|
+
renderScope,
|
|
105
|
+
resolvePolicyId,
|
|
106
|
+
} from "./policy-text";
|
|
111
107
|
|
|
112
108
|
// ── Walker visitor ────────────────────────────────────────────────
|
|
113
109
|
|
|
@@ -131,33 +127,6 @@ const cedarVisitor: SerializerVisitor = {
|
|
|
131
127
|
|
|
132
128
|
// ── Cedar policy text ─────────────────────────────────────────────
|
|
133
129
|
|
|
134
|
-
function refText(value: unknown): string {
|
|
135
|
-
return String(value);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** `principal`, `principal == User::"alice"`, `action in [ … ]`, `resource is Photo`. */
|
|
139
|
-
function renderScope(variable: string, scope: unknown): string {
|
|
140
|
-
if (!isRecord(scope)) return variable;
|
|
141
|
-
|
|
142
|
-
const parts = [variable];
|
|
143
|
-
if (typeof scope.is === "string") parts.push(`is ${scope.is}`);
|
|
144
|
-
if (scope.eq !== undefined) parts.push(`== ${refText(scope.eq)}`);
|
|
145
|
-
if (scope.in !== undefined) {
|
|
146
|
-
parts.push(
|
|
147
|
-
Array.isArray(scope.in)
|
|
148
|
-
? `in [${scope.in.map(refText).join(", ")}]`
|
|
149
|
-
: `in ${refText(scope.in)}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
return parts.join(" ");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function conditionStrings(value: unknown): string[] {
|
|
156
|
-
if (value === undefined || value === null) return [];
|
|
157
|
-
if (Array.isArray(value)) return value.map(refText);
|
|
158
|
-
return [refText(value)];
|
|
159
|
-
}
|
|
160
|
-
|
|
161
130
|
/**
|
|
162
131
|
* One policy, as `.cedar` text.
|
|
163
132
|
*
|
|
@@ -165,24 +134,12 @@ function conditionStrings(value: unknown): string[] {
|
|
|
165
134
|
* nothing else — `AWS::VerifiedPermissions::Policy` carries its policy as
|
|
166
135
|
* `Definition.Static.Statement`, a single Cedar policy rather than a set. Two
|
|
167
136
|
* renderers would be two dialects; there is one.
|
|
137
|
+
*
|
|
138
|
+
* The head comes from `renderPolicyHead`, which the dogwood dialect's `.dw`
|
|
139
|
+
* leg also calls: three surfaces, one rendering of an annotation and a scope.
|
|
168
140
|
*/
|
|
169
141
|
export function renderPolicyText(id: string, props: Record<string, unknown>): string {
|
|
170
|
-
const lines
|
|
171
|
-
|
|
172
|
-
// @id first, then the author's own annotations in declaration order.
|
|
173
|
-
const annotations = isRecord(props.annotations) ? props.annotations : {};
|
|
174
|
-
lines.push(`@id("${escapeCedarString(id)}")`);
|
|
175
|
-
for (const [key, value] of Object.entries(annotations)) {
|
|
176
|
-
if (key === "id" || value === undefined || value === null) continue;
|
|
177
|
-
lines.push(`@${key}("${escapeCedarString(refText(value))}")`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const effect = props.effect === "forbid" ? "forbid" : "permit";
|
|
181
|
-
lines.push(`${effect} (`);
|
|
182
|
-
lines.push(` ${renderScope("principal", props.principal)},`);
|
|
183
|
-
lines.push(` ${renderScope("action", props.action)},`);
|
|
184
|
-
lines.push(` ${renderScope("resource", props.resource)}`);
|
|
185
|
-
lines.push(")");
|
|
142
|
+
const lines = renderPolicyHead(id, props);
|
|
186
143
|
|
|
187
144
|
for (const clause of conditionStrings(props.when)) {
|
|
188
145
|
lines.push(`when { ${clause} }`);
|
|
@@ -257,22 +214,6 @@ export interface CedarPolicyRecord {
|
|
|
257
214
|
props: Record<string, unknown>;
|
|
258
215
|
}
|
|
259
216
|
|
|
260
|
-
/**
|
|
261
|
-
* The Cedar id for a policy: an explicit `annotations.id` when the author gave
|
|
262
|
-
* one, else derived from the logical name.
|
|
263
|
-
*
|
|
264
|
-
* This is the only rule that links a chant entity to a policy in a live AVP
|
|
265
|
-
* store (#1652) — the observation resolves the same id from the same props and
|
|
266
|
-
* matches it against the `@id` annotation the statement carries. Two copies of
|
|
267
|
-
* this rule would be a mapping that drifts silently, so there is one.
|
|
268
|
-
*/
|
|
269
|
-
export function resolvePolicyId(logicalName: string, props: Record<string, unknown>): string {
|
|
270
|
-
const explicit = isRecord(props.annotations) ? props.annotations.id : undefined;
|
|
271
|
-
return typeof explicit === "string" && explicit.length > 0
|
|
272
|
-
? explicit
|
|
273
|
-
: policyIdFromLogicalName(logicalName);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
217
|
/**
|
|
277
218
|
* Every `Cedar::Policy` in a build, with references walked and ids resolved.
|
|
278
219
|
*
|
|
@@ -304,6 +245,11 @@ export const cedarSerializer: Serializer = {
|
|
|
304
245
|
name: "cedar",
|
|
305
246
|
rulePrefix: "CED",
|
|
306
247
|
|
|
248
|
+
// The dogwood temporal dialect ships inside this lexicon (epic #1646): a
|
|
249
|
+
// `.dw` file stripped of Cedar semantics is meaningless, so its checks are
|
|
250
|
+
// this serializer's, under their own published id family.
|
|
251
|
+
extraRulePrefixes: ["DWD"],
|
|
252
|
+
|
|
307
253
|
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[]): string | SerializerResult {
|
|
308
254
|
const policyText: string[] = [];
|
|
309
255
|
|
|
@@ -311,25 +257,41 @@ export const cedarSerializer: Serializer = {
|
|
|
311
257
|
policyText.push(renderPolicyText(id, props));
|
|
312
258
|
}
|
|
313
259
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
260
|
+
// The dogwood dialect's own entities (#1658). Emitted from here rather
|
|
261
|
+
// than from a second serializer because they are one policy set with two
|
|
262
|
+
// halves — the `.dw` text and the `.cedar` text describe the same system,
|
|
263
|
+
// and a project holding only temporal policies still gets its files.
|
|
264
|
+
const dogwood = serializeDogwood(entities);
|
|
265
|
+
|
|
266
|
+
if (policyText.length === 0 && !dogwood) return "";
|
|
267
|
+
|
|
268
|
+
const files: Record<string, string> = {};
|
|
269
|
+
const warnings: string[] = [...(dogwood?.warnings ?? [])];
|
|
270
|
+
let primary = "";
|
|
271
|
+
|
|
272
|
+
if (policyText.length > 0) {
|
|
273
|
+
primary = policyText.join("\n\n") + "\n";
|
|
274
|
+
const { doc, error } = policySetJSON(primary);
|
|
275
|
+
|
|
276
|
+
// A policy set Cedar cannot read is a real defect, but it is the lint and
|
|
277
|
+
// post-synth surface's to report (#1651) — the text is still the artifact
|
|
278
|
+
// every evaluator consumes, so it is emitted either way, with the module's
|
|
279
|
+
// own message carried out as a build warning rather than swallowed.
|
|
280
|
+
if (doc) {
|
|
281
|
+
files[CEDAR_JSON_FILENAME] = JSON.stringify(doc, null, 2) + "\n";
|
|
282
|
+
} else {
|
|
283
|
+
warnings.push(
|
|
284
|
+
`cedar: the emitted policy text did not parse, so ${CEDAR_JSON_FILENAME} was not written — ${error}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
328
287
|
}
|
|
329
288
|
|
|
289
|
+
Object.assign(files, dogwood?.files ?? {});
|
|
290
|
+
|
|
330
291
|
return {
|
|
331
292
|
primary,
|
|
332
|
-
files
|
|
293
|
+
...(Object.keys(files).length > 0 ? { files } : {}),
|
|
294
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
333
295
|
};
|
|
334
296
|
},
|
|
335
297
|
};
|