@intentius/chant 0.44.14 → 0.45.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.
Files changed (170) hide show
  1. package/dist/audit/discover.d.ts +26 -2
  2. package/dist/audit/discover.d.ts.map +1 -1
  3. package/dist/audit/report-model.d.ts +10 -0
  4. package/dist/audit/report-model.d.ts.map +1 -1
  5. package/dist/audit/rules-doc.d.ts.map +1 -1
  6. package/dist/cli/build-params-cli.d.ts +16 -7
  7. package/dist/cli/build-params-cli.d.ts.map +1 -1
  8. package/dist/cli/commands/audit.d.ts +20 -0
  9. package/dist/cli/commands/audit.d.ts.map +1 -1
  10. package/dist/cli/commands/build.d.ts +9 -0
  11. package/dist/cli/commands/build.d.ts.map +1 -1
  12. package/dist/cli/commands/check-lexicon-docs.d.ts +13 -0
  13. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  14. package/dist/cli/commands/check-lexicon-examples.d.ts +24 -14
  15. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  18. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +5 -0
  19. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  20. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  21. package/dist/cli/commands/onboard.d.ts +28 -0
  22. package/dist/cli/commands/onboard.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.d.ts.map +1 -1
  24. package/dist/cli/handlers/graph.d.ts.map +1 -1
  25. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/handlers/search.d.ts +27 -0
  28. package/dist/cli/handlers/search.d.ts.map +1 -1
  29. package/dist/cli/main.d.ts.map +1 -1
  30. package/dist/codegen/docs-pages.d.ts +39 -0
  31. package/dist/codegen/docs-pages.d.ts.map +1 -0
  32. package/dist/codegen/docs-sections.d.ts.map +1 -1
  33. package/dist/codegen/docs-sidebar.d.ts +13 -2
  34. package/dist/codegen/docs-sidebar.d.ts.map +1 -1
  35. package/dist/codegen/docs-types.d.ts +30 -10
  36. package/dist/codegen/docs-types.d.ts.map +1 -1
  37. package/dist/codegen/docs.d.ts +14 -2
  38. package/dist/codegen/docs.d.ts.map +1 -1
  39. package/dist/codegen/surface-snapshot.d.ts +27 -3
  40. package/dist/codegen/surface-snapshot.d.ts.map +1 -1
  41. package/dist/codegen/validate.d.ts +11 -3
  42. package/dist/codegen/validate.d.ts.map +1 -1
  43. package/dist/components/capability-plugin.d.ts +13 -2
  44. package/dist/components/capability-plugin.d.ts.map +1 -1
  45. package/dist/components/driver.d.ts +38 -0
  46. package/dist/components/driver.d.ts.map +1 -1
  47. package/dist/components/index.d.ts +1 -1
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/starter-plugin.d.ts.map +1 -1
  50. package/dist/config.d.ts +57 -5
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/discovery/fold-import.d.ts.map +1 -1
  53. package/dist/fold/fold.d.ts +100 -1
  54. package/dist/fold/fold.d.ts.map +1 -1
  55. package/dist/graph-ir.d.ts +12 -0
  56. package/dist/graph-ir.d.ts.map +1 -1
  57. package/dist/graph-ops.d.ts +17 -0
  58. package/dist/graph-ops.d.ts.map +1 -0
  59. package/dist/lifecycle/change-set.d.ts +23 -2
  60. package/dist/lifecycle/change-set.d.ts.map +1 -1
  61. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  62. package/dist/lifecycle/observe.d.ts +8 -0
  63. package/dist/lifecycle/observe.d.ts.map +1 -1
  64. package/dist/lifecycle/replay.d.ts.map +1 -1
  65. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  66. package/dist/lifecycle/types.d.ts +7 -0
  67. package/dist/lifecycle/types.d.ts.map +1 -1
  68. package/dist/lint/policy.d.ts.map +1 -1
  69. package/dist/managed-fields.d.ts +33 -21
  70. package/dist/managed-fields.d.ts.map +1 -1
  71. package/dist/observation.d.ts +23 -1
  72. package/dist/observation.d.ts.map +1 -1
  73. package/dist/yaml.d.ts +0 -8
  74. package/dist/yaml.d.ts.map +1 -1
  75. package/package.json +1 -1
  76. package/src/__snapshots__/okf.test.ts.snap +20 -0
  77. package/src/audit/discover.test.ts +55 -1
  78. package/src/audit/discover.ts +70 -2
  79. package/src/audit/edge-init-safety.test.ts +149 -18
  80. package/src/audit/report-model.ts +12 -1
  81. package/src/audit/rules-doc.ts +1 -0
  82. package/src/build-params.test.ts +52 -0
  83. package/src/cli/build-params-cli.test.ts +25 -8
  84. package/src/cli/build-params-cli.ts +37 -23
  85. package/src/cli/commands/__fixtures__/audit-coverage/.github/workflows/ci.yml +5 -0
  86. package/src/cli/commands/__fixtures__/audit-coverage/Dockerfile +2 -0
  87. package/src/cli/commands/__fixtures__/audit-coverage/infra/main.tf +3 -0
  88. package/src/cli/commands/__fixtures__/audit-coverage/infra/stack.json +6 -0
  89. package/src/cli/commands/__fixtures__/audit-coverage/k8s/deploy.yaml +12 -0
  90. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/pages/getting-started.mdx +16 -0
  91. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +10 -2
  92. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +3 -1
  93. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +2 -1
  94. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +2 -1
  95. package/src/cli/commands/audit.test.ts +83 -1
  96. package/src/cli/commands/audit.ts +121 -28
  97. package/src/cli/commands/build.test.ts +144 -5
  98. package/src/cli/commands/build.ts +66 -14
  99. package/src/cli/commands/check-lexicon-docs.ts +30 -0
  100. package/src/cli/commands/check-lexicon-examples.test.ts +73 -7
  101. package/src/cli/commands/check-lexicon-examples.ts +66 -17
  102. package/src/cli/commands/check-lexicon.test.ts +1 -1
  103. package/src/cli/commands/check-lexicon.ts +19 -4
  104. package/src/cli/commands/init-lexicon/templates/codegen.ts +5 -2
  105. package/src/cli/commands/init-lexicon/templates/docs.ts +35 -2
  106. package/src/cli/commands/init-lexicon.test.ts +42 -1
  107. package/src/cli/commands/init-lexicon.ts +3 -1
  108. package/src/cli/commands/lexicon-rolling-upgrade.ts +1 -1
  109. package/src/cli/commands/lexicon-surface-diff.test.ts +1 -1
  110. package/src/cli/commands/lexicon-surface-diff.ts +1 -1
  111. package/src/cli/commands/onboard.test.ts +319 -225
  112. package/src/cli/commands/onboard.ts +144 -58
  113. package/src/cli/handlers/build.test.ts +3 -3
  114. package/src/cli/handlers/build.ts +2 -0
  115. package/src/cli/handlers/components.ts +2 -2
  116. package/src/cli/handlers/graph.test.ts +44 -0
  117. package/src/cli/handlers/graph.ts +22 -2
  118. package/src/cli/handlers/lifecycle.test.ts +117 -0
  119. package/src/cli/handlers/lifecycle.ts +16 -0
  120. package/src/cli/handlers/run.test.ts +5 -5
  121. package/src/cli/handlers/run.ts +1 -0
  122. package/src/cli/handlers/search-live.test.ts +217 -0
  123. package/src/cli/handlers/search.test.ts +41 -1
  124. package/src/cli/handlers/search.ts +55 -6
  125. package/src/cli/main.ts +5 -3
  126. package/src/cli/security-boundary.test.ts +2 -1
  127. package/src/codegen/docs-pages.test.ts +194 -0
  128. package/src/codegen/docs-pages.ts +138 -0
  129. package/src/codegen/docs-sections.ts +0 -8
  130. package/src/codegen/docs-sidebar.ts +50 -37
  131. package/src/codegen/docs-types.ts +32 -4
  132. package/src/codegen/docs.ts +78 -40
  133. package/src/codegen/lexicon-regen.ts +3 -1
  134. package/src/codegen/rolling-upgrade.test.ts +1 -1
  135. package/src/codegen/rolling-upgrade.ts +1 -1
  136. package/src/codegen/surface-snapshot.test.ts +139 -0
  137. package/src/codegen/surface-snapshot.ts +297 -122
  138. package/src/codegen/validate.test.ts +16 -3
  139. package/src/codegen/validate.ts +17 -7
  140. package/src/components/auto-release.ts +1 -1
  141. package/src/components/capability-plugin.ts +23 -2
  142. package/src/components/driver.test.ts +45 -0
  143. package/src/components/driver.ts +64 -27
  144. package/src/components/index.ts +2 -0
  145. package/src/components/starter-plugin.ts +5 -2
  146. package/src/config.ts +101 -7
  147. package/src/discovery/fold-import.test.ts +427 -2
  148. package/src/discovery/fold-import.ts +163 -7
  149. package/src/discovery/sandbox/driver.test.ts +11 -0
  150. package/src/fold/fold.test.ts +41 -0
  151. package/src/fold/fold.ts +372 -2
  152. package/src/graph-ir-live.test.ts +42 -0
  153. package/src/graph-ir.ts +42 -2
  154. package/src/graph-ops.test.ts +69 -0
  155. package/src/graph-ops.ts +42 -0
  156. package/src/lifecycle/change-set.test.ts +41 -0
  157. package/src/lifecycle/change-set.ts +30 -2
  158. package/src/lifecycle/deep-observe.ts +4 -0
  159. package/src/lifecycle/observe.ts +29 -2
  160. package/src/lifecycle/replay.ts +3 -0
  161. package/src/lifecycle/snapshot.ts +3 -0
  162. package/src/lifecycle/types.ts +7 -0
  163. package/src/lint/policy.ts +14 -3
  164. package/src/managed-fields.test.ts +15 -42
  165. package/src/managed-fields.ts +43 -26
  166. package/src/observation.test.ts +24 -8
  167. package/src/observation.ts +48 -4
  168. package/src/ownership.test.ts +63 -1
  169. package/src/yaml.test.ts +15 -0
  170. package/src/yaml.ts +19 -2
