@intentius/chant 0.73.0 → 0.74.1

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/dist/build.d.ts CHANGED
@@ -56,6 +56,22 @@ export interface StackGraph {
56
56
  * referencing an attribute of a resource in lexicon B ⇒ A depends on B). Returns
57
57
  * the edge set plus a topological order, parallel-safe waves, and any cycles.
58
58
  */
59
+ /**
60
+ * Is this entity a member of a lexicon stack?
61
+ *
62
+ * Four kinds are not, and every one of them is serializer-neutral: a
63
+ * `LexiconOutput` is collected separately, a secret provenance declaration
64
+ * (#1828) and a plan scenario (#1292) are data lint and the CLI read off the
65
+ * entity map, and an Op (#2118) builds to its own `op.json` rather than
66
+ * through a lexicon serializer.
67
+ *
68
+ * {@link partitionByLexicon} has skipped all four since each landed, and
69
+ * {@link computeStackGraph}'s walk did not — so an entity with no `lexicon`
70
+ * (a `LexiconOutput`) entered `edges`, `order` and `waves` as `undefined`
71
+ * while `nodes`, built from the partition, could never contain it (#2480).
72
+ * One predicate for both sites, so they cannot drift apart again.
73
+ */
74
+ export declare function isStackMember(entity: Declarable): boolean;
59
75
  export declare function computeStackGraph(entities: Map<string, Declarable>, lexiconNames: string[]): StackGraph;
