@intentius/chant 0.49.0 → 0.51.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/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
package/src/provenance.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* entity object, so it never serializes into the emitted YAML/JSON — it is build
|
|
6
6
|
* metadata, not declared configuration. Read it back with {@link getProvenance}.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Entity-level provenance (which file declared it, and which composite expanded
|
|
9
|
+
* it) answers "where did this resource come from?", which is the question an
|
|
10
|
+
* agent asks before changing it. {@link PathOrigin} (chant #1443) answers the
|
|
11
|
+
* follow-up — "what set THIS field" — for the paths a build can attribute
|
|
12
|
+
* without guessing. Neither is a YAML-line source map.
|
|
11
13
|
*
|
|
12
14
|
* {@link BuildParamProvenance} (chant #1064) is the other half: not "where did
|
|
13
15
|
* this ENTITY come from" but "what INPUTS was this whole BUILD invoked with" —
|
|
@@ -30,6 +32,58 @@ export interface EntityProvenance {
|
|
|
30
32
|
* collapse a composite to a single node at coarse diagram detail levels (#494).
|
|
31
33
|
*/
|
|
32
34
|
compositeInstance?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Per-path origins within the entity's `props` (chant #1443). Read with
|
|
37
|
+
* {@link originOfPath}, which does the prefix resolution these keys are
|
|
38
|
+
* written for.
|
|
39
|
+
*
|
|
40
|
+
* Keys are dotted property paths only — `spec.template.spec.containers` —
|
|
41
|
+
* with `""` for the whole entity. They never carry an array segment, because
|
|
42
|
+
* a producing mechanism attributes a whole array at once: the coarsest path
|
|
43
|
+
* it actually determines is the honest one to record, and a positional key
|
|
44
|
+
* would go stale the moment an element moved.
|
|
45
|
+
*
|
|
46
|
+
* Query paths come from #1441's diff addressing and may carry `[#key]` for an
|
|
47
|
+
* element of an associative list or `[n]` for a positional one. Resolution is
|
|
48
|
+
* longest segment-boundary prefix, so `spec.containers[#app].image` finds a
|
|
49
|
+
* key recorded at `spec.containers`, and a key recorded at
|
|
50
|
+
* `spec.containers.image` never matches it. The two grammars agree without
|
|
51
|
+
* the recorder needing the lexicon's ordering hook, which it has no access to.
|
|
52
|
+
*
|
|
53
|
+
* Absent means this build could not record path origins, not that nothing
|
|
54
|
+
* governs the entity's fields — the run path executes modules and has no
|
|
55
|
+
* expression to inspect, and a sandboxed child's provenance does not survive
|
|
56
|
+
* the wire (`discovery/entity-wire-codec.ts` drops build metadata).
|
|
57
|
+
*/
|
|
58
|
+
paths?: Record<string, PathOrigin>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What produced one path inside an entity's props.
|
|
63
|
+
*
|
|
64
|
+
* - `authored` — written in the source file that declared the entity.
|
|
65
|
+
* - `composite` — the entity was expanded by a composite, or the path was
|
|
66
|
+
* supplied by `propagate()`'s shared props at the instance.
|
|
67
|
+
* - `build-param` — the build parameters the authored expression for this path
|
|
68
|
+
* *reads*, which is a syntactic dependency, not a value taint. For
|
|
69
|
+
* `replicas: params.tier === "prod" ? 5 : 1` a taint says the value came from
|
|
70
|
+
* the literal `5`, which is true and useless; the dependency says the field
|
|
71
|
+
* is governed by `tier`, which is what an editor needs to know. Under-reports
|
|
72
|
+
* (a shape the collector does not follow drops a dependency) and never
|
|
73
|
+
* over-reports a parameter the expression does not mention.
|
|
74
|
+
*/
|
|
75
|
+
export type PathOrigin =
|
|
76
|
+
| { kind: "authored" }
|
|
77
|
+
| { kind: "composite"; composite: string; instance: string }
|
|
78
|
+
| { kind: "build-param"; params: string[] };
|
|
79
|
+
|
|
80
|
+
/** True when `prefix` addresses `path` or an ancestor of it, on a segment boundary. */
|
|
81
|
+
function isPathPrefix(prefix: string, path: string): boolean {
|
|
82
|
+
if (prefix === "") return true;
|
|
83
|
+
if (!path.startsWith(prefix)) return false;
|
|
84
|
+
if (path.length === prefix.length) return true;
|
|
85
|
+
const next = path[prefix.length];
|
|
86
|
+
return next === "." || next === "[";
|
|
33
87
|
}
|
|
34
88
|
|
|
35
89
|
/**
|
|
@@ -44,10 +98,16 @@ export function setProvenance(entity: object, prov: EntityProvenance): void {
|
|
|
44
98
|
existing.sourceFile ??= prov.sourceFile;
|
|
45
99
|
existing.composite ??= prov.composite;
|
|
46
100
|
existing.compositeInstance ??= prov.compositeInstance;
|
|
101
|
+
if (prov.paths) {
|
|
102
|
+
existing.paths ??= {};
|
|
103
|
+
for (const [path, origin] of Object.entries(prov.paths)) {
|
|
104
|
+
existing.paths[path] ??= origin;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
47
107
|
return;
|
|
48
108
|
}
|
|
49
109
|
Object.defineProperty(entity, PROVENANCE, {
|
|
50
|
-
value: { ...prov },
|
|
110
|
+
value: { ...prov, ...(prov.paths ? { paths: { ...prov.paths } } : {}) },
|
|
51
111
|
enumerable: false,
|
|
52
112
|
writable: true,
|
|
53
113
|
configurable: true,
|
|
@@ -59,6 +119,60 @@ export function getProvenance(entity: object): EntityProvenance | undefined {
|
|
|
59
119
|
return (entity as Record<symbol, unknown>)[PROVENANCE] as EntityProvenance | undefined;
|
|
60
120
|
}
|
|
61
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Record one path's origin, first writer wins — the same rule
|
|
124
|
+
* {@link setProvenance} applies to entity-level fields, and for the same
|
|
125
|
+
* reason: expansion runs innermost-composite-first, so the earliest writer is
|
|
126
|
+
* the most specific one.
|
|
127
|
+
*/
|
|
128
|
+
export function setPathProvenance(entity: object, path: string, origin: PathOrigin): void {
|
|
129
|
+
setProvenance(entity, { paths: { [path]: origin } });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* An entity's recorded path origins in sorted key order, or `undefined` when
|
|
134
|
+
* none were recorded. Sorted because a build's outputs are compared byte for
|
|
135
|
+
* byte and object key order is not something insertion should decide.
|
|
136
|
+
*/
|
|
137
|
+
export function getPathProvenance(entity: object): Record<string, PathOrigin> | undefined {
|
|
138
|
+
const paths = getProvenance(entity)?.paths;
|
|
139
|
+
if (!paths) return undefined;
|
|
140
|
+
const keys = Object.keys(paths).sort();
|
|
141
|
+
if (keys.length === 0) return undefined;
|
|
142
|
+
const out: Record<string, PathOrigin> = {};
|
|
143
|
+
for (const key of keys) out[key] = paths[key];
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The origin governing `path` — the longest recorded key that addresses it or
|
|
149
|
+
* one of its ancestors. See {@link PROVENANCE_PATH_GRAMMAR}.
|
|
150
|
+
*/
|
|
151
|
+
export function originOfPath(
|
|
152
|
+
paths: Readonly<Record<string, PathOrigin>> | undefined,
|
|
153
|
+
path: string,
|
|
154
|
+
): PathOrigin | undefined {
|
|
155
|
+
if (!paths) return undefined;
|
|
156
|
+
let best: string | undefined;
|
|
157
|
+
for (const key of Object.keys(paths)) {
|
|
158
|
+
if (!isPathPrefix(key, path)) continue;
|
|
159
|
+
if (best === undefined || key.length > best.length) best = key;
|
|
160
|
+
}
|
|
161
|
+
return best === undefined ? undefined : paths[best];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** One-line rendering of an origin, for diff output. */
|
|
165
|
+
export function describePathOrigin(origin: PathOrigin): string {
|
|
166
|
+
switch (origin.kind) {
|
|
167
|
+
case "authored":
|
|
168
|
+
return "authored";
|
|
169
|
+
case "composite":
|
|
170
|
+
return `composite ${origin.composite} (${origin.instance})`;
|
|
171
|
+
case "build-param":
|
|
172
|
+
return `param ${origin.params.join(", ")}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
62
176
|
/**
|
|
63
177
|
* One resolved build-time parameter (chant #1064, see ../build-params.ts): its
|
|
64
178
|
* final value and which source won it, so a build's parameter inputs are
|
package/src/runtime-adapter.ts
CHANGED
|
@@ -34,8 +34,13 @@ export interface RuntimeAdapter {
|
|
|
34
34
|
readonly hashAlgorithm: string;
|
|
35
35
|
/** Test whether filePath matches a glob pattern */
|
|
36
36
|
globMatch(pattern: string, filePath: string): boolean;
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Spawn a child process and collect output. When `opts.stdin` is set, it is
|
|
39
|
+
* written to the child's stdin and the stream is closed — the caller never
|
|
40
|
+
* needs to round-trip content through a shell (`sh -c "echo … | cmd"`) just
|
|
41
|
+
* to feed a command that reads from stdin (e.g. `git hash-object --stdin`).
|
|
42
|
+
*/
|
|
43
|
+
spawn(cmd: string[], opts?: { cwd?: string; stdin?: string }): Promise<SpawnResult>;
|
|
39
44
|
/** Commands to use when spawning package manager / executor */
|
|
40
45
|
readonly commands: RuntimeCommands;
|
|
41
46
|
}
|
|
@@ -59,16 +64,32 @@ class NodeRuntimeAdapter implements RuntimeAdapter {
|
|
|
59
64
|
return picomatch(pattern)(filePath);
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
async spawn(cmd: string[], opts?: { cwd?: string }): Promise<SpawnResult> {
|
|
67
|
+
async spawn(cmd: string[], opts?: { cwd?: string; stdin?: string }): Promise<SpawnResult> {
|
|
63
68
|
return new Promise((resolve) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
const child = execFile(
|
|
70
|
+
cmd[0],
|
|
71
|
+
cmd.slice(1),
|
|
72
|
+
{ cwd: opts?.cwd, maxBuffer: 10 * 1024 * 1024 },
|
|
73
|
+
(err, stdout, stderr) => {
|
|
74
|
+
resolve({
|
|
75
|
+
stdout: stdout ?? "",
|
|
76
|
+
stderr: stderr ?? "",
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
exitCode: err ? (err as any).code ?? 1 : 0,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
// Content is written verbatim (utf-8) — no shell involved, so nothing
|
|
83
|
+
// reinterprets backslash-escape sequences or needs quote-escaping.
|
|
84
|
+
// The error handler is load-bearing: a child that exits without reading
|
|
85
|
+
// stdin (any command that ignores it) makes this write race the pipe
|
|
86
|
+
// teardown, and an unhandled EPIPE 'error' event crashes the process.
|
|
87
|
+
if (child.stdin) {
|
|
88
|
+
child.stdin.on("error", () => {
|
|
89
|
+
/* EPIPE: child exited without consuming stdin — harmless */
|
|
70
90
|
});
|
|
71
|
-
|
|
91
|
+
child.stdin.end(opts?.stdin ?? "");
|
|
92
|
+
}
|
|
72
93
|
});
|
|
73
94
|
}
|
|
74
95
|
}
|
package/src/serializer.ts
CHANGED
|
@@ -42,6 +42,24 @@ export interface SerializerResult {
|
|
|
42
42
|
primary: string;
|
|
43
43
|
/** Additional files keyed by filename (e.g. "network.template.json" → content) */
|
|
44
44
|
files?: Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* Basenames from `files` that must reach disk byte-for-byte — never
|
|
47
|
+
* `JSON.parse`d, key-sorted, or reformatted to YAML by the build's
|
|
48
|
+
* additional-file writer (chant#1937).
|
|
49
|
+
*
|
|
50
|
+
* A separate field rather than a per-file flag on `files` (e.g. widening
|
|
51
|
+
* its value to `string | { content: string; verbatim: boolean }`) so every
|
|
52
|
+
* existing lexicon serializer that returns `files: Record<string, string>`
|
|
53
|
+
* keeps compiling and behaving unchanged: this list defaults to empty, and
|
|
54
|
+
* only a serializer that actually carries opaque/pre-formatted bytes
|
|
55
|
+
* (committed ciphertext, a vendored file) needs to populate it.
|
|
56
|
+
*
|
|
57
|
+
* Before this field existed, "JSON.parse fails on this content" was the
|
|
58
|
+
* only way to opt out of round-tripping — true for YAML that happens not to
|
|
59
|
+
* parse as JSON, but never a structural guarantee (YAML is a JSON
|
|
60
|
+
* superset).
|
|
61
|
+
*/
|
|
62
|
+
verbatimFiles?: string[];
|
|
45
63
|
/**
|
|
46
64
|
* Non-fatal diagnostics produced during serialization (e.g. a dialect
|
|
47
65
|
* dropping keys the target platform ignores). The build pipeline collects
|
package/src/testing.test.ts
CHANGED
|
@@ -3,8 +3,14 @@ import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { createMockPlugin } from "@intentius/chant-test-utils";
|
|
7
|
-
import {
|
|
6
|
+
import { createMockPlugin, staticObservation } from "@intentius/chant-test-utils";
|
|
7
|
+
import {
|
|
8
|
+
deployStack,
|
|
9
|
+
testEnvName,
|
|
10
|
+
TeardownIncompleteError,
|
|
11
|
+
LiveAssertionError,
|
|
12
|
+
UnobservedAssertionError,
|
|
13
|
+
} from "./testing";
|
|
8
14
|
import type { ActivityFn } from "./op/activity-registry";
|
|
9
15
|
import type { TeardownExecution } from "./lexicon";
|
|
10
16
|
|
|
@@ -258,4 +264,85 @@ describe("deployStack", () => {
|
|
|
258
264
|
await expect(deployStack(harness(nativeApply, { applyTargets: {} }))).rejects.toThrow(/nothing to deploy/);
|
|
259
265
|
expect(nativeApply).not.toHaveBeenCalled();
|
|
260
266
|
});
|
|
267
|
+
|
|
268
|
+
describe("assertLive", () => {
|
|
269
|
+
const nativeApply = vi.fn(async (_args: Record<string, unknown>) => ({ applied: 1, pruned: 0, notAttempted: 0 }));
|
|
270
|
+
|
|
271
|
+
test("resolves the metadata for an entity observed present and marker-matched", async () => {
|
|
272
|
+
await writeProject(dir);
|
|
273
|
+
const plugin = createMockPlugin({
|
|
274
|
+
name: "mock",
|
|
275
|
+
describeResources: (opts) =>
|
|
276
|
+
Promise.resolve({
|
|
277
|
+
bucket: { type: "Mock::Bucket", status: "READY", marker: { stack: "harness-fixture", env: opts.environment } },
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
281
|
+
|
|
282
|
+
const meta = await stack.assertLive("bucket");
|
|
283
|
+
expect(meta.status).toBe("READY");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("checks status when given", async () => {
|
|
287
|
+
await writeProject(dir);
|
|
288
|
+
const plugin = createMockPlugin({
|
|
289
|
+
name: "mock",
|
|
290
|
+
describeResources: (opts) =>
|
|
291
|
+
Promise.resolve({
|
|
292
|
+
bucket: { type: "Mock::Bucket", status: "READY", marker: { stack: "harness-fixture", env: opts.environment } },
|
|
293
|
+
}),
|
|
294
|
+
});
|
|
295
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
296
|
+
|
|
297
|
+
await expect(stack.assertLive("bucket", { status: "READY" })).resolves.toBeDefined();
|
|
298
|
+
await expect(stack.assertLive("bucket", { status: "GONE" })).rejects.toThrow(LiveAssertionError);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("throws naming the entity when observed absent", async () => {
|
|
302
|
+
await writeProject(dir);
|
|
303
|
+
const plugin = createMockPlugin({ name: "mock", describeResources: staticObservation({}) });
|
|
304
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
305
|
+
|
|
306
|
+
await expect(stack.assertLive("bucket")).rejects.toThrow(/bucket.*observed absent/s);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("throws UnobservedAssertionError, not a plain failure, when NOT-OBSERVED", async () => {
|
|
310
|
+
await writeProject(dir);
|
|
311
|
+
const plugin = createMockPlugin({
|
|
312
|
+
name: "mock",
|
|
313
|
+
describeResources: staticObservation({}, { bucket: { reason: "no-binding", type: "Mock::Bucket" } }),
|
|
314
|
+
});
|
|
315
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
316
|
+
|
|
317
|
+
const err = await stack.assertLive("bucket").then(
|
|
318
|
+
() => undefined,
|
|
319
|
+
(e: unknown) => e,
|
|
320
|
+
);
|
|
321
|
+
expect(err).toBeInstanceOf(UnobservedAssertionError);
|
|
322
|
+
expect(err).not.toBeInstanceOf(LiveAssertionError);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("throws when the observed resource carries another environment's marker — a same-named leftover cannot satisfy it", async () => {
|
|
326
|
+
await writeProject(dir);
|
|
327
|
+
const plugin = createMockPlugin({
|
|
328
|
+
name: "mock",
|
|
329
|
+
describeResources: staticObservation({
|
|
330
|
+
bucket: { type: "Mock::Bucket", status: "READY", marker: { stack: "harness-fixture", env: "test-someone-else-000000" } },
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
333
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
334
|
+
|
|
335
|
+
await expect(stack.assertLive("bucket")).rejects.toThrow(/not this deploy's/);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("an unknown entity name throws before any read is attempted", async () => {
|
|
339
|
+
await writeProject(dir);
|
|
340
|
+
const describeResources = vi.fn(staticObservation({}));
|
|
341
|
+
const plugin = createMockPlugin({ name: "mock", describeResources });
|
|
342
|
+
const stack = await deployStack(harness(nativeApply, { plugins: [plugin] }));
|
|
343
|
+
|
|
344
|
+
await expect(stack.assertLive("nope")).rejects.toThrow(/no such entity/);
|
|
345
|
+
expect(describeResources).not.toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
261
348
|
});
|
package/src/testing.ts
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* in-process for exactly this suite's environment. Stateless by design:
|
|
16
16
|
* a crashed suite's environment is recovered by calling destroy again (or
|
|
17
17
|
* `chant lifecycle teardown <env> --yes`).
|
|
18
|
+
* - **assertLive** (#1857) — `describeResources()` against exactly one
|
|
19
|
+
* declared entity, turned into a pass or a thrown failure. Preserves
|
|
20
|
+
* #1089's tri-state rather than collapsing it: NOT-OBSERVED throws {@link
|
|
21
|
+
* UnobservedAssertionError}, never a silent pass and never an ordinary
|
|
22
|
+
* failure, because chant could not read the entity is not the same claim
|
|
23
|
+
* as chant read it and it is gone.
|
|
18
24
|
*
|
|
19
25
|
* ## Isolation
|
|
20
26
|
*
|
|
@@ -44,7 +50,7 @@ import { basename, join, resolve } from "node:path";
|
|
|
44
50
|
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
45
51
|
import { tmpdir } from "node:os";
|
|
46
52
|
import { build } from "./build";
|
|
47
|
-
import type
|
|
53
|
+
import { isResourceDeclarable, type Declarable } from "./declarable";
|
|
48
54
|
import type { SerializerResult } from "./serializer";
|
|
49
55
|
import {
|
|
50
56
|
loadChantConfigUpward,
|
|
@@ -55,12 +61,20 @@ import { resolveBuildParams, type BuildParamValue } from "./build-params";
|
|
|
55
61
|
import { ENV_VAR, unknownEnvError } from "./env";
|
|
56
62
|
import { applyLiveEndpoint } from "./live-endpoint";
|
|
57
63
|
import { loadPlugins, resolveProjectLexicons, collectBuildRootContributors } from "./cli/plugins";
|
|
58
|
-
import type { LexiconPlugin } from "./lexicon";
|
|
64
|
+
import type { LexiconPlugin, ResourceMetadata } from "./lexicon";
|
|
59
65
|
import type { OwnershipMarker } from "./ownership";
|
|
60
66
|
import { runOpLocally } from "./op/local-executor";
|
|
61
67
|
import { loadActivities, loadProfiles, type ActivityFn, type ActivityProfile } from "./op/activity-registry";
|
|
62
68
|
import type { OpConfig, ActivityStep } from "./op/types";
|
|
63
69
|
import { executeTeardown, type TeardownReport } from "./lifecycle/teardown";
|
|
70
|
+
import {
|
|
71
|
+
assertLiveEntity,
|
|
72
|
+
LiveAssertionError,
|
|
73
|
+
UnobservedAssertionError,
|
|
74
|
+
type AssertLiveOptions,
|
|
75
|
+
} from "./lifecycle/assert-live";
|
|
76
|
+
|
|
77
|
+
export { LiveAssertionError, UnobservedAssertionError, type AssertLiveOptions };
|
|
64
78
|
|
|
65
79
|
/**
|
|
66
80
|
* Which `nativeApply` target deploys each lexicon's built output. Only these
|
|
@@ -118,6 +132,19 @@ export interface DeployedStack {
|
|
|
118
132
|
entities: Map<string, Declarable>;
|
|
119
133
|
/** The environment this deploy targeted — the teardown key. */
|
|
120
134
|
env: string;
|
|
135
|
+
/**
|
|
136
|
+
* Assert that a declared entity is live: observed present in this
|
|
137
|
+
* deploy's environment, not confirmed as another stack/env's resource,
|
|
138
|
+
* and — when `status` is given — reporting that status.
|
|
139
|
+
*
|
|
140
|
+
* Rides the observation contract (#1089): an entity `describeResources`
|
|
141
|
+
* could not cover throws {@link UnobservedAssertionError}, never a pass
|
|
142
|
+
* and never a plain failure — NOT-OBSERVED is not the same claim as
|
|
143
|
+
* absent. Observed-absent, a confirmed-foreign identity, or a status
|
|
144
|
+
* mismatch throws {@link LiveAssertionError}. Resolves to the entity's
|
|
145
|
+
* `ResourceMetadata` on success.
|
|
146
|
+
*/
|
|
147
|
+
assertLive(name: string, options?: AssertLiveOptions): Promise<ResourceMetadata>;
|
|
121
148
|
/**
|
|
122
149
|
* Tear down everything carrying this suite's marker `{ stack, env }`.
|
|
123
150
|
* Throws {@link TeardownIncompleteError} when any candidate failed to
|
|
@@ -321,6 +348,39 @@ export async function deployStack(options: DeployStackOptions): Promise<Deployed
|
|
|
321
348
|
applied.restore();
|
|
322
349
|
}
|
|
323
350
|
|
|
351
|
+
const assertLive = async (name: string, assertOptions: AssertLiveOptions = {}): Promise<ResourceMetadata> => {
|
|
352
|
+
const entity = result.entities.get(name);
|
|
353
|
+
if (!entity) {
|
|
354
|
+
throw new LiveAssertionError(
|
|
355
|
+
`assertLive("${name}"): no such entity — this deploy built ${[...result.entities.keys()].join(", ") || "nothing"}.`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const entityPlugin = plugins.find((p) => p.name === entity.lexicon);
|
|
359
|
+
if (!entityPlugin) {
|
|
360
|
+
throw new LiveAssertionError(`assertLive("${name}"): no loaded lexicon named "${entity.lexicon}".`);
|
|
361
|
+
}
|
|
362
|
+
const entityOutput = result.outputs.get(entity.lexicon);
|
|
363
|
+
const entityBuildOutput =
|
|
364
|
+
entityOutput === undefined ? "" : typeof entityOutput === "string" ? entityOutput : entityOutput.primary;
|
|
365
|
+
const props = isResourceDeclarable(entity) ? ((entity.props ?? {}) as Record<string, unknown>) : {};
|
|
366
|
+
|
|
367
|
+
const endpointForAssert = applyLiveEndpoint(config.environments, env, plugins);
|
|
368
|
+
try {
|
|
369
|
+
return await assertLiveEntity({
|
|
370
|
+
plugin: entityPlugin,
|
|
371
|
+
name,
|
|
372
|
+
entityType: entity.entityType,
|
|
373
|
+
props,
|
|
374
|
+
buildOutput: entityBuildOutput,
|
|
375
|
+
environment: env,
|
|
376
|
+
marker,
|
|
377
|
+
...assertOptions,
|
|
378
|
+
});
|
|
379
|
+
} finally {
|
|
380
|
+
endpointForAssert.restore();
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
324
384
|
const destroy = async (): Promise<TeardownReport> => {
|
|
325
385
|
const endpointForTeardown = applyLiveEndpoint(config.environments, env, plugins);
|
|
326
386
|
let report: TeardownReport;
|
|
@@ -334,5 +394,5 @@ export async function deployStack(options: DeployStackOptions): Promise<Deployed
|
|
|
334
394
|
return report;
|
|
335
395
|
};
|
|
336
396
|
|
|
337
|
-
return { outputs: result.outputs, entities: result.entities, env, destroy };
|
|
397
|
+
return { outputs: result.outputs, entities: result.entities, env, assertLive, destroy };
|
|
338
398
|
}
|