@@ -112,6 +112,24 @@ export interface ObservationResult {
112
112
  * namespace, endpoint or region was read, not the one the resource lives in.
113
113
  */
114
114
  queried?: Record<string, string>;
115
+ /**
116
+ * Notices about the read as a whole, not about any one entity (#1265) —
117
+ * "the ownership filter could not be applied on this surface" is the
118
+ * canonical one. A note is a property of the environment or the read path,
119
+ * so core says each distinct note once per run, after the answer, however
120
+ * many stacks or lexicons reported it. A lexicon returns it here instead of
121
+ * printing, so the note cannot land ahead of the rows it qualifies.
122
+ */
123
+ notes?: string[];
124
+ /**
125
+ * What the deployable unit publishes, keyed by the stack that publishes it
126
+ * (#1279) — a CloudFormation stack's outputs, for instance. Stack-level, so it
127
+ * lives here once rather than on every resource: a node's `attributes` are
128
+ * that resource's own properties, and a VPC that carried the stack's
129
+ * `expWebIp` beside no `CidrBlock` of its own was answering the wrong
130
+ * question. Values are already scrubbed of anything that looks secret.
131
+ */
132
+ stackExports?: Record<string, Record<string, unknown>>;
115
133
  }
116
134
 
117
135
  /**
@@ -126,6 +144,10 @@ export interface NormalizedObservation {
126
144
  unobserved: Record<string, UnobservedEntity>;
127
145
  /** Resolved query address per entity name (#1620). Empty when the lexicon reported none. */