60
76
  /**
61
77
  * Result of the build process
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,UAAU,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAyB,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAiC,MAAM,UAAU,CAAC;AACzE,OAAO,EAAE,aAAa,EAAmB,MAAM,kBAAkB,CAAC;AAQlE,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAKvF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,CACb,MAAM,EACN;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CACtD,CAAC;IACF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,wEAAwE;IACxE,UAAU,EAAE,UAAU,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3C,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAClB,yEAAyE;IACzE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,YAAY,EAAE,MAAM,EAAE,GACrB,UAAU,CAgFZ;AAED;;GAEG;AACH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAE5B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE7B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEnD;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAErC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAAC,CAAC;IAChD,8CAA8C;IAC9C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,qDAAqD;IACrD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,MAAM,EAAE,KAAK,CAAC,cAAc,GAAG,UAAU,CAAC,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,EAAE,aAAa,CAAC;IACxB,uCAAuC;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,YAAY,EAAE,CAAC;IAE9B;;;;;;;;;;;;OAYG;IACH,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;;;OAMG;IACH,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAEpC;;;;;;;;;;;;;OAaG;IACH,cAAc,EAAE,cAAc,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAChC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CA6BtC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAChC,aAAa,EAAE,CAuEjB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAChC,aAAa,EAAE,CA+FjB;AA6ED;8DAC8D;AAC9D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,YAAY,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAChD,OAAO,CAAC,oBAAoB,CAAC,CAwB/B;AAED;;;;;;;GAOG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,UAAU,EAAE,EACzB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC,CAYtB;AA6MD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,sBAAsB,EAC5B,WAAW,EAAE,UAAU,EAAE,EACzB,KAAK,SAAsB,EAC3B,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,WAAW,CAAC,CAgBtB"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.73.0",
3
+ "version": "0.74.1",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
package/src/build.test.ts CHANGED
@@ -2,7 +2,7 @@ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
2
  import { build, partitionByLexicon, detectCrossLexiconRefs, collectLexiconOutputs, computeStackGraph } from "./build";
3
3
  import { output } from "./lexicon-output";
4
4
  import { AttrRef } from "./attrref";
5
- import { INTRINSIC_MARKER } from "./intrinsic";
5
+ import { INTRINSIC_MARKER, type Intrinsic } from "./intrinsic";
6
6
  import type { Serializer } from "./serializer";
7
7
  import type { Declarable } from "./declarable";
8
8
  import { DECLARABLE_MARKER } from "./declarable";
@@ -793,6 +793,59 @@ describe("computeStackGraph (#200 — cross-stack apply ordering)", () => {
793
793
  const ent = (lexicon: string, props: Record<string, unknown> = {}): Declarable =>
794
794
  ({ lexicon, entityType: `${lexicon}::X`, [DECLARABLE_MARKER]: true, props }) as unknown as Declarable;
795
795
 
796
+ // #2480: the walk took `entity.lexicon` as the consumer for every entity,
797
+ // and four kinds have no place in a stack graph. A LexiconOutput carries no
798
+ // `lexicon` at all, so it produced an edge with `from: undefined`, and that
799
+ // `undefined` then reached `order` and `waves` — while `nodes`, built from
800
+ // the partition, could never contain it. Reproduced on chant's own
801
+ // fan-out-estate and bedrock-agentcore-agent examples, both of which declare
802
+ // outputs built from intrinsics.
803
+ //
804
+ // The invariant these assert is the one a consumer can actually rely on:
805
+ // `nodes` is the roster, and nothing may appear anywhere else that is not
806
+ // named in it.
807
+ const rosterHolds = (g: ReturnType<typeof computeStackGraph>): void => {
808
+ const roster = new Set(g.nodes);
809
+ expect(g.nodes.every((n) => typeof n === "string")).toBe(true);
810
+ for (const e of g.edges) {
811
+ expect(roster.has(e.from)).toBe(true);
812
+ expect(roster.has(e.to)).toBe(true);
813
+ }
814
+ for (const n of g.order) expect(roster.has(n)).toBe(true);
815
+ for (const wave of g.waves) for (const n of wave) expect(roster.has(n)).toBe(true);
816
+ for (const cycle of g.cycles) for (const n of cycle) expect(roster.has(n)).toBe(true);
817
+ };
818
+
819
+ test("a LexiconOutput is not a stack member, so it cannot reach edges, order or waves (#2480)", () => {
820
+ const vpc = ent("aws");
821
+ // An output built from an intrinsic holds real AttrRefs inside it, which
822
+ // is what carried the walk into `addEdge(undefined, "aws")`.
823
+ // Built from an INTRINSIC holding an AttrRef, which is the shape the two
824
+ // reproducing examples have (`Fn::Sub`/`Ref` over a resource attribute).
825
+ // An output built from a bare AttrRef stores only a WeakRef and carries
826
+ // the walk nowhere, so it does not reproduce this.
827
+ const intrinsic: Intrinsic = {
828
+ [INTRINSIC_MARKER]: true as const,
829
+ ref: new AttrRef(vpc, "id"),
830
+ toJSON: () => ({ "Fn::Sub": "x" }),
831
+ } as Intrinsic;
832
+ const out = output(intrinsic, "vpcId");
833
+
834
+ const g = computeStackGraph(new Map([["vpc", vpc], ["vpcId", out as unknown as Declarable]]), ["aws"]);
835
+
836
+ expect(g.nodes).toEqual(["aws"]);
837
+ expect(g.edges).toEqual([]);
838
+ expect(g.order).toEqual(["aws"]);
839
+ expect(g.waves).toEqual([["aws"]]);
840
+ rosterHolds(g);
841
+ });
842
+
843
+ test("the roster invariant holds for an ordinary cross-lexicon graph too", () => {
844
+ const vpc = ent("aws");
845
+ const svc = ent("k8s", { vpcId: new AttrRef(vpc, "id") });
846
+ rosterHolds(computeStackGraph(new Map([["vpc", vpc], ["svc", svc]]), ["aws", "k8s"]));
847
+ });
848
+
796
849
  test("infers a consumer→producer edge from a cross-lexicon AttrRef", () => {
797
850
  const vpc = ent("aws");
798
851
  const svc = ent("k8s", { vpcId: new AttrRef(vpc, "id") });
package/src/build.ts CHANGED
@@ -65,6 +65,30 @@ export interface StackGraph {
65
65
  * referencing an attribute of a resource in lexicon B ⇒ A depends on B). Returns
66
66
  * the edge set plus a topological order, parallel-safe waves, and any cycles.
67
67
  */
