@intentius/chant 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +21 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/search.d.ts +30 -1
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +20 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts +23 -0
- package/dist/codegen/registry.d.ts.map +1 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/composite.d.ts +23 -4
- package/dist/composite.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +11 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/graph-declared.d.ts.map +1 -1
- package/dist/graph-ir.d.ts +17 -3
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-refs.d.ts +24 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-config.d.ts +61 -0
- package/dist/lexicon-config.d.ts.map +1 -0
- package/dist/lexicon.d.ts +19 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +11 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/identity.d.ts +52 -0
- package/dist/lifecycle/identity.d.ts.map +1 -0
- package/dist/lifecycle/observe.d.ts +5 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/types.d.ts +30 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/managed-fields.d.ts +11 -0
- package/dist/managed-fields.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +24 -0
- package/src/cli/commands/build.ts +10 -0
- package/src/cli/commands/check-lexicon.ts +20 -1
- package/src/cli/handlers/lifecycle.ts +10 -0
- package/src/cli/handlers/run-client.ts +3 -1
- package/src/cli/handlers/search-kind.test.ts +45 -0
- package/src/cli/handlers/search.ts +102 -4
- package/src/cli/main.ts +32 -10
- package/src/cli/param-flag-scope.test.ts +69 -0
- package/src/cli/plugins.test.ts +33 -1
- package/src/cli/plugins.ts +55 -0
- package/src/codegen/registry.test.ts +56 -0
- package/src/codegen/registry.ts +69 -0
- package/src/components/SPRAWL-VALIDATION.md +5 -5
- package/src/components/sandbox/driver.test.ts +27 -0
- package/src/components/sandbox/driver.ts +12 -0
- package/src/composite.ts +33 -4
- package/src/deep-observation.ts +11 -0
- package/src/discovery/sandbox/driver.test.ts +34 -0
- package/src/discovery/sandbox/driver.ts +19 -0
- package/src/graph-declared.test.ts +86 -0
- package/src/graph-declared.ts +14 -2
- package/src/graph-ir.ts +32 -8
- package/src/graph-refs.test.ts +56 -0
- package/src/graph-refs.ts +37 -1
- package/src/kubectl-context.ts +4 -1
- package/src/lexicon-config.test.ts +111 -0
- package/src/lexicon-config.ts +92 -0
- package/src/lexicon.ts +20 -0
- package/src/lifecycle/deep-diff.test.ts +48 -1
- package/src/lifecycle/deep-diff.ts +16 -0
- package/src/lifecycle/digest.test.ts +81 -0
- package/src/lifecycle/digest.ts +34 -3
- package/src/lifecycle/identity.test.ts +39 -0
- package/src/lifecycle/identity.ts +61 -0
- package/src/lifecycle/observe.test.ts +75 -1
- package/src/lifecycle/observe.ts +28 -2
- package/src/lifecycle/replay.test.ts +251 -0
- package/src/lifecycle/replay.ts +67 -19
- package/src/lifecycle/types.ts +26 -0
- package/src/managed-fields.test.ts +50 -0
- package/src/managed-fields.ts +25 -6
- package/src/meta/peer-deps.test.ts +111 -14
|
@@ -50,6 +50,17 @@ export interface PropertyDrift {
|
|
|
50
50
|
* accepted value there would lose the most useful column in the report.
|
|
51
51
|
*/
|
|
52
52
|
baseline?: unknown;
|
|
53
|
+
/**
|
|
54
|
+
* The field manager that owns this path live, where the substrate records one
|
|
55
|
+
* (#1189) — Kubernetes' `managedFields`, and nowhere else today.
|
|
56
|
+
*
|
|
57
|
+
* `kind` says a path is `undeclared` or `changed`; this says who did it.
|
|
58
|
+
* "Owned by `kubectl-client-side-apply`" and "owned by `hpa-controller`" are
|
|
59
|
+
* the same `kind` and mean opposite things: one is somebody bypassing the
|
|
60
|
+
* pipeline, the other is a controller doing its job. Absent on a substrate
|
|
61
|
+
* with no per-field ownership, which is every substrate but k8s.
|
|
62
|
+
*/
|
|
63
|
+
owner?: string;
|
|
53
64
|
}
|
|
54
65
|
/** Property-level drift for one declared entity. */
|
|
55
66
|
export interface DeepEntityDrift {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-diff.d.ts","sourceRoot":"","sources":["../../src/lifecycle/deep-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEjF;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEpE,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"deep-diff.d.ts","sourceRoot":"","sources":["../../src/lifecycle/deep-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEjF;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;AAEpE,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,qDAAqD;IACrD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,8EAA8E;IAC9E,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,0EAA0E;IAC1E,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,IAAI,EAAE,yBAAyB,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAmG7D;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/lifecycle/digest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AAIvE;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/lifecycle/digest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAC;AAIvE;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,GAAG,WAAW,CA4BxE;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,WAAW,GAAG,SAAS,GAChC,UAAU,CAiCZ"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What makes an unqualified resource one resource (#1416).
|
|
3
|
+
*
|
|
4
|
+
* Managed resources are stack-qualified (`${stack}::${id}`, #1162) and cannot
|
|
5
|
+
* collide. Everything else — dependencies (#1273) and ambient resources (#1278)
|
|
6
|
+
* — is keyed by physical id and merged across stacks, because the account's
|
|
7
|
+
* default VPC route table is one resource however many stacks route through it.
|
|
8
|
+
*
|
|
9
|
+
* That is right for a resource that really is account-level, and wrong for a
|
|
10
|
+
* regional one. A VPC, subnet or security group in `us-west-1` is a different
|
|
11
|
+
* resource from one in `us-east-1`, and merging them by id drops all but the
|
|
12
|
+
* first sighting.
|
|
13
|
+
*
|
|
14
|
+
* On AWS the difference is invisible: VPC, subnet and security-group ids are
|
|
15
|
+
* globally unique, so keying by id and keying by (region, id) give the same
|
|
16
|
+
* answer. It shows up against an emulator that reuses ids across regions
|
|
17
|
+
* (lex00/floci#21), where a three-region estate's nine default subnets merged
|
|
18
|
+
* to three and "how many subnets are empty" came back 2 instead of 8.
|
|
19
|
+
*
|
|
20
|
+
* The rule is deliberately not a table of regional kinds. A lexicon stamps a
|
|
21
|
+
* region on a resource when region is part of that resource's identity — the
|
|
22
|
+
* AWS lexicon does it in `stampRegion` for ambient resources and its own
|
|
23
|
+
* property reads — and this reads that stamp back. A genuinely account-level
|
|
24
|
+
* resource carries no region and keeps merging as it always did, which is why
|
|
25
|
+
* dependencies (never stamped) are untouched by this.
|
|
26
|
+
*/
|
|
27
|
+
import type { ResourceMetadata } from "../lexicon.js";
|
|
28
|
+
/**
|
|
29
|
+
* The region a resource records itself as being in, if it records one.
|
|
30
|
+
*
|
|
31
|
+
* Read from `attributes.region`, which is where a lexicon stamps it. Absent
|
|
32
|
+
* means the resource is account-level, or the lexicon has no region to give —
|
|
33
|
+
* both of which are read the same way, as "region is not part of this id".
|
|
34
|
+
*/
|
|
35
|
+
export declare function regionOf(meta: Pick<ResourceMetadata, "attributes">): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The key an unqualified resource merges under.
|
|
38
|
+
*
|
|
39
|
+
* `${region}::${id}` for a resource that carries a region, matching the shape
|
|
40
|
+
* stack qualification already uses, and the bare id for one that does not.
|
|
41
|
+
*
|
|
42
|
+
* Applied only where results from more than one region are actually merged —
|
|
43
|
+
* `collectAmbient` across stacks, `replaySnapshots` across recorded stacks.
|
|
44
|
+
* Recording one region's observation qualifies nothing, so a stored snapshot
|
|
45
|
+
* keeps the keys it has always had and the first snapshot taken after this
|
|
46
|
+
* lands does not diff as "every ambient resource replaced".
|
|
47
|
+
*
|
|
48
|
+
* Idempotent, so an id that already carries this region survives a second pass
|
|
49
|
+
* unchanged rather than becoming `us-east-1::us-east-1::sg-1`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function unqualifiedKey(id: string, meta: Pick<ResourceMetadata, "attributes">): string;
|
|
52
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/lifecycle/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,SAAS,CAGvF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,YAAY,CAAC,GAAG,MAAM,CAI7F"}
|
|
@@ -60,6 +60,11 @@ export declare function observeResources(environment: string, plugins: Observati
|
|
|
60
60
|
* Ask a lexicon what exists of the kinds it manages, beyond what is declared
|
|
61
61
|
* (#1278). Once per stack for the region, merged by physical id — the same
|
|
62
62
|
* ambient resource seen from two stacks is one resource.
|
|
63
|
+
*
|
|
64
|
+
* "The same" is per region (#1416). Two stacks in one region reporting the
|
|
65
|
+
* account's default security group is one group; two regions' default security
|
|
66
|
+
* groups are two, whatever their ids look like, so the merge key carries the
|
|
67
|
+
* region a resource records itself in.
|
|
63
68
|
*/
|
|
64
69
|
export declare function collectAmbient(plugin: ObservationLexicon, opts: {
|
|
65
70
|
environment: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../src/lifecycle/observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"observe.d.ts","sourceRoot":"","sources":["../../src/lifecycle/observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAWnD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,WAAW,EAAE,WAAW,EACxB,IAAI,CAAC,EAAE;IACL,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzE;;8CAE0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GACA,OAAO,CAAC,aAAa,CAAC,CA2KxB;AAyBD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,GACA,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CA2B3C;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE,GACA,OAAO,CAAC,qBAAqB,CAAC,CA0ChC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/lifecycle/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/lifecycle/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;;;;;;;GAYG;AACH;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMvE;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GACxB,OAAO,CAAC;IAAE,YAAY,EAAE,eAAe,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsIpH"}
|
|
@@ -92,6 +92,18 @@ export interface ResourceDigest {
|
|
|
92
92
|
export interface BuildDigest {
|
|
93
93
|
/** Per-resource digest keyed by logical name */
|
|
94
94
|
resources: Record<string, ResourceDigest>;
|
|
95
|
+
/**
|
|
96
|
+
* chant #1442 — lexicon name → version that produced this build.
|
|
97
|
+
*
|
|
98
|
+
* Recorded once per lexicon rather than on every {@link ResourceDigest},
|
|
99
|
+
* which would repeat one string across every resource of a stack for no
|
|
100
|
+
* added information.
|
|
101
|
+
*
|
|
102
|
+
* Optional because a digest read back from an older snapshot will not have
|
|
103
|
+
* it. Absent and empty are different: absent means "recorded before this
|
|
104
|
+
* existed", empty means "recorded, and no plugins were loaded".
|
|
105
|
+
*/
|
|
106
|
+
lexiconVersions?: Record<string, string>;
|
|
95
107
|
/** Resource-level dependency graph */
|
|
96
108
|
dependencies: Record<string, string[]>;
|
|
97
109
|
/** Cross-lexicon output bridges from BuildManifest */
|
|
@@ -115,5 +127,23 @@ export interface DigestDiff {
|
|
|
115
127
|
changed: string[];
|
|
116
128
|
/** Resources where propsHash matches */
|
|
117
129
|
unchanged: string[];
|
|
130
|
+
/**
|
|
131
|
+
* chant #1442 — lexicons whose VERSION moved between the two digests, even
|
|
132
|
+
* where every resource is unchanged.
|
|
133
|
+
*
|
|
134
|
+
* A build whose only difference is the lexicon that emitted it is a real
|
|
135
|
+
* difference: the lexicon is a generated artifact pinned to an upstream
|
|
136
|
+
* spec, so a bump can change output with no source change. Reported
|
|
137
|
+
* separately from `changed` because no resource's declaration moved — the
|
|
138
|
+
* interpreter did.
|
|
139
|
+
*
|
|
140
|
+
* Empty when neither digest recorded versions, so a comparison against a
|
|
141
|
+
* pre-#1442 snapshot reports nothing rather than inventing a change.
|
|
142
|
+
*/
|
|
143
|
+
lexiconVersionChanges: Array<{
|
|
144
|
+
lexicon: string;
|
|
145
|
+
previous?: string;
|
|
146
|
+
current?: string;
|
|
147
|
+
}>;
|
|
118
148
|
}
|
|
119
149
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB;;gFAE4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IACrD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,qFAAqF;IACrF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,iCAAiC;IACjC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lifecycle/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB;;gFAE4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IACrD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,qFAAqF;IACrF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,iCAAiC;IACjC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,qBAAqB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxF"}
|
package/dist/managed-fields.d.ts
CHANGED
|
@@ -81,6 +81,17 @@ export interface OwnershipSets {
|
|
|
81
81
|
foreignOwned: ReadonlySet<string>;
|
|
82
82
|
/** The subset of `foreignOwned` where the declared manifest also sets the path — drift-relevant despite foreign ownership. */
|
|
83
83
|
foreignContested: ReadonlySet<string>;
|
|
84
|
+
/**
|
|
85
|
+
* Path → the name of the manager that owns it (#1189).
|
|
86
|
+
*
|
|
87
|
+
* The three sets above answer *which category* owns a path, which is all the
|
|
88
|
+
* prune rule needs. A reader needs the other question — "owned by
|
|
89
|
+
* `kubectl-client-side-apply`" and "owned by `hpa-controller`" are the same
|
|
90
|
+
* category and mean very different things to an operator. Last writer wins
|
|
91
|
+
* where several managers touch one path, matching what the API server itself
|
|
92
|
+
* reports.
|
|
93
|
+
*/
|
|
94
|
+
owners: ReadonlyMap<string, string>;
|
|
84
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Build the three ownership sets for one live object. `entries` is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"managed-fields.d.ts","sourceRoot":"","sources":["../src/managed-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAIrE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kCAAkC,EAAE,WAAW,CAAC,MAAM,CAQjE,CAAC;AAwBH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAwBhF;AAID;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,kDAAkD;IAClD,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,8HAA8H;IAC9H,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"managed-fields.d.ts","sourceRoot":"","sources":["../src/managed-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAIrE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kCAAkC,EAAE,WAAW,CAAC,MAAM,CAQjE,CAAC;AAwBH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAwBhF;AAID;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,kDAAkD;IAClD,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,8HAA8H;IAC9H,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;;;;;OASG;IACH,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AA6HD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,sBAAsB,EAAE,EAC1C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,GACvD,aAAa,CAoBf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAM7E"}
|
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -185,6 +185,14 @@ export interface BuildOptions {
|
|
|
185
185
|
*/
|
|
186
186
|
lexicons?: readonly string[];
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* chant #1442 — lexicon name → the version of the plugin that served it.
|
|
190
|
+
* Recorded on {@link BuildResult.lexiconVersions} so a build digest can say
|
|
191
|
+
* WHAT interpreted the declarations, not only what was declared. The CLI
|
|
192
|
+
* populates this from `options.plugins`; `build()` only carries it.
|
|
193
|
+
*/
|
|
194
|
+
lexiconVersions?: Readonly<Record<string, string>>;
|
|
195
|
+
|
|
188
196
|
/**
|
|
189
197
|
* chant #1045 Phase 2 — opt-in: run-fallback files (or, when {@link fold}
|
|
190
198
|
* isn't set, every file) execute together, isolated, in one sandboxed
|
|
@@ -230,6 +238,21 @@ export interface BuildResult {
|
|
|
230
238
|
*/
|
|
231
239
|
foldDecisions: FoldDecision[];
|
|
232
240
|
|
|
241
|
+
/**
|
|
242
|
+
* chant #1442 — lexicon name → the version of the plugin that served this
|
|
243
|
+
* build, passed through verbatim from {@link BuildOptions.lexiconVersions}.
|
|
244
|
+
*
|
|
245
|
+
* The other half of what a build digest needs. `hashProps` fingerprints the
|
|
246
|
+
* declaration; this records what turned it into output. A lexicon is a
|
|
247
|
+
* generated artifact pinned to an upstream spec, so a bump can change
|
|
248
|
+
* emitted output with no source change at all — and without this the two
|
|
249
|
+
* builds are indistinguishable.
|
|
250
|
+
*
|
|
251
|
+
* Empty when the caller supplied no plugins (`build()` used as a library,
|
|
252
|
+
* and most tests).
|
|
253
|
+
*/
|
|
254
|
+
lexiconVersions: Record<string, string>;
|
|
255
|
+
|
|
233
256
|
/**
|
|
234
257
|
* This build's resolved build-time parameters (#1064) — the build
|
|
235
258
|
* provenance record for `params.<name>` values, alongside the existing
|
|
@@ -717,6 +740,7 @@ async function buildFromDiscoveryResult(
|
|
|
717
740
|
manifest,
|
|
718
741
|
sourceFileCount: discoveryResult.sourceFiles.length,
|
|
719
742
|
foldDecisions: discoveryResult.foldDecisions,
|
|
743
|
+
lexiconVersions: { ...(options?.lexiconVersions ?? {}) },
|
|
720
744
|
buildParams: options?.buildParams ?? [],
|
|
721
745
|
};
|
|
722
746
|
}
|
|
@@ -3,6 +3,7 @@ import { loadChantConfigUpward, resolveOwnershipMarker, resolveFoldEnabled, reso
|
|
|
3
3
|
import { resolveCliBuildParams } from "../build-params-cli";
|
|
4
4
|
import type { Serializer, SerializerResult } from "../../serializer";
|
|
5
5
|
import type { LexiconPlugin } from "../../lexicon";
|
|
6
|
+
import { resolveLexiconVersions } from "../plugins";
|
|
6
7
|
import { runPostSynthChecks } from "../../lint/post-synth";
|
|
7
8
|
import { applyConfiguredSeverity } from "../../lint/config";
|
|
8
9
|
import { loadPolicyChecks } from "../../lint/policy";
|
|
@@ -242,6 +243,14 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
242
243
|
// ../plugins.ts and fold-import.ts's `lexiconPackageName`.
|
|
243
244
|
const lexicons = options.plugins?.map((plugin) => plugin.name) ?? [];
|
|
244
245
|
|
|
246
|
+
// chant #1442 — which lexicon VERSION interpreted each declaration. The
|
|
247
|
+
// declaration is fingerprinted by `hashProps`; the thing that turned it into
|
|
248
|
+
// output was not recorded at all, so a lexicon bump that changed emitted
|
|
249
|
+
// output left the build digest identical. Captured here because this is the
|
|
250
|
+
// layer that knows which lexicons were loaded — `build()` is given only
|
|
251
|
+
// their names.
|
|
252
|
+
const lexiconVersions = resolveLexiconVersions(lexicons);
|
|
253
|
+
|
|
245
254
|
// Run the build
|
|
246
255
|
const result = await build(infraPath, options.serializers, undefined, {
|
|
247
256
|
ownership,
|
|
@@ -250,6 +259,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
250
259
|
sandbox,
|
|
251
260
|
intrinsics,
|
|
252
261
|
lexicons,
|
|
262
|
+
lexiconVersions,
|
|
253
263
|
buildParams: paramsResolution.provenance,
|
|
254
264
|
});
|
|
255
265
|
|
|
@@ -3,7 +3,7 @@ import { join, basename } from "path";
|
|
|
3
3
|
import { auditIntrinsics } from "./check-lexicon-intrinsics";
|
|
4
4
|
import { checkExamplesBuild } from "./check-lexicon-examples";
|
|
5
5
|
import { auditDocsReachability } from "./check-lexicon-docs";
|
|
6
|
-
import { auditMcpNames } from "./check-lexicon-mcp";
|
|
6
|
+
import { auditMcpNames, lexiconNameFor } from "./check-lexicon-mcp";
|
|
7
7
|
import { loadLexiconFromDir, registers, safeList } from "./check-lexicon-plugin";
|
|
8
8
|
import { RULE_CATALOG } from "../../audit/catalog";
|
|
9
9
|
|
|
@@ -448,6 +448,25 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
448
448
|
: "no marker channel — every verdict must be unknown",
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
+
// #1344 — a lexicon that reads its own `chant.config.ts` namespace should
|
|
452
|
+
// declare its shape, or a typo inside that namespace is accepted and silently
|
|
453
|
+
// ignored: `forgejo: { runnerLabel: … }` left the dialect on its defaults with
|
|
454
|
+
// nothing said. Source scan, because the read happens deep in a serializer
|
|
455
|
+
// rather than anywhere the plugin object can be asked.
|
|
456
|
+
const lexiconName = lexiconNameFor(dir);
|
|
457
|
+
const readsOwnNamespace = findFiles(join(dir, "src"), (n) => n.endsWith(".ts") && !n.endsWith(".test.ts"))
|
|
458
|
+
.some((file) => new RegExp(`config\\s*\\??\\.\\s*${lexiconName}\\b`).test(readOr(file)));
|
|
459
|
+
items.push({
|
|
460
|
+
name: "Declares a configSchema if it reads its own config namespace",
|
|
461
|
+
tier: 2,
|
|
462
|
+
pass: !readsOwnNamespace || plugin?.configSchema !== undefined,
|
|
463
|
+
detail: readsOwnNamespace
|
|
464
|
+
? plugin?.configSchema
|
|
465
|
+
? `config.${lexiconName} is declared and validated`
|
|
466
|
+
: `reads config.${lexiconName} but declares no schema — unknown keys there are silently ignored`
|
|
467
|
+
: "reads no config namespace of its own",
|
|
468
|
+
});
|
|
469
|
+
|
|
451
470
|
const compositeFiles = listTsFiles(join(dir, "src/composites"), ["index.ts"]);
|
|
452
471
|
items.push({
|
|
453
472
|
name: "At least 1 composite in src/composites/",
|
|
@@ -597,6 +597,16 @@ async function runLifecycleDiffDigest(args: DigestDiffArgs): Promise<number> {
|
|
|
597
597
|
for (const name of diff.unchanged) {
|
|
598
598
|
console.log(name.padEnd(20) + "unchanged".padEnd(12) + (currentDigest.resources[name]?.type ?? ""));
|
|
599
599
|
}
|
|
600
|
+
|
|
601
|
+
// chant #1442 — a lexicon bump can change emitted output with no source
|
|
602
|
+
// change, so every resource above can read "unchanged" while the build is
|
|
603
|
+
// not the same build. Printed under the table rather than as a row: no
|
|
604
|
+
// resource's declaration moved, the thing that interpreted them did.
|
|
605
|
+
for (const change of diff.lexiconVersionChanges) {
|
|
606
|
+
const from = change.previous ?? "(absent)";
|
|
607
|
+
const to = change.current ?? "(absent)";
|
|
608
|
+
console.log(`\nlexicon ${change.lexicon}: ${from} → ${to}`);
|
|
609
|
+
}
|
|
600
610
|
}
|
|
601
611
|
|
|
602
612
|
return 0;
|
|
@@ -116,7 +116,9 @@ export function resolveProfile(
|
|
|
116
116
|
config: Record<string, unknown>,
|
|
117
117
|
profileName?: string,
|
|
118
118
|
): WorkerProfile {
|
|
119
|
-
|
|
119
|
+
// #1344 — the temporal lexicon declares this namespace and core validates it
|
|
120
|
+
// at load, so this reads a checked value rather than asserting one.
|
|
121
|
+
const temporal = (config as { temporal?: Record<string, unknown> }).temporal;
|
|
120
122
|
if (!temporal?.profiles) {
|
|
121
123
|
throw new Error(
|
|
122
124
|
'No temporal.profiles found in chant.config.ts. Add a profile to use `chant run`.',
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveKindTerms, type Term } from "./search";
|
|
3
|
+
import type { IRNode } from "../../graph-ir";
|
|
4
|
+
|
|
5
|
+
const nodes = [
|
|
6
|
+
{ id: "vpc", kind: "AWS::EC2::VPC", lexicon: "aws", attrs: {} },
|
|
7
|
+
{ id: "att", kind: "AWS::EC2::VPCGatewayAttachment", lexicon: "aws", attrs: {} },
|
|
8
|
+
{ id: "sub", kind: "AWS::EC2::Subnet", lexicon: "aws", attrs: {} },
|
|
9
|
+
{ id: "assoc", kind: "AWS::EC2::SubnetRouteTableAssociation", lexicon: "aws", attrs: {} },
|
|
10
|
+
{ id: "igw", kind: "AWS::EC2::InternetGateway", lexicon: "aws", attrs: {} },
|
|
11
|
+
] as IRNode[];
|
|
12
|
+
|
|
13
|
+
const resolve = (a: string) => {
|
|
14
|
+
const t: Term = { kind: "kind", a };
|
|
15
|
+
resolveKindTerms([t], nodes);
|
|
16
|
+
return t.kinds ? [...t.kinds].sort() : undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("a kind term means the kind, not any type containing its letters", () => {
|
|
20
|
+
it("EC2::VPC is the VPC, not the gateway attachment", () => {
|
|
21
|
+
// The estate has 6 VPCs and answered 9. VPCGatewayAttachment contains the
|
|
22
|
+
// characters "EC2::VPC" and is not a VPC.
|
|
23
|
+
expect(resolve("EC2::VPC")).toEqual(["AWS::EC2::VPC"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("Subnet is the subnet, not the route table association", () => {
|
|
27
|
+
expect(resolve("Subnet")).toEqual(["AWS::EC2::Subnet"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("a full type still resolves to itself", () => {
|
|
31
|
+
expect(resolve("AWS::EC2::Subnet")).toEqual(["AWS::EC2::Subnet"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("a genuine substring search still works", () => {
|
|
35
|
+
// Nothing lines up with a `::` boundary here, so the documented substring
|
|
36
|
+
// rule applies and finds both gateway-ish kinds.
|
|
37
|
+
expect(resolve("Gateway")).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("resolves inside an edge sub-term, where the negation lives", () => {
|
|
41
|
+
const t: Term = { kind: "edge", a: "", dir: "in", sub: { kind: "kind", a: "EC2::VPC" } };
|
|
42
|
+
resolveKindTerms([t], nodes);
|
|
43
|
+
expect(t.sub?.kinds && [...t.sub.kinds]).toEqual(["AWS::EC2::VPC"]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { build } from "../../build";
|
|
3
|
-
import { buildGraphIr, buildLiveGraphIr, sourceOverlayGraphs, type GraphIR, type IRNode } from "../../graph-ir";
|
|
3
|
+
import { buildGraphIr, buildLiveGraphIr, sourceOverlayGraphs, type GraphIR, type IRNode, type IREdge } from "../../graph-ir";
|
|
4
4
|
import { buildDeclaredPerStack } from "../../graph-declared";
|
|
5
5
|
import { enrichEffectiveTopology } from "../../graph-effective";
|
|
6
6
|
import { reconstructEdges, mergeCatalogs, type ReferenceCatalog } from "../../graph-refs";
|
|
@@ -127,6 +127,8 @@ export async function runSearch(ctx: CommandContext): Promise<number> {
|
|
|
127
127
|
source = { kind: "live" };
|
|
128
128
|
}
|
|
129
129
|
let live = buildLiveGraphIr(observations);
|
|
130
|
+
// Containment edges, kept aside until after the overlay (see below).
|
|
131
|
+
let containmentEdges: IREdge[] = [];
|
|
130
132
|
// Live-only: enrichment is a provider call, so it has no place in a replay.
|
|
131
133
|
// A recorded answer that quietly reached for the API would stop being one.
|
|
132
134
|
if (!args.at) {
|
|
@@ -147,13 +149,24 @@ export async function runSearch(ctx: CommandContext): Promise<number> {
|
|
|
147
149
|
// topology has nothing to traverse on anything the declared graph does not
|
|
148
150
|
// already model.
|
|
149
151
|
const catalogs = observing.map((p) => p.referenceCatalog).filter((c): c is ReferenceCatalog => !!c);
|
|
152
|
+
// Held back until after the overlay — see where it is merged for why.
|
|
150
153
|
if (catalogs.length > 0) {
|
|
151
154
|
// Merge, never replace. A lexicon reports relationships a catalog cannot
|
|
152
155
|
// reconstruct (#1273) — an instance placed in a subnet it did not declare
|
|
153
156
|
// carries a template `Ref` in its attributes, not the physical subnet id,
|
|
154
157
|
// so no identity index resolves it. Overwriting here dropped exactly those
|
|
155
158
|
// edges and left the fold with a chain missing its first hop.
|
|
156
|
-
|
|
159
|
+
// Containment travels with the references here, and only here. `->`/`<-`
|
|
160
|
+
// asks which nodes reach which, and being inside something is a way of
|
|
161
|
+
// reaching it: "which subnets have no network interfaces IN them" and
|
|
162
|
+
// "which VPCs have no instances IN them" are containment questions, and
|
|
163
|
+
// with only peer references to walk the negation matched everything.
|
|
164
|
+
//
|
|
165
|
+
// `graph` still reads `.containment` on its own and draws boundary boxes,
|
|
166
|
+
// so nothing gains a line it did not have.
|
|
167
|
+
const rebuilt = reconstructEdges(live.nodes, mergeCatalogs(catalogs));
|
|
168
|
+
containmentEdges = rebuilt.containmentEdges;
|
|
169
|
+
const reconstructed = rebuilt.edges;
|
|
157
170
|
const seen = new Set((live.edges ?? []).map((e) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`));
|
|
158
171
|
live = {
|
|
159
172
|
...live,
|
|
@@ -177,6 +190,36 @@ export async function runSearch(ctx: CommandContext): Promise<number> {
|
|
|
177
190
|
? await buildDeclaredPerStack(stacks, projectPath)
|
|
178
191
|
: buildGraphIr((await discover(resolve(args.src ?? config.sourceDir ?? "."))).entities, projectPath);
|
|
179
192
|
ir = sourceOverlayGraphs(declared, live);
|
|
193
|
+
// Containment goes on AFTER the overlay, not through it.
|
|
194
|
+
//
|
|
195
|
+
// `sourceOverlayGraphs` admits a live edge only when one end is foreign,
|
|
196
|
+
// because "declared edges already cover every declared relationship". That
|
|
197
|
+
// is true of the references it was written for and false of containment:
|
|
198
|
+
// the declared canvas carries what the source *says*, and no source says
|
|
199
|
+
// "this instance is in this VPC" — the instance declares a subnet, and the
|
|
200
|
+
// VPC is implied by the account.
|
|
201
|
+
//
|
|
202
|
+
// So an instance and its VPC, both declared, had their containment dropped
|
|
203
|
+
// between them, while an instance in the account's DEFAULT VPC kept it
|
|
204
|
+
// because that VPC is foreign. "Which VPCs have no instances" answered
|
|
205
|
+
// correctly for the one VPC nobody declared and wrongly for every VPC
|
|
206
|
+
// somebody did.
|
|
207
|
+
if (containmentEdges.length > 0) {
|
|
208
|
+
const present = new Set(ir.nodes.map((n) => n.id));
|
|
209
|
+
const seen = new Set(ir.edges.map((e) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`));
|
|
210
|
+
ir = {
|
|
211
|
+
...ir,
|
|
212
|
+
edges: [
|
|
213
|
+
...ir.edges,
|
|
214
|
+
...containmentEdges.filter(
|
|
215
|
+
(e) =>
|
|
216
|
+
present.has(e.from) &&
|
|
217
|
+
present.has(e.to) &&
|
|
218
|
+
!seen.has(`${e.from}|${e.to}|${e.viaAttr ?? ""}`),
|
|
219
|
+
),
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
180
223
|
// Carry live-derived attrs onto the declared canvas — some facts only exist
|
|
181
224
|
// in live account state (e.g. `internetFacing` for an instance in the
|
|
182
225
|
// account's default VPC, whose route table chant does not model). The
|
|
@@ -190,6 +233,9 @@ export async function runSearch(ctx: CommandContext): Promise<number> {
|
|
|
190
233
|
// Fold derived reachability facts (effectiveIngress, internetFacing) onto
|
|
191
234
|
// instance nodes so multi-hop/launch-template joins are one node predicate (#1139).
|
|
192
235
|
ir = enrichEffectiveTopology(ir);
|
|
236
|
+
// Resolve `kind:` terms against what is actually in this graph, before any
|
|
237
|
+
// node is tested against them.
|
|
238
|
+
resolveKindTerms(terms, ir.nodes);
|
|
193
239
|
const nodeById = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
194
240
|
const matches = ir.nodes.filter((n) => terms.every((t) => matchTerm(n, t, ir, nodeById)));
|
|
195
241
|
if (matches.length === 0) {
|
|
@@ -489,7 +535,7 @@ function describeTerm(t: Term): string {
|
|
|
489
535
|
return leaf(t);
|
|
490
536
|
}
|
|
491
537
|
|
|
492
|
-
interface Term {
|
|
538
|
+
export interface Term {
|
|
493
539
|
kind: "word" | "kind" | "tag" | "attr" | "edge";
|
|
494
540
|
/** `!term` — the node must NOT satisfy this (#1280). */
|
|
495
541
|
negated?: boolean;
|
|
@@ -498,6 +544,53 @@ interface Term {
|
|
|
498
544
|
/** For edge terms: the direction and the sub-predicate matched at the far end. */
|
|
499
545
|
dir?: "out" | "in";
|
|
500
546
|
sub?: Term;
|
|
547
|
+
/**
|
|
548
|
+
* For kind terms: the kinds this term actually means, resolved against the
|
|
549
|
+
* graph (see {@link resolveKindTerms}). Absent when nothing resolved it, in
|
|
550
|
+
* which case the substring rule applies unchanged.
|
|
551
|
+
*/
|
|
552
|
+
kinds?: Set<string>;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Decide which kinds a `kind:` term means, given what is in the graph.
|
|
557
|
+
*
|
|
558
|
+
* `kind:` is documented as a substring, and substrings of a CloudFormation type
|
|
559
|
+
* cross kind boundaries: `kind:EC2::VPC` also matches
|
|
560
|
+
* `AWS::EC2::VPCGatewayAttachment`, and `kind:EC2::Subnet` also matches
|
|
561
|
+
* `AWS::EC2::SubnetRouteTableAssociation`. An estate with 6 VPCs answers 9, and
|
|
562
|
+
* the caller has no reason to suspect the number they were given.
|
|
563
|
+
*
|
|
564
|
+
* That cost real answers. Asked how many VPCs have no instances, agents named
|
|
565
|
+
* the right VPCs and reported "9 VPCs in the estate" beside them; the grader
|
|
566
|
+
* failed the answer for contradicting the estate, which it did.
|
|
567
|
+
*
|
|
568
|
+
* So: a term that lines up with `::` boundaries means those kinds and only
|
|
569
|
+
* those. `EC2::VPC` is the last two segments of `AWS::EC2::VPC` and is not any
|
|
570
|
+
* run of segments in `AWS::EC2::VPCGatewayAttachment`, because
|
|
571
|
+
* `VPCGatewayAttachment` is not `VPC`.
|
|
572
|
+
*
|
|
573
|
+
* Substring is kept as the fallback, so `kind:Gateway` still finds both
|
|
574
|
+
* `InternetGateway` and `VPCGatewayAttachment` — a genuine substring search
|
|
575
|
+
* that no segment rule would serve. Most specific wins; nothing else changes.
|
|
576
|
+
*/
|
|
577
|
+
export function resolveKindTerms(terms: Term[], nodes: IRNode[]): void {
|
|
578
|
+
const present = [...new Set(nodes.map((n) => n.kind).filter((k): k is string => !!k))];
|
|
579
|
+
const walk = (t: Term): void => {
|
|
580
|
+
if (t.sub) walk(t.sub);
|
|
581
|
+
if (t.kind !== "kind") return;
|
|
582
|
+
const want = t.a.toLowerCase().split("::").filter(Boolean);
|
|
583
|
+
if (want.length === 0) return;
|
|
584
|
+
const onBoundary = present.filter((k) => {
|
|
585
|
+
const have = k.toLowerCase().split("::");
|
|
586
|
+
for (let i = 0; i + want.length <= have.length; i++) {
|
|
587
|
+
if (want.every((w, j) => have[i + j] === w)) return true;
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
});
|
|
591
|
+
if (onBoundary.length > 0) t.kinds = new Set(onBoundary);
|
|
592
|
+
};
|
|
593
|
+
terms.forEach(walk);
|
|
501
594
|
}
|
|
502
595
|
|
|
503
596
|
function parseLeaf(tok: string): Term {
|
|
@@ -616,7 +709,12 @@ function matchTerm(n: IRNode, t: Term, ir?: GraphIR, byId?: Map<string, IRNode>)
|
|
|
616
709
|
.filter((x): x is IRNode => !!x);
|
|
617
710
|
return neighbors.some((m) => matchTerm(m, t.sub!, ir, byId));
|
|
618
711
|
}
|
|
619
|
-
if (t.kind === "kind")
|
|
712
|
+
if (t.kind === "kind") {
|
|
713
|
+
// Resolved kinds when the term lined up with `::` boundaries; the
|
|
714
|
+
// documented substring otherwise.
|
|
715
|
+
if (t.kinds) return t.kinds.has(n.kind ?? "");
|
|
716
|
+
return (n.kind ?? "").toLowerCase().includes(t.a.toLowerCase());
|
|
717
|
+
}
|
|
620
718
|
if (t.kind === "attr") {
|
|
621
719
|
const val = attrString((attrs as Record<string, unknown>)[t.a]);
|
|
622
720
|
return t.b === undefined ? t.a in attrs : val.toLowerCase().includes(t.b.toLowerCase());
|
package/src/cli/main.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { formatSuccess, formatError } from "./format";
|
|
|
6
6
|
import { loadPlugins, resolveProjectLexicons } from "./plugins";
|
|
7
7
|
import { resolveCommand, type CommandDef, type ParsedArgs } from "./registry";
|
|
8
8
|
import { loadChantConfigUpward } from "../config";
|
|
9
|
+
import { validateLexiconConfig, formatLexiconConfigProblems } from "../lexicon-config";
|
|
9
10
|
import { armSandboxConfigEvaluation } from "../config-sandbox";
|
|
10
11
|
import { armSandboxPolicyExecution } from "../lint/policy-import";
|
|
11
12
|
import { ENV_VAR, unknownEnvError } from "../env";
|
|
@@ -559,16 +560,17 @@ Options:
|
|
|
559
560
|
project source; network egress is NOT blocked (see
|
|
560
561
|
docs). Default: off (also settable via
|
|
561
562
|
chant.config.ts's build.sandbox: true; #1045)
|
|
562
|
-
--param <name=value> (build) Bind a declared
|
|
563
|
-
(chant.config.ts's buildParams)
|
|
564
|
-
source to read as params.<name>
|
|
565
|
-
process.env — repeatable.
|
|
566
|
-
lexicon's deploy-time
|
|
567
|
-
before synthesis, so it
|
|
568
|
-
are produced at all.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
563
|
+
--param <name=value> (build, graph, run --components) Bind a declared
|
|
564
|
+
build-time parameter (chant.config.ts's buildParams)
|
|
565
|
+
to a value, for source to read as params.<name>
|
|
566
|
+
(#1064) instead of process.env — repeatable.
|
|
567
|
+
Distinct from the AWS lexicon's deploy-time
|
|
568
|
+
Parameter(): this resolves before synthesis, so it
|
|
569
|
+
can change which resources are produced at all.
|
|
570
|
+
Highest precedence.
|
|
571
|
+
--params-file <path> (build, graph, run --components) JSON file of
|
|
572
|
+
{ "name": value } build-time parameter values
|
|
573
|
+
(#1064). Second precedence, after --param.
|
|
572
574
|
|
|
573
575
|
Examples:
|
|
574
576
|
chant build ./infra/
|
|
@@ -668,6 +670,26 @@ async function loadPluginsOrExit(path: string): Promise<import("../lexicon").Lex
|
|
|
668
670
|
process.exit(1);
|
|
669
671
|
}
|
|
670
672
|
|
|
673
|
+
// #1344 — a lexicon that declares the shape of its own `chant.config.ts`
|
|
674
|
+
// namespace gets it validated here, once, before any command runs. The
|
|
675
|
+
// config schema is `.passthrough()`, so before this an unknown key inside a
|
|
676
|
+
// namespace was accepted and silently ignored: `forgejo: { runnerLabel: … }`
|
|
677
|
+
// left the dialect on its defaults with nothing said. A namespace whose
|
|
678
|
+
// lexicon declares no schema keeps that passthrough.
|
|
679
|
+
try {
|
|
680
|
+
const { config } = await loadChantConfigUpward(resolve(path));
|
|
681
|
+
const problems = validateLexiconConfig(plugins, config);
|
|
682
|
+
if (problems.length > 0) {
|
|
683
|
+
console.error(formatError({
|
|
684
|
+
message: `Invalid lexicon configuration in chant.config:\n${formatLexiconConfigProblems(problems)}`,
|
|
685
|
+
hint: "Remove or correct the key. A lexicon's namespace accepts only the keys it declares.",
|
|
686
|
+
}));
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
// No config, or one that failed to load — the caller's own handling stands.
|
|
691
|
+
}
|
|
692
|
+
|
|
671
693
|
return plugins;
|
|
672
694
|
}
|
|
673
695
|
|