128
146
  queried: Record<string, string>;
147
+ /** Run-level notices (#1265), distinct. Empty when the lexicon reported none. */
148
+ notes: string[];
149
+ /** Per-stack exports (#1279), keyed by stack name. Absent when the lexicon reported none. */
150
+ stackExports?: Record<string, Record<string, unknown>>;
129
151
  }
130
152
 
131
153
  /** True when `value` is the versioned {@link ObservationResult} envelope. */
@@ -145,12 +167,16 @@ export function observation(
145
167
  resources: Record<string, ResourceMetadata>,
146
168
  unobserved?: Record<string, UnobservedEntity>,
147
169
  queried?: Record<string, string>,
170
+ notes?: string[],
171
+ stackExports?: Record<string, Record<string, unknown>>,
148
172
  ): ObservationResult {
149
173
  return {
150
174
  observation: "v1",
151
175
  resources,
152
176
  ...(unobserved && Object.keys(unobserved).length > 0 ? { unobserved } : {}),
153
177
  ...(queried && Object.keys(queried).length > 0 ? { queried } : {}),
178
+ ...(notes && notes.length > 0 ? { notes } : {}),
179
+ ...(stackExports && Object.keys(stackExports).length > 0 ? { stackExports } : {}),
154
180
  };
155
181
  }