68
+ /**
69
+ * Is this entity a member of a lexicon stack?
70
+ *
71
+ * Four kinds are not, and every one of them is serializer-neutral: a
72
+ * `LexiconOutput` is collected separately, a secret provenance declaration
73
+ * (#1828) and a plan scenario (#1292) are data lint and the CLI read off the
74
+ * entity map, and an Op (#2118) builds to its own `op.json` rather than
75
+ * through a lexicon serializer.
76
+ *
77
+ * {@link partitionByLexicon} has skipped all four since each landed, and
78
+ * {@link computeStackGraph}'s walk did not — so an entity with no `lexicon`
79
+ * (a `LexiconOutput`) entered `edges`, `order` and `waves` as `undefined`
80
+ * while `nodes`, built from the partition, could never contain it (#2480).
81
+ * One predicate for both sites, so they cannot drift apart again.
82
+ */
83
+ export function isStackMember(entity: Declarable): boolean {
84
+ return (
85
+ !isLexiconOutput(entity) &&
86
+ !isSecretDeclaration(entity) &&
87
+ !isScenario(entity) &&
88
+ !isOpEntity(entity)
89
+ );
90
+ }
91
+
68
92
  export function computeStackGraph(
69
93
  entities: Map<string, Declarable>,
70
94
  lexiconNames: string[],
@@ -72,6 +96,10 @@ export function computeStackGraph(
72
96
  const edges: Array<{ from: string; to: string }> = [];
73
97
  const edgeSet = new Set<string>();
74
98
  const addEdge = (from: string, to: string): void => {
99
+ // A structural guard, not a defensive one: `nodes` comes from the
100
+ // partition, so an endpoint the partition would have dropped can never be
101
+ // a legitimate member of this graph (#2480).
102
+ if (typeof from !== "string" || typeof to !== "string") return;
75
103
  if (from === to) return;
76
104
  const key = `${from}\0${to}`;
77
105
  if (edgeSet.has(key)) return;
@@ -104,6 +132,9 @@ export function computeStackGraph(
104
132
  };
105
133
 
106
134
  for (const [, entity] of entities) {
135
+ // #2480: the same four kinds `partitionByLexicon` drops. Walking one as a
136
+ // consumer takes its absent `lexicon` as the edge's `from`.
137
+ if (!isStackMember(entity)) continue;
107
138
  const consumer = entity.lexicon;
108
139
  const visited = new Set<unknown>();
109
140
  for (const val of Object.values(entity as unknown as Record<string, unknown>)) {
@@ -117,10 +148,10 @@ export function computeStackGraph(
117
148
  // Dependency map: node → producers it depends on.
118
149
  const nodes = lexiconNames.length
119
150
  ? [...lexiconNames]
120
- : // Secret provenance declarations (#1828) and plan scenarios (#1292) never
121
- // form a stack — their pseudo-lexicon has no serializer and must not
122
- // appear in the manifest.
123
- [...new Set([...entities.values()].filter((e) => !isSecretDeclaration(e) && !isScenario(e)).map((e) => e.lexicon))];
151
+ : // The same predicate, rather than the partial copy of it this branch
152
+ // carried: it filtered secrets and scenarios but not outputs or Ops, so
153
+ // it could mint a node the partition would have dropped (#2480).
154
+ [...new Set([...entities.values()].filter(isStackMember).map((e) => e.lexicon))];
124
155
  const deps = new Map<string, Set<string>>();
125
156
  for (const n of nodes) deps.set(n, new Set());
126
157
  for (const { from, to } of edges) {
@@ -325,23 +356,13 @@ export function partitionByLexicon(
325
356
  const partitions = new Map<string, Map<string, Declarable>>();
326
357
 
327
358
  for (const [name, entity] of entities) {
328
- // LexiconOutput instances are collected separately; skip them here
329
- if (isLexiconOutput(entity)) continue;
330
- // Secret provenance declarations (#1828) are serializer-neutral: data
331
- // that lint and lexicons read from the entity map, never output. Keeping
332
- // them out of every partition means no serializer sees them and no
333
- // "No serializer found" warning fires for their pseudo-lexicon.
334
- if (isSecretDeclaration(entity)) continue;
335
- // Plan scenarios (#1292) are serializer-neutral the same way: a checkable
336
- // expectation the CLI reads off the entity map, never output.
337
- if (isScenario(entity)) continue;
338
- // Op declarations (#2118) too. An Op's build output is its `op.json` IR,
339
- // and core writes that itself from the entity map (`./cli/commands/build.ts`)
340
- // rather than routing it through a lexicon serializer — the Op model, the
341
- // executor and the IR are all core's, and no lexicon renders an Op entity
342
- // into its own manifest. Keeping them out of every partition means no
343
- // "No serializer found" warning fires for core's own `chant` lexicon.
344
- if (isOpEntity(entity)) continue;
359
+ // The four serializer-neutral kinds, each explained on {@link isStackMember}:
360
+ // a LexiconOutput is collected separately, and a secret declaration
361
+ // (#1828), a plan scenario (#1292) and an Op (#2118) are read off the
362
+ // entity map rather than routed through a lexicon serializer. Keeping them
363
+ // out of every partition is what stops a "No serializer found" warning
364
+ // firing for a pseudo-lexicon.
365
+ if (!isStackMember(entity)) continue;
345
366
  const lexicon = entity.lexicon;
346
367
  if (!partitions.has(lexicon)) {
347
368
  partitions.set(lexicon, new Map());