@intentius/chant 0.41.0 → 0.41.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.
@@ -52,4 +52,28 @@ export interface CliBuildParamsResolution {
52
52
  * the same way #1022's fold decisions are.
53
53
  */
54
54
  export declare function resolveCliBuildParams(buildParamsConfig: BuildParamsConfig | undefined, args: CliBuildParamsArgs): CliBuildParamsResolution;
55
+ /**
56
+ * Resolve a command's declared build-time parameters, the same way `chant
57
+ * build` does, so a `build()`/`discover()` on the declared side sees the values
58
+ * the source will actually read.
59
+ *
60
+ * Every command that reads declared source needs this and only `chant build`
61
+ * had it. `chant graph` was fixed in #1483; the lifecycle family — `diff`,
62
+ * `snapshot`, `plan`, and `components status --live` — built the declared side
63
+ * on parameter *defaults* while the live side was whatever is really deployed.
64
+ *
65
+ * For a project whose parameters choose which resources *exist* that is not a
66
+ * near miss. `chant lifecycle diff dev --live` on kubemicrovm-ops returned
67
+ * byte-identical output at `KMV_TIER=minimal` and `KMV_TIER=prod-ha`, and
68
+ * reported the tier label itself as drift — `minimal → prod-ha`, declared
69
+ * against live — which is the comparison announcing it is against the wrong
70
+ * declaration.
71
+ *
72
+ * Returns `undefined` when resolution failed, having printed why: the caller
73
+ * should stop rather than compare against source that will not build.
74
+ */
75
+ export declare function commandBuildParams(buildParamsConfig: BuildParamsConfig | undefined, args: {
76
+ param?: string[];
77
+ paramsFile?: string;
78
+ }): Promise<BuildParamProvenance[] | undefined>;
55
79
  //# sourceMappingURL=build-params-cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-params-cli.d.ts","sourceRoot":"","sources":["../../src/cli/build-params-cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAG1D;;;;;;;;;;;;;;;GAeG;AAEH,4SAA4S;AAC5S,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAQtF;AAED,qOAAqO;AACrO,MAAM,WAAW,kBAAkB;IACjC,mNAAmN;IACnN,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,iHAAiH;IACjH,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,4FAA4F;IAC5F,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,EAChD,IAAI,EAAE,kBAAkB,GACvB,wBAAwB,CAkC1B"}
1
+ {"version":3,"file":"build-params-cli.d.ts","sourceRoot":"","sources":["../../src/cli/build-params-cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAG1D;;;;;;;;;;;;;;;GAeG;AAEH,4SAA4S;AAC5S,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAQtF;AAED,qOAAqO;AACrO,MAAM,WAAW,kBAAkB;IACjC,mNAAmN;IACnN,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,iHAAiH;IACjH,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,4FAA4F;IAC5F,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,EAChD,IAAI,EAAE,kBAAkB,GACvB,wBAAwB,CAkC1B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,kBAAkB,CACtC,iBAAiB,EAAE,iBAAiB,GAAG,SAAS,EAChD,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,oBAAoB,EAAE,GAAG,SAAS,CAAC,CAU7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/components.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,4BAA4B,CAAC;AAEnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ErF;AA4DD;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;6EAQ6E;AAC7E,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAiBzD;AAqDD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAqR9E;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/E"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/components.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,4BAA4B,CAAC;AAGnE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4ErF;AA4DD;;;;;;;;;;;;;;;GAeG;AACH;;;;;;;;6EAQ6E;AAC7E,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,EAAE,CAMzD;AA4DD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA6R9E;AAED,kEAAkE;AAClE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/E"}
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqDlD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoH/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C3E;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAuJ3E;AAoiBD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAoJ3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqDlD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwH/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C3E;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2J3E;AAoiBD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA0J3E;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Deploy units — which live unit(s) a component's composition targets (#1495).
3
+ *
4
+ * `chant components status --live` (and `chant graph --live`) need to know
5
+ * what a component deployed in order to observe it. That answer used to be a
6
+ * string literal in the walk — `kind === "cfn-deploy"` — which made the whole
7
+ * chain CloudFormation-shaped: a component whose deploy phase is a server-side
8
+ * apply or a Helm upgrade contributed nothing and was skipped, not
9
+ * "unobserved", simply absent from the result.
10
+ *
11
+ * This module is the seam that fixes the walk (piece 1 of #1495's
12
+ * decomposition): a registry mapping a deploy-family step `kind` to the field
13
+ * naming its unit and the lexicon whose `describeStackStatus` can observe it.
14
+ * The registry is data, not a rule about what steps look like — a kind not
15
+ * listed here contributes no unit, exactly as before.
16
+ */
17
+ import type { Phase } from "./component.js";
18
+ /** A deploy-family step kind that names a live unit, and who observes it. */
19
+ export interface DeployUnitRule {
20
+ /** The step `kind` (the capability's registered name). */
21
+ kind: string;
22
+ /** The step field carrying the unit's name (`stack`, `release`). */
23
+ field: string;
24
+ /** The lexicon whose `describeStackStatus` reads this kind of unit. */
25
+ lexicon: string;
26
+ }
27
+ /**
28
+ * The deploy-family kinds that name a unit. cfn-deploy's unit is its stack;
29
+ * kubectl-apply's is the ownership stack its labels carry (#1495 piece 2);
30
+ * helm-upgrade's is its release (#1495 piece 4). Listing a kind here is safe
31
+ * before its lexicon implements `describeStackStatus` — units whose lexicon
32
+ * has no observer are skipped by the caller, the same absent-observer path as
33
+ * before.
34
+ */
35
+ export declare const DEPLOY_UNIT_RULES: readonly DeployUnitRule[];
36
+ /** One resolved unit a component's deploy composition targets. */
37
+ export interface DeployUnit {
38
+ /** The unit's name — a stack, a release. */
39
+ unit: string;
40
+ /** The lexicon that observes this kind of unit. */
41
+ lexicon: string;
42
+ }
43
+ /**
44
+ * Every distinct deploy unit a component's phases target, in declaration
45
+ * order. A step may itself be a nested `Phase`, so the walk recurses; a
46
+ * resolved component carries the unit as a concrete string. Pure.
47
+ */
48
+ export declare function deployUnits(deploy: Phase[]): DeployUnit[];
49
+ //# sourceMappingURL=deploy-units.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy-units.d.ts","sourceRoot":"","sources":["../../src/components/deploy-units.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAItD,CAAC;AAEF,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,UAAU,EAAE,CA0BzD"}
package/dist/config.d.ts CHANGED
@@ -350,6 +350,16 @@ export declare function loadChantConfig(dir: string): Promise<ResolvedConfig>;
350
350
  * same walk `chant lint`/`chant graph` already used ({@link findProjectConfig},
351
351
  * shared with `./lint/config.ts`'s `findProjectRoot`) — one config-discovery
352
352
  * contract for the whole CLI.
353
+ *
354
+ * chant #1502 — a lint-scoping fragment does not end the walk. The convention
355
+ * of a `src/chant.config.json` holding only `extends`/`rules` (cc-aws-canonical
356
+ * and most of examples/) sits BETWEEN the build directory and the real
357
+ * `chant.config.ts`, and stopping there re-introduced the exact silent
358
+ * fallback this walk exists to prevent: `chant build src` resolved the
359
+ * fragment, found no `ownership`, and built unstamped manifests that every
360
+ * owned-scoped live read then withheld. A fragment is skipped, not merged —
361
+ * lint resolution keeps its own nearest-wins walk untouched, and a JSON
362
+ * config declaring any project-level key still wins where it stands.
353
363
  */
354
364
  export declare function loadChantConfigUpward(startDir: string): Promise<ResolvedConfig>;
355
365
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD;;;;;;;;;GASG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElF,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,sBAAsB,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAEzG;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,sBAAsB,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxH;AAaD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoDd,CAAC;AAEjB;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAExC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,0EAA0E;QAC1E,IAAI,EAAE,MAAM,CAAC;QACb,8EAA8E;QAC9E,GAAG,EAAE,MAAM,CAAC;QACZ;mFAC2E;QAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IAEH,8DAA8D;IAC9D,IAAI,CAAC,EAAE,UAAU,CAAC;IAElB;;;;;OAKG;IACH,SAAS,CAAC,EAAE;QACV,2EAA2E;QAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,qCAAqC;QACrC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,kEAAkE;QAClE,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IAEF;;OAEG;IACH,KAAK,CAAC,EAAE;QACN;;;;;;;WAOG;QACH,IAAI,CAAC,EAAE,OAAO,CAAC;QAEf;;;;;;;WAOG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IAEF;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE;QACR,8KAA8K;QAC9K,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IAEF;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QACL,+OAA+O;QAC/O,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,4cAA4c;QAC5c,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE;QACR,wJAAwJ;QACxJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,sRAAsR;QACtR,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,2IAA2I;QAC3I,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,0IAA0I;QAC1I,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,4UAA4U;QAC5U,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IAEF;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE;QACX,kEAAkE;QAClE,YAAY,CAAC,EAAE,QAAQ,CAAC;QACxB,2EAA2E;QAC3E,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,gEAAgE;QAChE,YAAY,CAAC,EAAE,QAAQ,CAAC;QACxB,wEAAwE;QACxE,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,0FAA0F;QAC1F,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,+EAA+E;QAC/E,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC;QAChD,yKAAyK;QACzK,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;QAC5B,kHAAkH;QAClH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,+BAA+B;IAC/B,MAAM,EAAE,WAAW,CAAC;IAEpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAgB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkB1E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAGrF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAIvF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAG1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAIlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAGrF;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,CAE1F;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC,gJAAgJ;IAChJ,OAAO,EAAE,OAAO,CAAC;IACjB,qIAAqI;IACrI,GAAG,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtB,6RAA6R;IAC7R,sBAAsB,EAAE;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,uBAAuB,CAanF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAW1E"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.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.41.0",
3
+ "version": "0.41.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",
@@ -105,3 +105,38 @@ export function resolveCliBuildParams(
105
105
 
106
106
  return { success: true, provenance: resolution.provenance, errors: [] };
107
107
  }
108
+
109
+ /**
110
+ * Resolve a command's declared build-time parameters, the same way `chant
111
+ * build` does, so a `build()`/`discover()` on the declared side sees the values
112
+ * the source will actually read.
113
+ *
114
+ * Every command that reads declared source needs this and only `chant build`
115
+ * had it. `chant graph` was fixed in #1483; the lifecycle family — `diff`,
116
+ * `snapshot`, `plan`, and `components status --live` — built the declared side
117
+ * on parameter *defaults* while the live side was whatever is really deployed.
118
+ *
119
+ * For a project whose parameters choose which resources *exist* that is not a
120
+ * near miss. `chant lifecycle diff dev --live` on kubemicrovm-ops returned
121
+ * byte-identical output at `KMV_TIER=minimal` and `KMV_TIER=prod-ha`, and
122
+ * reported the tier label itself as drift — `minimal → prod-ha`, declared
123
+ * against live — which is the comparison announcing it is against the wrong
124
+ * declaration.
125
+ *
126
+ * Returns `undefined` when resolution failed, having printed why: the caller
127
+ * should stop rather than compare against source that will not build.
128
+ */
129
+ export async function commandBuildParams(
130
+ buildParamsConfig: BuildParamsConfig | undefined,
131
+ args: { param?: string[]; paramsFile?: string },
132
+ ): Promise<BuildParamProvenance[] | undefined> {
133
+ const resolution = resolveCliBuildParams(buildParamsConfig, {
134
+ cli: parseParamFlags(args.param),
135
+ paramsFile: args.paramsFile,
136
+ });
137
+ if (!resolution.success) {
138
+ for (const message of resolution.errors) console.error(message);
139
+ return undefined;
140
+ }
141
+ return resolution.provenance;
142
+ }
@@ -35,6 +35,7 @@ import {
35
35
  InvalidReleaseRecordError,
36
36
  } from "../../lifecycle/release-ledger";
37
37
  import { reconcileStatus, liveEvidenceFromChangeSet, compareAcrossEnvironments, mergeLiveEvidence, type LiveComponentEvidence } from "../../lifecycle/status";
38
+ import { commandBuildParams } from "../build-params-cli";
38
39
  import { buildChangeSet } from "../../lifecycle/change-set";
39
40
  import { buildLedgerEntries, componentBomSummary, type BuildLedgerEntry } from "../../lifecycle/build-ledger";
40
41
  import { findBuildManifestByArtifactDigest } from "../../lifecycle/build-ledger-store";
@@ -48,6 +49,7 @@ import type { CommandContext } from "../registry";
48
49
  import type { LexiconPlugin } from "../../lexicon";
49
50
  import { normalizeObservation, mergeObservations, unobservedAll, type NormalizedObservation } from "../../observation";
50
51
  import type { Phase, Component } from "../../components/component";
52
+ import { deployUnits } from "../../components/deploy-units";
51
53
 
52
54
  /**
53
55
  * chant components release <env> --component <name> --digest <sha256:...>
@@ -228,22 +230,11 @@ interface StatusJsonRow {
228
230
  * observe on a multi-stack, per-component project (`describeResources`'s
229
231
  * single-stack-named-after-the-environment convention never matches one). */
230
232
  export function cfnDeployStacks(deploy: Phase[]): string[] {
231
- const stacks = new Set<string>();
232
- const walkSteps = (steps: Phase["steps"]): void => {
233
- for (const step of steps) {
234
- // A step may itself be a nested Phase (it carries its own `steps`). Step is
235
- // open-typed (capability inputs), so discriminate structurally on `steps`.
236
- const nested = (step as { steps?: unknown }).steps;
237
- if (Array.isArray(nested)) {
238
- walkSteps(nested as Phase["steps"]);
239
- continue;
240
- }
241
- const s = step as { kind?: string; stack?: unknown };
242
- if (s.kind === "cfn-deploy" && typeof s.stack === "string") stacks.add(s.stack);
243
- }
244
- };
245
- for (const phase of deploy) walkSteps(phase.steps);
246
- return [...stacks];
233
+ // The CloudFormation slice of the generic deploy-unit walk (#1495) — kept
234
+ // because `chant graph --live`'s stack enrichment is genuinely CFN-shaped.
235
+ return deployUnits(deploy)
236
+ .filter((u) => u.lexicon === "aws")
237
+ .map((u) => u.unit);
247
238
  }
248
239
 
249
240
  /**
@@ -258,15 +249,22 @@ export function cfnDeployStacks(deploy: Phase[]): string[] {
258
249
  */
259
250
  async function observeComponentStacks(
260
251
  components: Map<string, { component: Component }>,
261
- observer: LexiconPlugin,
252
+ plugins: LexiconPlugin[],
262
253
  environment: string,
263
254
  ): Promise<Map<string, LiveComponentEvidence>> {
255
+ // Observer per unit's own lexicon (#1495): a component's kubectl-apply unit
256
+ // is read by the k8s lexicon, its cfn-deploy stack by aws. A unit whose
257
+ // lexicon ships no describeStackStatus is skipped — the same absent-observer
258
+ // degradation as before, per unit instead of per project.
259
+ const observerFor = (lexicon: string) =>
260
+ plugins.find((p) => p.name === lexicon && p.describeStackStatus);
264
261
  const evidence = new Map<string, LiveComponentEvidence>();
265
262
  for (const [name, { component }] of components) {
266
- const stacks = cfnDeployStacks(component.deploy);
263
+ const units = deployUnits(component.deploy).filter((u) => observerFor(u.lexicon));
264
+ const stacks = units.map((u) => u.unit);
267
265
  if (stacks.length === 0) continue;
268
266
  const observed = await Promise.all(
269
- stacks.map((stack) => observer.describeStackStatus!({ environment, stack }).catch(() => null)),
267
+ units.map((u) => observerFor(u.lexicon)!.describeStackStatus!({ environment, stack: u.unit }).catch(() => null)),
270
268
  );
271
269
  const determinate = observed.filter((o): o is NonNullable<typeof o> => o !== null);
272
270
  if (determinate.length === 0) {
@@ -355,7 +353,15 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
355
353
  if (endpointResult.notice) console.error(formatWarning({ message: endpointResult.notice }));
356
354
  try {
357
355
  const targetSerializers = serializers;
358
- const buildResult = await build(resolve(args.src ?? config.sourceDir ?? "."), targetSerializers);
356
+ // With this invocation's parameters (#1483). Built on defaults, the
357
+ // declared half of the comparison is a different estate from the one
358
+ // deployed, and every resource the real parameter declares reads as
359
+ // absent.
360
+ const statusParams = await commandBuildParams(config.buildParams, args);
361
+ if (!statusParams) return 1;
362
+ const buildResult = await build(resolve(args.src ?? config.sourceDir ?? "."), targetSerializers, undefined, {
363
+ buildParams: statusParams,
364
+ });
359
365
  // Which deployed stack(s) to read the change set from (behold#100).
360
366
  //
361
367
  // `describeResources` defaults to the single-stack convention — the
@@ -431,9 +437,9 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
431
437
  // invisible to the entity-keyed, single-stack `describeResources` above —
432
438
  // observe each component's own cfn-deploy stack directly and overlay it as
433
439
  // the authoritative presence signal (#57).
434
- const stackObserver = plugins.find((p) => p.describeStackStatus);
435
- if (stackObserver) {
436
- const stackEvidence = await observeComponentStacks(discovery.components, stackObserver, environment);
440
+ const anyObserver = plugins.some((p) => p.describeStackStatus);
441
+ if (anyObserver) {
442
+ const stackEvidence = await observeComponentStacks(discovery.components, plugins, environment);
437
443
  liveEvidence = mergeLiveEvidence(liveEvidence, stackEvidence);
438
444
  }
439
445
  } finally {
@@ -85,8 +85,15 @@ vi.mock("../../config", async () => {
85
85
  loadChantConfigUpward: (...a: unknown[]) => loadChantConfigUpwardMock(...a),
86
86
  };
87
87
  });
88
+ const buildMock = vi.fn();
88
89
  vi.mock("../../build", () => ({
89
- build: () => Promise.resolve({ errors: [] }),
90
+ // Forwards its arguments so a test can assert what the live path passed —
91
+ // `buildParams` in particular (#1483), the same reason `discover` above
92
+ // forwards for #1359.
93
+ build: (...a: unknown[]) => {
94
+ buildMock(...a);
95
+ return Promise.resolve({ errors: [] });
96
+ },
90
97
  partitionByLexicon: () => ({}),
91
98
  computeStackGraph: () => ({}),
92
99
  }));
@@ -136,6 +143,7 @@ describe("runGraph", () => {
136
143
  // Default: no components — the single-stack --live path most tests exercise.
137
144
  discoverComponentsMock.mockResolvedValue({ components: new Map(), sourceFiles: [], errors: [] });
138
145
  observeMock.mockReset();
146
+ buildMock.mockReset();
139
147
  loadPluginsMock.mockReset();
140
148
  resolveLexMock.mockReset();
141
149
  loadChantConfigMock.mockReset();
@@ -480,6 +488,48 @@ describe("runGraph", () => {
480
488
  });
481
489
 
482
490
  describe("live graph (--live)", () => {
491
+ // #1483 — the live path builds the source to learn which entities to
492
+ // observe, and did so on default parameters while the declared overlay
493
+ // resolved the caller's. For a project whose parameters choose *which
494
+ // resources exist* — a tier, a size, a profile — that observed one estate
495
+ // and compared it against another, so every resource the real parameter
496
+ // declares read as absent and every resource the default declares read as
497
+ // pending. A confidently wrong overlay, not an empty one.
498
+ describe("build-time parameters reach the observed build (#1483)", () => {
499
+ const liveWithTier = async (param?: string[]) => {
500
+ loadChantConfigUpwardMock.mockResolvedValue({
501
+ config: { buildParams: { tier: { type: "string", enum: ["light", "prod"], default: "light" } } },
502
+ });
503
+ resolveLexMock.mockResolvedValue(["aws"]);
504
+ loadPluginsMock.mockResolvedValue([
505
+ { name: "aws", serializer: {}, emulator: awsEmulatorStub, describeResources: () => Promise.resolve({}) },
506
+ ]);
507
+ observeMock.mockResolvedValue({ observations: [], errors: [], warnings: [] });
508
+ return runGraph({
509
+ args: makeArgs({ format: "ir", live: true, env: "prod", ...(param ? { param } : {}) }),
510
+ plugins: [],
511
+ serializers: [],
512
+ });
513
+ };
514
+
515
+ const paramsOf = (call: unknown[]) =>
516
+ (call[3] as { buildParams?: Array<{ name: string; value: unknown }> } | undefined)?.buildParams;
517
+
518
+ test("--param reaches the build the observation is scoped from", async () => {
519
+ expect(await liveWithTier(["tier=prod"])).toBe(0);
520
+ expect(paramsOf(buildMock.mock.calls[0]!)).toContainEqual(
521
+ expect.objectContaining({ name: "tier", value: "prod" }),
522
+ );
523
+ });
524
+
525
+ test("a declared default reaches it too, rather than nothing at all", async () => {
526
+ expect(await liveWithTier()).toBe(0);
527
+ expect(paramsOf(buildMock.mock.calls[0]!)).toContainEqual(
528
+ expect.objectContaining({ name: "tier", value: "light" }),
529
+ );
530
+ });
531
+ });
532
+
483
533
  // Regression: `graph` is not `requiresPlugins`, so `ctx.plugins` is empty. The
484
534
  // live path must load the project's plugins itself — otherwise it wrongly
485
535
  // reports "No lexicons implement describeResources" and observes nothing.
@@ -144,7 +144,22 @@ async function runGraphLive(
144
144
 
145
145
  // Build to get each lexicon's entity names + output (the scope
146
146
  // describeResources needs), mirroring `chant lifecycle snapshot`.
147
- const buildResult = await build(resolve(args.src ?? config.sourceDir ?? "."), plugins.map((p) => p.serializer));
147
+ //
148
+ // With the same build params the source graph resolves (#1483). Without
149
+ // them this build ran on defaults while the declared overlay below ran on
150
+ // the caller's, so a project whose parameters choose *which resources
151
+ // exist* — a tier, a size, a profile — observed one estate and compared it
152
+ // against another. Every resource the real parameter declares read as
153
+ // absent and every resource the default declares read as pending, which is
154
+ // a confidently wrong overlay rather than an empty one.
155
+ const liveBuildParams = await graphBuildParams(ctx, projectPath);
156
+ if (!liveBuildParams) return 1;
157
+ const buildResult = await build(
158
+ resolve(args.src ?? config.sourceDir ?? "."),
159
+ plugins.map((p) => p.serializer),
160
+ undefined,
161
+ { buildParams: liveBuildParams },
162
+ );
148
163
  if (buildResult.errors.length > 0) {
149
164
  console.error(formatError({ message: "Build failed — fix errors before graphing live state" }));
150
165
  return 1;
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { commandBuildParams } from "../build-params-cli";
2
3
  import { build } from "../../build";
3
4
  import { takeSnapshot } from "../../lifecycle/snapshot";
4
5
  import { readSnapshot, readSnapshotAt, readEnvironmentSnapshots, listSnapshots, fetchLifecycle, pushLifecycle, snapshotStorageKey, StaleLifecycleBranchError } from "../../lifecycle/git";
@@ -101,6 +102,10 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
101
102
  // Validate environment against config
102
103
  const projectPath = resolve(".");
103
104
  const { config } = await loadChantConfig(projectPath);
105
+ // This invocation's parameters, so the declared side of the comparison is the estate
106
+ // the caller asked for rather than the parameter defaults (#1483).
107
+ const declaredParams = await commandBuildParams(config.buildParams, args);
108
+ if (!declaredParams) return 1;
104
109
  const declaredEnvNames = environmentNames(config.environments);
105
110
  if (declaredEnvNames && !declaredEnvNames.includes(environment)) {
106
111
  console.error(formatError({
@@ -146,7 +151,7 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
146
151
  const built: Array<{ target: (typeof targets)[number]; buildResult: Awaited<ReturnType<typeof build>> }> = [];
147
152
  for (const target of targets) {
148
153
  const label = target.stack ? `stack "${target.stack}"` : "project";
149
- const buildResult = await build(target.root, targetSerializers);
154
+ const buildResult = await build(target.root, targetSerializers, undefined, { buildParams: declaredParams });
150
155
  if (buildResult.errors.length > 0) {
151
156
  console.error(formatError({ message: `Build failed for ${label} — fix errors before taking a snapshot` }));
152
157
  anyHardError = true;
@@ -326,6 +331,10 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
326
331
 
327
332
  // Fetch previous snapshots once (all stacks share the orphan branch).
328
333
  const { config } = await loadChantConfig(resolve("."));
334
+ // This invocation's parameters, so the declared side of the comparison is the estate
335
+ // the caller asked for rather than the parameter defaults (#1483).
336
+ const declaredParams = await commandBuildParams(config.buildParams, args);
337
+ if (!declaredParams) return 1;
329
338
  await fetchLifecycle();
330
339
 
331
340
  // One target per stack (single-stack projects: exactly one), each built from
@@ -355,7 +364,7 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
355
364
 
356
365
  try {
357
366
  for (const target of targets) {
358
- const buildResult = await build(target.root, targetSerializers);
367
+ const buildResult = await build(target.root, targetSerializers, undefined, { buildParams: declaredParams });
359
368
  if (buildResult.errors.length > 0) {
360
369
  const label = target.stack ? `stack "${target.stack}"` : "project";
361
370
  console.error(formatError({ message: `Build failed for ${label} — fix errors before diffing` }));
@@ -1021,7 +1030,13 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
1021
1030
  : serializers;
1022
1031
 
1023
1032
  const { config } = await loadChantConfig(resolve("."));
1024
- const buildResult = await build(resolveBuildRoot(args, config), targetSerializers);
1033
+ // This invocation's parameters, so the declared side of the comparison is the estate
1034
+ // the caller asked for rather than the parameter defaults (#1483).
1035
+ const declaredParams = await commandBuildParams(config.buildParams, args);
1036
+ if (!declaredParams) return 1;
1037
+ const buildResult = await build(resolveBuildRoot(args, config), targetSerializers, undefined, {
1038
+ buildParams: declaredParams,
1039
+ });
1025
1040
  if (buildResult.errors.length > 0) {
1026
1041
  console.error(formatError({ message: "Build failed — fix errors before planning" }));
1027
1042
  return 1;
@@ -0,0 +1,42 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { deployUnits } from "./deploy-units";
3
+ import type { Phase } from "./component";
4
+
5
+ const phase = (name: string, steps: Phase["steps"]): Phase => ({ phase: name, steps });
6
+
7
+ describe("deployUnits (#1495 piece 1)", () => {
8
+ test("resolves a cfn-deploy step to its stack, keyed to the aws observer", () => {
9
+ const deploy = [phase("Apply", [{ kind: "cfn-deploy", stack: "cc-canonical", template: "t.json" }])];
10
+ expect(deployUnits(deploy)).toEqual([{ unit: "cc-canonical", lexicon: "aws" }]);
11
+ });
12
+
13
+ test("resolves kubectl-apply and helm-upgrade units once their steps exist", () => {
14
+ const deploy = [
15
+ phase("Apply", [
16
+ { kind: "kubectl-apply", stack: "kubemicrovm-ops" },
17
+ { kind: "helm-upgrade", release: "operator" },
18
+ ]),
19
+ ];
20
+ expect(deployUnits(deploy)).toEqual([
21
+ { unit: "kubemicrovm-ops", lexicon: "k8s" },
22
+ { unit: "operator", lexicon: "helm" },
23
+ ]);
24
+ });
25
+
26
+ test("walks nested phases, dedupes per lexicon, and skips unitless steps", () => {
27
+ const deploy = [
28
+ phase("Outer", [
29
+ phase("Inner", [{ kind: "cfn-deploy", stack: "web" }]) as never,
30
+ { kind: "cfn-deploy", stack: "web" },
31
+ { kind: "shell", cmd: "echo", reason: "no capability yet" },
32
+ { kind: "cfn-deploy" }, // no stack named — contributes nothing
33
+ ]),
34
+ ];
35
+ expect(deployUnits(deploy)).toEqual([{ unit: "web", lexicon: "aws" }]);
36
+ });
37
+
38
+ test("an unlisted kind contributes no unit — the registry is the rule", () => {
39
+ const deploy = [phase("Apply", [{ kind: "gcloud-deploy", stack: "x" }])];
40
+ expect(deployUnits(deploy)).toEqual([]);
41
+ });
42
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Deploy units — which live unit(s) a component's composition targets (#1495).
3
+ *
4
+ * `chant components status --live` (and `chant graph --live`) need to know
5
+ * what a component deployed in order to observe it. That answer used to be a
6
+ * string literal in the walk — `kind === "cfn-deploy"` — which made the whole
7
+ * chain CloudFormation-shaped: a component whose deploy phase is a server-side
8
+ * apply or a Helm upgrade contributed nothing and was skipped, not
9
+ * "unobserved", simply absent from the result.
10
+ *
11
+ * This module is the seam that fixes the walk (piece 1 of #1495's
12
+ * decomposition): a registry mapping a deploy-family step `kind` to the field
13
+ * naming its unit and the lexicon whose `describeStackStatus` can observe it.
14
+ * The registry is data, not a rule about what steps look like — a kind not
15
+ * listed here contributes no unit, exactly as before.
16
+ */
17
+ import type { Phase } from "./component";
18
+
19
+ /** A deploy-family step kind that names a live unit, and who observes it. */
20
+ export interface DeployUnitRule {
21
+ /** The step `kind` (the capability's registered name). */
22
+ kind: string;
23
+ /** The step field carrying the unit's name (`stack`, `release`). */
24
+ field: string;
25
+ /** The lexicon whose `describeStackStatus` reads this kind of unit. */
26
+ lexicon: string;
27
+ }
28
+
29
+ /**
30
+ * The deploy-family kinds that name a unit. cfn-deploy's unit is its stack;
31
+ * kubectl-apply's is the ownership stack its labels carry (#1495 piece 2);
32
+ * helm-upgrade's is its release (#1495 piece 4). Listing a kind here is safe
33
+ * before its lexicon implements `describeStackStatus` — units whose lexicon
34
+ * has no observer are skipped by the caller, the same absent-observer path as
35
+ * before.
36
+ */
37
+ export const DEPLOY_UNIT_RULES: readonly DeployUnitRule[] = [
38
+ { kind: "cfn-deploy", field: "stack", lexicon: "aws" },
39
+ { kind: "kubectl-apply", field: "stack", lexicon: "k8s" },
40
+ { kind: "helm-upgrade", field: "release", lexicon: "helm" },
41
+ ];
42
+
43
+ /** One resolved unit a component's deploy composition targets. */
44
+ export interface DeployUnit {
45
+ /** The unit's name — a stack, a release. */
46
+ unit: string;
47
+ /** The lexicon that observes this kind of unit. */
48
+ lexicon: string;
49
+ }
50
+
51
+ /**
52
+ * Every distinct deploy unit a component's phases target, in declaration
53
+ * order. A step may itself be a nested `Phase`, so the walk recurses; a
54
+ * resolved component carries the unit as a concrete string. Pure.
55
+ */
56
+ export function deployUnits(deploy: Phase[]): DeployUnit[] {
57
+ const byKind = new Map(DEPLOY_UNIT_RULES.map((r) => [r.kind, r]));
58
+ const seen = new Set<string>();
59
+ const units: DeployUnit[] = [];
60
+ const walkSteps = (steps: Phase["steps"]): void => {
61
+ for (const step of steps) {
62
+ // A step may itself be a nested Phase (it carries its own `steps`). Step
63
+ // is open-typed (capability inputs), so discriminate structurally.
64
+ const nested = (step as { steps?: unknown }).steps;
65
+ if (Array.isArray(nested)) {
66
+ walkSteps(nested as Phase["steps"]);
67
+ continue;
68
+ }
69
+ const s = step as { kind?: string } & Record<string, unknown>;
70
+ const rule = s.kind ? byKind.get(s.kind) : undefined;
71
+ if (!rule) continue;
72
+ const unit = s[rule.field];
73
+ if (typeof unit !== "string" || unit.length === 0) continue;
74
+ const key = `${rule.lexicon}\0${unit}`;
75
+ if (seen.has(key)) continue;
76
+ seen.add(key);
77
+ units.push({ unit, lexicon: rule.lexicon });
78
+ }
79
+ };
80
+ for (const phase of deploy) walkSteps(phase.steps);
81
+ return units;
82
+ }
@@ -1,6 +1,7 @@
1
1
  import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
2
  import {
3
3
  loadChantConfig,
4
+ loadChantConfigUpward,
4
5
  DEFAULT_CHANT_CONFIG,
5
6
  resolveAutoReleaseDisabled,
6
7
  resolveFoldEnabled,
@@ -137,6 +138,58 @@ describe("loadChantConfig", () => {
137
138
  });
138
139
  });
139
140
 
141
+ // #1502 — the upward walk skips lint-scoping fragments. A `src/chant.config.json`
142
+ // holding only `extends`/`rules` (the examples/ convention) must not shadow the
143
+ // project config above it, or `chant build src` silently loses `ownership`/
144
+ // `buildParams` — the exact fallback #1117's walk exists to prevent.
145
+ describe("loadChantConfigUpward (#1502 — lint fragments do not end the walk)", () => {
146
+ const SRC = join(TEST_DIR, "src");
147
+
148
+ test("walks past a lint-only src/chant.config.json to the project config", async () => {
149
+ mkdirSync(SRC, { recursive: true });
150
+ writeFileSync(
151
+ join(SRC, "chant.config.json"),
152
+ JSON.stringify({ extends: ["@intentius/chant/lint/presets/strict"], rules: { COR001: "off" } }),
153
+ );
154
+ writeFileSync(
155
+ join(TEST_DIR, "chant.config.json"),
156
+ JSON.stringify({ ownership: { stack: "billing", env: "prod" } }),
157
+ );
158
+
159
+ const result = await loadChantConfigUpward(SRC);
160
+ expect(result.config.ownership).toEqual({ stack: "billing", env: "prod" });
161
+ expect(result.configPath).toBe(join(TEST_DIR, "chant.config.json"));
162
+ });
163
+
164
+ test("a src/chant.config.json declaring any project-level key still wins in place", async () => {
165
+ mkdirSync(SRC, { recursive: true });
166
+ writeFileSync(
167
+ join(SRC, "chant.config.json"),
168
+ JSON.stringify({ ownership: { stack: "nested" }, rules: { COR001: "off" } }),
169
+ );
170
+ writeFileSync(
171
+ join(TEST_DIR, "chant.config.json"),
172
+ JSON.stringify({ ownership: { stack: "root" } }),
173
+ );
174
+
175
+ const result = await loadChantConfigUpward(SRC);
176
+ expect(result.config.ownership?.stack).toBe("nested");
177
+ });
178
+
179
+ test("a fragment-only project resolves to the default config at the boundary", async () => {
180
+ mkdirSync(SRC, { recursive: true });
181
+ writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "boundary" }));
182
+ writeFileSync(
183
+ join(SRC, "chant.config.json"),
184
+ JSON.stringify({ extends: ["@intentius/chant/lint/presets/strict"] }),
185
+ );
186
+
187
+ const result = await loadChantConfigUpward(SRC);
188
+ expect(result.config).toEqual(DEFAULT_CHANT_CONFIG);
189
+ expect(result.configPath).toBeUndefined();
190
+ });
191
+ });
192
+
140
193
  // #1166 — `environments` accepts either a bare name (unchanged) or
141
194
  // `{ name, endpoint }`, so a declared environment can be self-sufficient for
142
195
  // `--live` reads without an ambient AWS_ENDPOINT_URL export.
package/src/config.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { existsSync } from "fs";
2
- import { join } from "path";
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
3
  import { z } from "zod";
4
4
  import type { LintConfig } from "./lint/config";
5
5
  import type { OwnershipMarker } from "./ownership";
@@ -384,12 +384,50 @@ export async function loadChantConfig(dir: string): Promise<ResolvedConfig> {
384
384
  * same walk `chant lint`/`chant graph` already used ({@link findProjectConfig},
385
385
  * shared with `./lint/config.ts`'s `findProjectRoot`) — one config-discovery
386
386
  * contract for the whole CLI.
387
+ *
388
+ * chant #1502 — a lint-scoping fragment does not end the walk. The convention
389
+ * of a `src/chant.config.json` holding only `extends`/`rules` (cc-aws-canonical
390
+ * and most of examples/) sits BETWEEN the build directory and the real
391
+ * `chant.config.ts`, and stopping there re-introduced the exact silent
392
+ * fallback this walk exists to prevent: `chant build src` resolved the
393
+ * fragment, found no `ownership`, and built unstamped manifests that every
394
+ * owned-scoped live read then withheld. A fragment is skipped, not merged —
395
+ * lint resolution keeps its own nearest-wins walk untouched, and a JSON
396
+ * config declaring any project-level key still wins where it stands.
387
397
  */
388
398
  export async function loadChantConfigUpward(startDir: string): Promise<ResolvedConfig> {
389
- const { dir } = findProjectConfig(startDir);
399
+ let { dir, configPath } = findProjectConfig(startDir);
400
+ while (configPath && isLintOnlyFragment(configPath)) {
401
+ const parent = dirname(dir);
402
+ if (parent === dir) break;
403
+ ({ dir, configPath } = findProjectConfig(parent));
404
+ }
390
405
  return loadChantConfig(dir);
391
406
  }
392
407
 
408
+ /**
409
+ * The top-level keys of `./lint/config.ts`'s `LintConfigSchema` (plus the
410
+ * `$schema` editor convention). A `chant.config.json` whose keys all come from
411
+ * this set is a lint-scoping fragment, not a project config — see
412
+ * {@link loadChantConfigUpward}. `chant.config.ts` is never a fragment: it is
413
+ * project-authored code, and inspecting it would mean evaluating it.
414
+ */
415
+ const LINT_FRAGMENT_KEYS = new Set(["$schema", "extends", "rules", "overrides", "plugins", "policies"]);
416
+
417
+ function isLintOnlyFragment(configPath: string): boolean {
418
+ if (!configPath.endsWith("chant.config.json")) return false;
419
+ try {
420
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as unknown;
421
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
422
+ const keys = Object.keys(parsed);
423
+ return keys.length > 0 && keys.every((k) => LINT_FRAGMENT_KEYS.has(k));
424
+ } catch {
425
+ // Unreadable/unparseable JSON: let loadChantConfig surface the real error
426
+ // in place rather than silently walking past it.
427
+ return false;
428
+ }
429
+ }
430
+
393
431
  /**
394
432
  * Resolve the ownership marker to stamp from project config, or undefined when
395
433
  * ownership marking is off (no `stack`, or `enabled: false`).