156
182
 
@@ -161,11 +187,17 @@ export function observation(
161
187
  * {@link unobservedAll} rather than returning nothing.
162
188
  */
163
189
  export function normalizeObservation(value: DescribeResourcesResult | undefined): NormalizedObservation {
164
- if (!value) return { resources: {}, unobserved: {}, queried: {} };
190
+ if (!value) return { resources: {}, unobserved: {}, queried: {}, notes: [] };
165
191
  if (isObservationResult(value)) {
166
- return { resources: value.resources ?? {}, unobserved: value.unobserved ?? {}, queried: value.queried ?? {} };
192
+ return {
193
+ resources: value.resources ?? {},
194
+ unobserved: value.unobserved ?? {},
195
+ queried: value.queried ?? {},
196
+ notes: [...new Set(value.notes ?? [])],
197
+ ...(value.stackExports && Object.keys(value.stackExports).length > 0 ? { stackExports: value.stackExports } : {}),
198
+ };
167
199
  }
168
- return { resources: value, unobserved: {}, queried: {} };
200
+ return { resources: value, unobserved: {}, queried: {}, notes: [] };
169
201
  }
170
202
 
171
203
  /**
@@ -205,15 +237,27 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
205
237
  const resources: Record<string, ResourceMetadata> = {};
206
238
  const unobserved: Record<string, UnobservedEntity> = {};
207
239
  const queried: Record<string, string> = {};
240
+ // A note is about the read, not a stack; four stacks saying the same thing
241
+ // is one note (#1265).
242
+ const notes = new Set<string>();
243
+ const stackExports: Record<string, Record<string, unknown>> = {};
208
244
  for (const part of parts) {
209
245
  Object.assign(resources, part.resources);
210
246
  Object.assign(unobserved, part.unobserved);
211
247
  Object.assign(queried, part.queried);
248
+ for (const n of part.notes) notes.add(n);
249
+ Object.assign(stackExports, part.stackExports ?? {});
212
250
  }
213
251
  // Present wins: a stack that could not be read does not un-observe a resource
214
252
  // another stack returned.
215
253
  for (const name of Object.keys(resources)) delete unobserved[name];
216
- return { resources, unobserved, queried };
254
+ return {
255
+ resources,
256
+ unobserved,
257
+ queried,
258
+ notes: [...notes],
259
+ ...(Object.keys(stackExports).length > 0 ? { stackExports } : {}),
260
+ };
217
261
  }
218
262
 
219
263
  /** One-line human phrasing of a reason, for CLI output. */
@@ -9,7 +9,12 @@ import {
9
9
  OWNERSHIP_MANAGED_BY_VALUE,
10
10
  type ChannelKeys,
11
11
  } from "./ownership";
12
- import { resolveOwnershipMarker } from "./config";
12
+ import {
13
+ resolveOwnershipMarker,
14
+ resolveOwnershipEnv,
15
+ resolveOwnershipStack,
16
+ ownershipEnvDisagreement,
17
+ } from "./config";
13
18
 
14
19
  // A stand-in for a lexicon-provided convention (e.g. AWS's colon keys), to
15
20
  // prove the core helpers are generic over any `ChannelKeys` — the per-provider
@@ -105,3 +110,60 @@ describe("resolveOwnershipMarker (config opt-in)", () => {
105
110
  expect(resolveOwnershipMarker({ ownership: { stack: "billing", enabled: false } })).toBeUndefined();
106
111
  });
107
112
  });
113
+
114
+ describe("ownership.env as a build-parameter reference (#1396)", () => {
115
+ const config = {
116
+ ownership: { stack: "fountain", env: { param: "env" } },
117
+ buildParams: { env: { type: "string" as const, default: "dev", env: "FOUNTAIN_ENV" } },
118
+ };
119
+
120
+ test("takes the resolved parameter value — --param env=prod drives the marker", () => {
121
+ expect(resolveOwnershipMarker(config, [{ name: "env", value: "prod", source: "cli" }])).toEqual({
122
+ stack: "fountain",
123
+ env: "prod",
124
+ });
125
+ expect(resolveOwnershipMarker(config, [{ name: "env", value: "dev", source: "default" }])).toEqual({
126
+ stack: "fountain",
127
+ env: "dev",
128
+ });
129
+ });
130
+
131
+ test("a reference to an undeclared parameter is an error, not an env-less marker", () => {
132
+ expect(() => resolveOwnershipMarker({ ownership: { stack: "s", env: { param: "env" } } }, [])).toThrow(
133
+ /does not declare/,
134
+ );
135
+ });
136
+
137
+ test("a reference to a parameter that resolved to nothing is an error", () => {
138
+ expect(() => resolveOwnershipMarker(config, [])).toThrow(
139
+ /resolved to no value.*--param env=<value>.*FOUNTAIN_ENV/,
140
+ );
141
+ });
142
+
143
+ test("the stack-only resolver needs no parameters", () => {
144
+ expect(resolveOwnershipStack(config)).toBe("fountain");
145
+ expect(resolveOwnershipStack({ ownership: { stack: "s", enabled: false } })).toBeUndefined();
146
+ });
147
+
148
+ test("a literal env is returned untouched, with or without parameters", () => {
149
+ expect(resolveOwnershipEnv({ ownership: { stack: "s", env: "prod" } }, undefined)).toBe("prod");
150
+ expect(resolveOwnershipEnv({ ownership: { stack: "s" } }, [])).toBeUndefined();
151
+ });
152
+
153
+ test("a literal env disagreeing with an env parameter is reported", () => {
154
+ const literal = {
155
+ ownership: { stack: "fountain", env: "dev" },
156
+ buildParams: { env: { type: "string" as const, default: "dev" } },
157
+ };
158
+ expect(ownershipEnvDisagreement(literal, [{ name: "env", value: "dev", source: "default" }])).toBeUndefined();
159
+ expect(ownershipEnvDisagreement(literal, [{ name: "env", value: "prod", source: "cli" }])).toMatch(
160
+ /ownership\.env is "dev" but the env build parameter resolved to "prod" \(cli\)/,
161
+ );
162
+ // A reference never disagrees — there is one source.
163
+ expect(ownershipEnvDisagreement(config, [{ name: "env", value: "prod", source: "cli" }])).toBeUndefined();
164
+ // Marking off — nothing is stamped, nothing to disagree with.
165
+ expect(
166
+ ownershipEnvDisagreement({ ownership: { env: "dev" } }, [{ name: "env", value: "prod", source: "cli" }]),
167
+ ).toBeUndefined();
168
+ });
169
+ });
package/src/yaml.test.ts CHANGED
@@ -10,6 +10,21 @@ describe("emitYAML", () => {
10
10
  expect(emitYAML(undefined, 0)).toBe("null");
11
11
  });
12
12
 
13
+ test("an undefined object value drops the key, an explicit null keeps it (#1371)", () => {
14
+ // Matches `JSON.stringify`: `{ a: undefined }` is `{}`; an unset optional
15
+ // build parameter passed straight into a resource must not ship as
16
+ // `a: null`.
17
+ expect(emitYAML({ a: undefined, b: 1 }, 0)).toBe("\nb: 1");
18
+ expect(emitYAML({ a: null, b: 1 }, 0)).toBe("\na: null\nb: 1");
19
+ expect(emitYAML({ a: undefined }, 0)).toBe("{}");
20
+ expect(emitYAML({ spec: { baseImageArn: undefined, name: "x" } }, 0)).toBe("\nspec:\n name: x");
21
+ });
22
+
23
+ test("an undefined inside an array-item object drops the key; a bare undefined element is null", () => {
24
+ expect(emitYAML([{ a: undefined, b: 1 }, { c: undefined, d: 2, e: 3 }], 0)).toBe("\n- b: 1\n- d: 2\n e: 3");
25
+ expect(emitYAML([undefined, 1], 0)).toBe("\n- null\n- 1");
26
+ });
27
+
13
28
  test("booleans", () => {
14
29
  expect(emitYAML(true, 0)).toBe("true");
15
30
  expect(emitYAML(false, 0)).toBe("false");
package/src/yaml.ts CHANGED
@@ -19,9 +19,26 @@
19
19
  * with `\n` so the caller can append it after a key.
20
20
  * - Tagged values `{ tag, value }` emit `!tag [...]` or `!tag scalar`.
21
21
  */
22
+ /**
23
+ * An object's entries minus those whose value is `undefined`. An `undefined`
24
+ * property is "not supplied" in TypeScript (a declared-but-unset optional
25
+ * build parameter, `{ x: cond ? v : undefined }`), which `JSON.stringify`
26
+ * already omits; the YAML emitter must agree, or the same source ships
27
+ * `key: null` under `-o out.yaml` and no key at all under `-o out.json`. An
28
+ * explicit `null` is a value and is kept (chant #1371).
29
+ */
30
+ function definedEntries(obj: Record<string, unknown>): [string, unknown][] {
31
+ return Object.entries(obj).filter(([, val]) => val !== undefined);
32
+ }
33
+
22
34
  export function emitYAML(value: unknown, indent: number): string {
23
35
  const prefix = " ".repeat(indent);
24
36
 
37
+ // `undefined` only reaches here as a top-level value or an array element —
38
+ // an object entry whose value is `undefined` is dropped below, the same way
39
+ // `JSON.stringify` omits it. A bare `undefined` renders as `null` for the
40
+ // same reason `JSON.stringify([undefined])` is `[null]`: YAML has no way to
41
+ // say "absent" in a sequence slot.
25
42
  if (value === null || value === undefined) {
26
43
  return "null";
27
44
  }
@@ -74,7 +91,7 @@ export function emitYAML(value: unknown, indent: number): string {
74
91
  for (const item of value) {
75
92
  if (typeof item === "object" && item !== null && !Array.isArray(item)) {
76
93
  // Object items in arrays
77
- const entries = Object.entries(item as Record<string, unknown>);
94
+ const entries = definedEntries(item as Record<string, unknown>);
78
95
  if (entries.length > 0) {
79
96
  const [firstKey, firstVal] = entries[0];
80
97
  const firstEmitted = emitYAML(firstVal, indent + 2);
@@ -111,7 +128,7 @@ export function emitYAML(value: unknown, indent: number): string {
111
128
  return `${obj.tag} ${emitYAML(obj.value, indent)}`;
112
129
  }
113
130
 
114
- const entries = Object.entries(obj);
131
+ const entries = definedEntries(obj);
115
132
  if (entries.length === 0) return "{}";
116
133
  const lines: string[] = [];
117
134
  for (const [key, val] of entries) {