@intentius/chant 0.46.0 → 0.50.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 +30 -3
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +9 -2
- 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 +3 -3
- 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/commands/lint.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 +12 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/main.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/tools/explain.d.ts +6 -0
- package/dist/cli/mcp/tools/explain.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/plugins.d.ts +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +14 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/reporters/stylish.d.ts +15 -1
- package/dist/cli/reporters/stylish.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +4 -0
- package/dist/components/auto-release.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/starter-plugin.d.ts +2 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/ensure-secret.d.ts +50 -0
- package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +13 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts +76 -0
- package/dist/components/verbs/r2-sync.d.ts.map +1 -0
- 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/components/verbs/wrangler.d.ts +108 -0
- package/dist/components/verbs/wrangler.d.ts.map +1 -0
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +14 -0
- package/dist/deep-observation.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/effect-receipt.d.ts +177 -0
- package/dist/effect-receipt.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 +20 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.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 +137 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +33 -5
- 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/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/observation-baseline.d.ts +21 -3
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/receipt-plan.d.ts +62 -0
- package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +20 -0
- package/dist/lifecycle/release-ledger.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/lifecycle/teardown.d.ts +6 -4
- package/dist/lifecycle/teardown.d.ts.map +1 -1
- package/dist/lifecycle/unobserved-gate.d.ts +67 -0
- package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
- package/dist/lint/knowledge-checks.d.ts +48 -0
- package/dist/lint/knowledge-checks.d.ts.map +1 -0
- package/dist/lint/output-checks.d.ts +5 -0
- package/dist/lint/output-checks.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/pipeline-change-gate.d.ts +101 -0
- package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +41 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/receipt-checks.d.ts +9 -0
- package/dist/lint/receipt-checks.d.ts.map +1 -0
- 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/lint/rules/cor022-receipt-leaf.d.ts +13 -0
- package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +3 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/okf-read.d.ts +78 -0
- package/dist/okf-read.d.ts.map +1 -0
- 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 +140 -3
- 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 +18 -2
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +2 -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/receipt-store.d.ts +138 -0
- package/dist/op/receipt-store.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 +49 -2
- 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/secret-materialization.d.ts +138 -0
- package/dist/secret-materialization.d.ts.map +1 -0
- package/dist/secret-provenance.d.ts +218 -0
- package/dist/secret-provenance.d.ts.map +1 -0
- package/dist/serializer.d.ts +29 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.test.ts +57 -0
- package/src/audit/core.ts +0 -0
- package/src/audit/detect-bundle.test.ts +1 -1
- package/src/audit/discover.test.ts +24 -0
- package/src/audit/discover.ts +40 -4
- 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 +13 -1
- 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.test.ts +41 -0
- package/src/build.ts +39 -6
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
- package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
- package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +268 -1
- package/src/cli/commands/audit.ts +87 -18
- package/src/cli/commands/build.test.ts +245 -0
- package/src/cli/commands/build.ts +210 -21
- package/src/cli/commands/lint.ts +15 -3
- 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/explain.test.ts +70 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +12 -3
- package/src/cli/handlers/lifecycle.test.ts +115 -1
- package/src/cli/handlers/lifecycle.ts +96 -15
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +72 -1
- package/src/cli/mcp/resource-handlers.ts +38 -1
- package/src/cli/mcp/server.test.ts +323 -3
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/tools/explain.ts +51 -2
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/plugins.ts +4 -2
- package/src/cli/registry.ts +14 -2
- package/src/cli/reporters/stylish.test.ts +154 -0
- package/src/cli/reporters/stylish.ts +154 -33
- 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/auto-release.ts +6 -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/registry.test.ts +7 -2
- package/src/components/starter-plugin.ts +17 -0
- package/src/components/verbs/ensure-secret.test.ts +130 -0
- package/src/components/verbs/ensure-secret.ts +79 -0
- package/src/components/verbs/index.ts +13 -0
- package/src/components/verbs/r2-sync.test.ts +107 -0
- package/src/components/verbs/r2-sync.ts +124 -0
- 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/components/verbs/wrangler.test.ts +170 -0
- package/src/components/verbs/wrangler.ts +241 -0
- package/src/composite.ts +31 -2
- package/src/config.test.ts +15 -0
- package/src/config.ts +30 -0
- package/src/deep-observation.test.ts +19 -0
- package/src/deep-observation.ts +17 -0
- 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/effect-receipt-exclusion.test.ts +190 -0
- package/src/effect-receipt.test.ts +419 -0
- package/src/effect-receipt.ts +412 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +95 -6
- package/src/fold/subset.ts +66 -2
- package/src/index.ts +4 -0
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +151 -5
- package/src/lifecycle/change-set.ts +46 -7
- 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/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/observation-baseline.test.ts +46 -0
- package/src/lifecycle/observation-baseline.ts +33 -1
- package/src/lifecycle/receipt-plan.test.ts +250 -0
- package/src/lifecycle/receipt-plan.ts +249 -0
- package/src/lifecycle/release-ledger.ts +20 -0
- 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/lifecycle/teardown.test.ts +31 -0
- package/src/lifecycle/teardown.ts +6 -4
- package/src/lifecycle/unobserved-gate.test.ts +109 -0
- package/src/lifecycle/unobserved-gate.ts +102 -0
- package/src/lint/knowledge-checks.test.ts +80 -0
- package/src/lint/knowledge-checks.ts +74 -0
- package/src/lint/output-checks.test.ts +85 -0
- package/src/lint/output-checks.ts +99 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/pipeline-change-gate.test.ts +144 -0
- package/src/lint/pipeline-change-gate.ts +153 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +60 -0
- package/src/lint/receipt-checks.test.ts +101 -0
- package/src/lint/receipt-checks.ts +93 -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/cor022-receipt-leaf.test.ts +116 -0
- package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
- package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
- package/src/lint/rules/index.ts +7 -1
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/okf-read.test.ts +149 -0
- package/src/okf-read.ts +197 -0
- 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 +198 -6
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/effect-step.test.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 +40 -3
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +212 -29
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/op.test.ts +25 -2
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/receipt-store.ts +211 -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 +51 -2
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/secret-materialization.test.ts +199 -0
- package/src/secret-materialization.ts +235 -0
- package/src/secret-provenance.test.ts +388 -0
- package/src/secret-provenance.ts +475 -0
- package/src/serializer.ts +30 -0
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
- package/src/yaml.test.ts +88 -0
- package/src/yaml.ts +76 -6
package/src/fold/subset.test.ts
CHANGED
|
@@ -74,10 +74,12 @@ const SUPPORTED_CASES: SubsetCase[] = [
|
|
|
74
74
|
|
|
75
75
|
const UNSUPPORTED_CASES: SubsetCase[] = [
|
|
76
76
|
{ name: "function call as a value", expr: `getName()` },
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
77
|
+
// chant #1966 moved "method call as a value" and "registered helper name
|
|
78
|
+
// reached as a method" out of this strict-parity list and into "documented
|
|
79
|
+
// divergences" below — a method call now folds when its receiver resolves,
|
|
80
|
+
// which is exactly the identifier-resolution asymmetry every other
|
|
81
|
+
// divergence there already documents.
|
|
82
|
+
//
|
|
81
83
|
// ...and a registered helper with an unfoldable argument is rejected on the
|
|
82
84
|
// argument, by both sides, at the argument's own position.
|
|
83
85
|
{ name: "registered helper with an unfoldable argument", expr: `phase("Apply", [getName()])` },
|
|
@@ -258,6 +260,89 @@ describe("documented divergences — NOT unified by design (see subset.ts module
|
|
|
258
260
|
expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
|
|
259
261
|
});
|
|
260
262
|
|
|
263
|
+
test("method call on an unresolved receiver: fold rejects; EVL001 accepts the shape (chant #1966, resolution-dependent)", () => {
|
|
264
|
+
// `config.getName()` — chant #1966 widened `findSubsetViolation` to
|
|
265
|
+
// accept a method call unconditionally (mirroring `.step` and a nested
|
|
266
|
+
// `new` before it), because whether the receiver resolves to a value
|
|
267
|
+
// with that method is resolution-dependent (module doc, point 1) and out
|
|
268
|
+
// of reach for a syntax-only classifier. fold() still rejects it here on
|
|
269
|
+
// the receiver: `config` isn't a `const` this file has.
|
|
270
|
+
const source = `const bad = new Thing({ x: config.getName() });`;
|
|
271
|
+
const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
|
|
272
|
+
const consts = collectConsts(sourceFile);
|
|
273
|
+
const badInit = consts.get("bad") as ts.NewExpression;
|
|
274
|
+
|
|
275
|
+
expect(() => foldResource(badInit, consts, [])).toThrow(FoldError);
|
|
276
|
+
|
|
277
|
+
const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
|
|
278
|
+
expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("registered helper name reached as a method: not the registered call, on either side — rejected for a different reason since #1966", () => {
|
|
282
|
+
// chant #1082 — a registered helper NAME reached through a namespace is
|
|
283
|
+
// still a method call, never the bare registered call: `fold()`'s
|
|
284
|
+
// `{__helper}` shape only ever matches a BARE identifier callee, so
|
|
285
|
+
// `helpers.phase(...)` never gets that treatment, before or after #1966.
|
|
286
|
+
// What changed is WHY it's rejected — `helpers` is an unresolved
|
|
287
|
+
// identifier now reached through the general method-call case, not "no
|
|
288
|
+
// case for a property-access callee" — same divergence direction as the
|
|
289
|
+
// test above.
|
|
290
|
+
const source = `const bad = new Thing({ x: helpers.phase("Apply", []) });`;
|
|
291
|
+
const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
|
|
292
|
+
const consts = collectConsts(sourceFile);
|
|
293
|
+
const badInit = consts.get("bad") as ts.NewExpression;
|
|
294
|
+
|
|
295
|
+
let error: unknown;
|
|
296
|
+
try {
|
|
297
|
+
foldResource(badInit, consts, []);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
error = e;
|
|
300
|
+
}
|
|
301
|
+
expect(error).toBeInstanceOf(FoldError);
|
|
302
|
+
expect((error as FoldError).message).toContain("unresolved identifier: helpers");
|
|
303
|
+
|
|
304
|
+
const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
|
|
305
|
+
expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("composite step access: NO LONGER a divergence (chant #1174) — both fold and EVL001 accept it", () => {
|
|
309
|
+
// `Checkout({...}).step` — the single-action Composite() wrapper idiom
|
|
310
|
+
// every lexicon's own docs/examples embed inline inside a Job's
|
|
311
|
+
// `steps:` array. This was a documented, EVL-only-permissive divergence
|
|
312
|
+
// from chant #1544 until #1174: `fold()` had no way to invoke an
|
|
313
|
+
// arbitrary composite factory, so it fell the file back to run
|
|
314
|
+
// (correct, not an error) while EVL001 already opted into treating the
|
|
315
|
+
// shape as valid via a since-removed `allowCompositeStepAccess` flag.
|
|
316
|
+
//
|
|
317
|
+
// fold() now folds this to a `{__compositeStep, args}` envelope
|
|
318
|
+
// (../fold/fold.ts) that ../discovery/fold-import.ts's bridge resolves
|
|
319
|
+
// through the folding file's own imports — interpreted when the callee
|
|
320
|
+
// is a project-file registered Composite (chant #1023), invoked for
|
|
321
|
+
// real otherwise (every lexicon-package composite, which is what
|
|
322
|
+
// `Checkout` is) — and reads `.step` off the REAL result. Kept as a
|
|
323
|
+
// test rather than deleted: the assertion that the divergence stays
|
|
324
|
+
// closed.
|
|
325
|
+
const source = `const bad = new Thing({ x: Checkout({}).step });`;
|
|
326
|
+
const sourceFile = ts.createSourceFile("t.ts", source, ts.ScriptTarget.Latest, true);
|
|
327
|
+
const consts = collectConsts(sourceFile);
|
|
328
|
+
const badInit = consts.get("bad") as ts.NewExpression;
|
|
329
|
+
|
|
330
|
+
expect(foldResource(badInit, consts, [])).toEqual({
|
|
331
|
+
__resource: "Thing",
|
|
332
|
+
props: { x: { __compositeStep: "Checkout", args: [{}] } },
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// findSubsetViolation (fold()'s own shared predicate, no opt-in needed
|
|
336
|
+
// anymore) agrees the shape is admissible.
|
|
337
|
+
const stepAccessExpr = (
|
|
338
|
+
(badInit.arguments![0] as ts.ObjectLiteralExpression).properties[0] as ts.PropertyAssignment
|
|
339
|
+
).initializer;
|
|
340
|
+
expect(findSubsetViolation(stepAccessExpr)).toBeUndefined();
|
|
341
|
+
|
|
342
|
+
const context: LintContext = { sourceFile, entities: [], filePath: "t.ts", lexicon: undefined };
|
|
343
|
+
expect(evl001NonLiteralExpressionRule.check(context)).toHaveLength(0);
|
|
344
|
+
});
|
|
345
|
+
|
|
261
346
|
test("nested resource construction: NO LONGER a divergence (chant #1169) — both fold and EVL001 accept it", () => {
|
|
262
347
|
// This was the largest divergence in the table until #1169: a nested
|
|
263
348
|
// `new Type()` as a property value could only fold to a {__resource, props}
|
|
@@ -352,10 +437,14 @@ describe("findSubsetViolation — optional intrinsic registry (#1044)", () => {
|
|
|
352
437
|
expect(v?.message).toContain("function call as a value is not foldable: Ref(...)");
|
|
353
438
|
});
|
|
354
439
|
|
|
355
|
-
test("the registry doesn't widen anything else: a name in it without the opt-in, a method
|
|
440
|
+
test("the registry doesn't widen anything else: a name in it without the opt-in, a closure-taking method, and a plain call stay violations", () => {
|
|
356
441
|
const notOptedIn: IntrinsicDef[] = [{ name: "Reference", isTag: false }];
|
|
357
442
|
expect(findSubsetViolation(propValue(`Reference("db")`), notOptedIn)).toBeDefined();
|
|
358
|
-
|
|
443
|
+
// chant #1966 — `aws.Ref("db")` is a method call (`Ref` reached through a
|
|
444
|
+
// namespace, not a bare identifier), so it's no longer here: shape-wise
|
|
445
|
+
// it's now the same resolution-dependent divergence
|
|
446
|
+
// "method call on an unresolved receiver" documents above, unrelated to
|
|
447
|
+
// whether `Ref` itself is opted into `foldsAsCall`.
|
|
359
448
|
expect(findSubsetViolation(propValue(`cidrs.map((c) => c)`, `const cidrs = [];`), REF)).toBeDefined();
|
|
360
449
|
expect(findSubsetViolation(propValue(`makeName("a")`), REF)).toBeDefined();
|
|
361
450
|
});
|
package/src/fold/subset.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import { isFoldableHelperName } from "./foldable-helpers";
|
|
3
|
-
import { intrinsicCallFolds, type IntrinsicDef } from "../lexicon";
|
|
3
|
+
import { intrinsicCallFolds, intrinsicCallFoldsEagerly, type IntrinsicDef } from "../lexicon";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* subset — the single canonical definition of chant's statically-foldable
|
|
@@ -265,6 +265,26 @@ function checkArrayElement(
|
|
|
265
265
|
* environment-dependent exceptions). Returns the first (deepest,
|
|
266
266
|
* `fold()`-evaluation-order) unsupported node, or `undefined` when `node`'s
|
|
267
267
|
* whole shape is foldable.
|
|
268
|
+
*
|
|
269
|
+
* The `<call>(...).step` composite-consumer idiom (`Checkout({...}).step`,
|
|
270
|
+
* every lexicon's single-action `Composite()` wrapper embedded inline in a
|
|
271
|
+
* `Job`'s `steps` array — see composites.mdx) used to be an EVL-only,
|
|
272
|
+
* MORE-permissive divergence here (chant #1544's `allowCompositeStepAccess`
|
|
273
|
+
* opt-in parameter, since removed): `fold()` had no way to invoke an
|
|
274
|
+
* arbitrary composite factory, so it fell the file back to run for this
|
|
275
|
+
* shape (documented, correct behavior, never an error) while this shared
|
|
276
|
+
* predicate had to be told to stop treating that fallback as a lint error.
|
|
277
|
+
* chant #1174 closed the divergence — `fold()` now folds exactly this shape
|
|
278
|
+
* (narrowed to the literal member name `"step"`, the one idiom actually in
|
|
279
|
+
* use) — so this classifier accepts it unconditionally, the same way it
|
|
280
|
+
* already accepts a nested `new Type(...)` unconditionally since #1169.
|
|
281
|
+
*
|
|
282
|
+
* chant #1966 adds a CallExpression whose callee is a property access —
|
|
283
|
+
* `github.actor.toString()`, `matrix("os").toString()`, `[...].join(",")` —
|
|
284
|
+
* to the same unconditional-acceptance set: whether the receiver actually
|
|
285
|
+
* folds to a value with a callable method of that name is resolution-
|
|
286
|
+
* dependent (module doc, point 1), so this classifier stays permissive and
|
|
287
|
+
* only recurses into shape.
|
|
268
288
|
*/
|
|
269
289
|
export function findSubsetViolation(
|
|
270
290
|
node: ts.Node,
|
|
@@ -334,6 +354,13 @@ export function findSubsetViolation(
|
|
|
334
354
|
}
|
|
335
355
|
|
|
336
356
|
if (ts.isPropertyAccessExpression(node)) {
|
|
357
|
+
// chant #1174 — see this function's doc comment. `<call>(...).step`
|
|
358
|
+
// (any callee, any argument shape) is the composite-consumer idiom, and
|
|
359
|
+
// `fold()` now folds it too (narrowed to the literal member `"step"` —
|
|
360
|
+
// see fold.ts's PropertyAccessExpression branch).
|
|
361
|
+
if (node.name.text === "step" && ts.isCallExpression(node.expression)) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
337
364
|
return findSubsetViolation(node.expression, intrinsics);
|
|
338
365
|
}
|
|
339
366
|
|
|
@@ -358,7 +385,10 @@ export function findSubsetViolation(
|
|
|
358
385
|
}
|
|
359
386
|
// Flow-insensitive — see module doc: fold() short-circuits &&/||/?? and
|
|
360
387
|
// only evaluates the taken side; EVL requires both sides shape-valid.
|
|
361
|
-
return
|
|
388
|
+
return (
|
|
389
|
+
findSubsetViolation(node.left, intrinsics) ??
|
|
390
|
+
findSubsetViolation(node.right, intrinsics)
|
|
391
|
+
);
|
|
362
392
|
}
|
|
363
393
|
|
|
364
394
|
if (ts.isConditionalExpression(node)) {
|
|
@@ -426,6 +456,40 @@ export function findSubsetViolation(
|
|
|
426
456
|
return undefined;
|
|
427
457
|
}
|
|
428
458
|
|
|
459
|
+
// chant #1966 — the eager counterpart of the #1044 case above: a lexicon
|
|
460
|
+
// intrinsic registered with `foldsEagerly` instead of `foldsAsCall`
|
|
461
|
+
// ({@link intrinsicCallFoldsEagerly}, ../lexicon.ts). Same registry-gated
|
|
462
|
+
// shape, same reasoning.
|
|
463
|
+
if (
|
|
464
|
+
intrinsics &&
|
|
465
|
+
ts.isIdentifier(node.expression) &&
|
|
466
|
+
intrinsics.some((i) => i.name === (node.expression as ts.Identifier).text && intrinsicCallFoldsEagerly(i))
|
|
467
|
+
) {
|
|
468
|
+
for (const arg of node.arguments) {
|
|
469
|
+
const v = findSubsetViolation(arg, intrinsics);
|
|
470
|
+
if (v) return v;
|
|
471
|
+
}
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// chant #1966 — a method call (`github.actor.toString()`, `[...].join(",")`,
|
|
476
|
+
// `matrix("os").toString()`): `fold()` now folds this shape unconditionally
|
|
477
|
+
// — the receiver must fold to a real value with the named method, which is
|
|
478
|
+
// resolution-dependent (module doc, point 1) and so out of reach for a
|
|
479
|
+
// syntax-only classifier, exactly the same asymmetry as `.step` above and
|
|
480
|
+
// a same-file `new` used as a value (#1169). Accepted here regardless of
|
|
481
|
+
// the intrinsics registry, unlike the two cases just above: nothing about
|
|
482
|
+
// whether a method call folds depends on a lexicon's registration.
|
|
483
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
484
|
+
const v = findSubsetViolation(node.expression.expression, intrinsics);
|
|
485
|
+
if (v) return v;
|
|
486
|
+
for (const arg of node.arguments) {
|
|
487
|
+
const argViolation = findSubsetViolation(arg, intrinsics);
|
|
488
|
+
if (argViolation) return argViolation;
|
|
489
|
+
}
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
429
493
|
return violation(node, callExpressionMessage(node));
|
|
430
494
|
}
|
|
431
495
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
export * from "./declarable";
|
|
8
8
|
export * from "./composite";
|
|
9
9
|
export * from "./provenance";
|
|
10
|
+
export * from "./secret-provenance";
|
|
11
|
+
export * from "./secret-materialization";
|
|
12
|
+
export * from "./effect-receipt";
|
|
10
13
|
export * from "./build-params";
|
|
11
14
|
export * from "./intrinsic";
|
|
12
15
|
export * from "./types";
|
|
@@ -43,6 +46,7 @@ export * from "./lint/selectors";
|
|
|
43
46
|
export * from "./lint/named-checks";
|
|
44
47
|
export * from "./lint/post-synth";
|
|
45
48
|
export * from "./lint/policy";
|
|
49
|
+
export * from "./lint/pipeline-change-gate";
|
|
46
50
|
export * from "./lint/rule-loader";
|
|
47
51
|
export * from "./lint/discover";
|
|
48
52
|
export * from "./import/parser";
|
package/src/lexicon-schema.ts
CHANGED
|
@@ -21,6 +21,9 @@ export const IntrinsicDefSchema = z.object({
|
|
|
21
21
|
// registration written before #1044 means. Only an explicit `true` opts a
|
|
22
22
|
// call into folding. See IntrinsicDef.foldsAsCall in ../lexicon.ts.
|
|
23
23
|
foldsAsCall: z.boolean().optional(),
|
|
24
|
+
// chant #1966 — same default-off shape as foldsAsCall, for the EAGER
|
|
25
|
+
// call-form opt-in. See IntrinsicDef.foldsEagerly in ../lexicon.ts.
|
|
26
|
+
foldsEagerly: z.boolean().optional(),
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
// ---------------------------------------------------------------------------
|
package/src/lexicon.ts
CHANGED
|
@@ -226,6 +226,29 @@ export interface IntrinsicDef {
|
|
|
226
226
|
* run path would have called, from the module the source itself named.
|
|
227
227
|
*/
|
|
228
228
|
readonly foldsAsCall?: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* chant #1966 — opt this intrinsic's PLAIN-CALL form into EAGER folding:
|
|
231
|
+
* unlike {@link foldsAsCall}, which defers to a `{__intrinsic}` envelope
|
|
232
|
+
* revived later, `fold()` (../fold/fold.ts) calls the real function
|
|
233
|
+
* immediately, with the folded arguments, and its real return value IS the
|
|
234
|
+
* folded value.
|
|
235
|
+
*
|
|
236
|
+
* Exists for a lexicon helper whose result is commonly embedded directly in
|
|
237
|
+
* a template literal (`` `${matrix("os")}` ``, github's
|
|
238
|
+
* `matrix`/`inputs`/…) — an envelope is still a plain `{__intrinsic, args}`
|
|
239
|
+
* object when the enclosing template folds, so `String()` coercion at fold
|
|
240
|
+
* time would stringify it as `"[object Object]"` instead of the real value.
|
|
241
|
+
* Eager evaluation produces the real, already-live result instead.
|
|
242
|
+
*
|
|
243
|
+
* Same admission bar as {@link foldsAsCall} (a pure function of its
|
|
244
|
+
* arguments — no I/O, no mutable state, no dependency on anything but its
|
|
245
|
+
* arguments) and mutually exclusive with both {@link isTag} and
|
|
246
|
+
* `foldsAsCall`: a plain call folds exactly one way.
|
|
247
|
+
* `../discovery/fold-import.ts`'s `resolveActiveLexiconExport` checks this
|
|
248
|
+
* to admit the function export into `externals` at all — a lexicon
|
|
249
|
+
* package's function export is excluded there otherwise.
|
|
250
|
+
*/
|
|
251
|
+
readonly foldsEagerly?: boolean;
|
|
229
252
|
}
|
|
230
253
|
|
|
231
254
|
/**
|
|
@@ -257,8 +280,8 @@ export interface IntrinsicDef {
|
|
|
257
280
|
* `dist/manifest.json`) that may predate either field. `undefined` there
|
|
258
281
|
* means what it always did — not a tag, not opted in.
|
|
259
282
|
*/
|
|
260
|
-
export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean }): boolean {
|
|
261
|
-
return intrinsicTagFolds(def) || intrinsicCallFolds(def);
|
|
283
|
+
export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean; foldsEagerly?: boolean }): boolean {
|
|
284
|
+
return intrinsicTagFolds(def) || intrinsicCallFolds(def) || intrinsicCallFoldsEagerly(def);
|
|
262
285
|
}
|
|
263
286
|
|
|
264
287
|
/** True when this intrinsic's TAGGED-TEMPLATE form folds (`Sub\`...\``) — see {@link intrinsicFolds}. */
|
|
@@ -280,6 +303,15 @@ export function intrinsicCallFolds(def: { isTag?: boolean; foldsAsCall?: boolean
|
|
|
280
303
|
return def.isTag !== true && def.foldsAsCall === true;
|
|
281
304
|
}
|
|
282
305
|
|
|
306
|
+
/**
|
|
307
|
+
* True when this intrinsic's PLAIN-CALL form folds EAGERLY (chant #1966) —
|
|
308
|
+
* i.e. the lexicon opted it in via {@link IntrinsicDef.foldsEagerly}.
|
|
309
|
+
* `isTag: true` disqualifies, same as {@link intrinsicCallFolds}.
|
|
310
|
+
*/
|
|
311
|
+
export function intrinsicCallFoldsEagerly(def: { isTag?: boolean; foldsEagerly?: boolean }): boolean {
|
|
312
|
+
return def.isTag !== true && def.foldsEagerly === true;
|
|
313
|
+
}
|
|
314
|
+
|
|
283
315
|
/**
|
|
284
316
|
* Options passed to a MigrationSource by `chant migrate`.
|
|
285
317
|
*/
|
|
@@ -406,6 +438,61 @@ export interface ComponentPipelineResult {
|
|
|
406
438
|
jobs: ComponentPipelineJob[];
|
|
407
439
|
}
|
|
408
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Finding-mode a scheduled Op surfaces on drift — the vocabulary the
|
|
443
|
+
* temporal-lexicon composites (`WorkflowAuditOp`/`PipelineAuditOp`/
|
|
444
|
+
* `ReconcileOp`) already declare on the Op itself (#927). The mode is baked
|
|
445
|
+
* into the Op's own activity args at build time and is never re-passed on the
|
|
446
|
+
* generated CI invocation; here it decides only what token/permission wiring
|
|
447
|
+
* the generated job needs to act on a finding — elevated write access for
|
|
448
|
+
* `issue`/`pull-request`/`merge-request`, none for `report`.
|
|
449
|
+
*/
|
|
450
|
+
export type OpFindingMode = "report" | "issue" | "pull-request" | "merge-request";
|
|
451
|
+
|
|
452
|
+
/** One scheduled Op to generate CI for — the cron-triggered counterpart to a component (generate mode). */
|
|
453
|
+
export interface ScheduledOpSpec {
|
|
454
|
+
/** Op name (`*.op.ts`'s `Op({ name })`) — what `chant run <name>` targets. */
|
|
455
|
+
name: string;
|
|
456
|
+
/** Cron expression driving the CI-native trigger — the CI-only alternative to a Temporal `TemporalSchedule`. */
|
|
457
|
+
schedule: string;
|
|
458
|
+
/** This Op's finding-mode, for permission/token wiring only (see {@link OpFindingMode}). Default: "report" — no elevated permissions. */
|
|
459
|
+
findingMode?: OpFindingMode;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** One CI job generated for a scheduled Op. */
|
|
463
|
+
export interface OpPipelineJob {
|
|
464
|
+
/** CI job name (safe as a YAML key). */
|
|
465
|
+
jobName: string;
|
|
466
|
+
/** The Op this job runs. */
|
|
467
|
+
op: string;
|
|
468
|
+
/** Cron expression this job's workflow is scheduled on. */
|
|
469
|
+
schedule: string;
|
|
470
|
+
/** The finding-mode wired for this job. */
|
|
471
|
+
findingMode: OpFindingMode;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* One generated CI file for a scheduled Op. Unlike a component pipeline —
|
|
476
|
+
* one combined document for the whole graph — a cron trigger is
|
|
477
|
+
* workflow-scoped in every CI provider chant targets (GitHub Actions'
|
|
478
|
+
* `on.schedule`, a GitLab pipeline schedule bound to one `.gitlab-ci.yml`), so
|
|
479
|
+
* each scheduled Op gets its own file.
|
|
480
|
+
*/
|
|
481
|
+
export interface OpPipelineFile {
|
|
482
|
+
/** Suggested file name (e.g. `<op-name>.yml`), relative to the provider's workflow directory. */
|
|
483
|
+
name: string;
|
|
484
|
+
/** The synthesized CI YAML for this Op. */
|
|
485
|
+
yaml: string;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** The synthesized CI pipeline for a set of scheduled Ops (generate mode's Op counterpart, #927). */
|
|
489
|
+
export interface OpPipelineResult {
|
|
490
|
+
/** One file per scheduled Op. */
|
|
491
|
+
files: OpPipelineFile[];
|
|
492
|
+
/** Every generated job, in emit order — one per Op. */
|
|
493
|
+
jobs: OpPipelineJob[];
|
|
494
|
+
}
|
|
495
|
+
|
|
409
496
|
/**
|
|
410
497
|
* Live status of a single deploy unit (a CloudFormation stack, a K8s release, …)
|
|
411
498
|
* addressed by its deployed name — the per-component presence signal
|
|
@@ -439,6 +526,24 @@ export interface BuildRootContext {
|
|
|
439
526
|
projectRoot: string;
|
|
440
527
|
/** The resolved project configuration, for the lexicon's own namespace. */
|
|
441
528
|
config: Record<string, unknown>;
|
|
529
|
+
/**
|
|
530
|
+
* The discovered entities, read-only (#1828 / SOPS provenance). A
|
|
531
|
+
* contributor that reacts to what the project DECLARED — rather than to
|
|
532
|
+
* what its config listed — reads them here: the committed-encrypted
|
|
533
|
+
* `declareSecret()` hook resolves each declaration's `file` into a sidecar
|
|
534
|
+
* entity, and any future declaration-driven contributor needs the same.
|
|
535
|
+
*
|
|
536
|
+
* Read-only on purpose. The merge that follows is the only thing that adds
|
|
537
|
+
* to the entity set, and it refuses a name collision rather than
|
|
538
|
+
* overwriting; a contributor mutating the map directly would slip past
|
|
539
|
+
* that. Contributors run in order, so a contributor also sees entities
|
|
540
|
+
* earlier contributors added.
|
|
541
|
+
*
|
|
542
|
+
* Optional: a caller that has no entity set (a plugin hook invoked
|
|
543
|
+
* directly, a graph mode that never discovered) omits it, and a hook must
|
|
544
|
+
* treat an absent map as an empty one.
|
|
545
|
+
*/
|
|
546
|
+
entities?: ReadonlyMap<string, Declarable>;
|
|
442
547
|
}
|
|
443
548
|
|
|
444
549
|
/**
|
|
@@ -450,6 +555,17 @@ export interface BuildRootContribution {
|
|
|
450
555
|
warnings?: string[];
|
|
451
556
|
}
|
|
452
557
|
|
|
558
|
+
/**
|
|
559
|
+
* A plugin's `buildRoots` hook, already bound to this invocation's project
|
|
560
|
+
* root and config (`collectBuildRootContributors`, ./cli/plugins.ts). What is
|
|
561
|
+
* NOT bindable that early is the entity set — discovery has not run yet — so
|
|
562
|
+
* the merge supplies it when it calls the closure. A contributor that ignores
|
|
563
|
+
* the argument is still assignable, which is what every pre-#1828 hook does.
|
|
564
|
+
*/
|
|
565
|
+
export type BuildRootContributor = (
|
|
566
|
+
ctx: Pick<BuildRootContext, "entities">,
|
|
567
|
+
) => Promise<BuildRootContribution>;
|
|
568
|
+
|
|
453
569
|
export interface LexiconPlugin {
|
|
454
570
|
// ── Required ──────────────────────────────────────────────
|
|
455
571
|
/** Human-readable name (e.g. "aws", "gcp") */
|
|
@@ -540,6 +656,17 @@ export interface LexiconPlugin {
|
|
|
540
656
|
*/
|
|
541
657
|
auditCatalog?(): Record<string, RuleMeta>;
|
|
542
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Parse standalone template content (a file audit discovery classified for
|
|
661
|
+
* this lexicon) into the lexicon's entity graph, keyed the way `ctx.entities`
|
|
662
|
+
* is during a build. Lets entity-reading post-synth checks fire on
|
|
663
|
+
* `chant audit` of hand-written manifests — parse-to-graph rather than
|
|
664
|
+
* output-reading rule variants (#1567). Implementations must tolerate
|
|
665
|
+
* arbitrary external content: a malformed document yields no entities, never
|
|
666
|
+
* a throw. Omit for lexicons whose audit checks read `ctx.outputs`.
|
|
667
|
+
*/
|
|
668
|
+
auditEntities?(content: string): Map<string, Declarable>;
|
|
669
|
+
|
|
543
670
|
/**
|
|
544
671
|
* Machine-readable spec-coverage accounting for `check-lexicon` (#1330).
|
|
545
672
|
*
|
|
@@ -600,6 +727,20 @@ export interface LexiconPlugin {
|
|
|
600
727
|
options?: ComponentPipelineOptions,
|
|
601
728
|
): ComponentPipelineResult;
|
|
602
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Generate CI for a set of scheduled, stateless Ops — the cron-triggered
|
|
732
|
+
* sibling of {@link generateComponentPipeline} (#927). Each
|
|
733
|
+
* `ScheduledOpSpec` becomes its own CI file (cron triggers are
|
|
734
|
+
* workflow-scoped in every CI provider chant targets), invoking `chant run
|
|
735
|
+
* <name>` on the local executor — the same one-shot invocation `chant run
|
|
736
|
+
* <op>` runs by hand. Only CI-provider lexicons (gitlab, github, forgejo)
|
|
737
|
+
* implement this; omit for lexicons that are not CI providers.
|
|
738
|
+
*/
|
|
739
|
+
generateOpPipeline?(
|
|
740
|
+
ops: ScheduledOpSpec[],
|
|
741
|
+
options?: ComponentPipelineOptions,
|
|
742
|
+
): OpPipelineResult;
|
|
743
|
+
|
|
603
744
|
/**
|
|
604
745
|
* Render this lexicon's config-declared build roots into entities (#1548
|
|
605
746
|
* piece 3) — the seam for a build root that is NOT typed chant source. The
|
|
@@ -1115,10 +1256,15 @@ export interface TeardownOutcome {
|
|
|
1115
1256
|
/**
|
|
1116
1257
|
* - `deleted` — gone, including already-gone (deletion is idempotent);
|
|
1117
1258
|
* - `failed` — the delete errored (core retries these once);
|
|
1118
|
-
* - `not-prunable` — deliberately not deleted; `detail` says why
|
|
1259
|
+
* - `not-prunable` — deliberately not deleted; `detail` says why;
|
|
1260
|
+
* - `retained` — owned and no longer declared, but deliberately kept
|
|
1261
|
+
* (#1365 decision 5): a `generated-once` secret never enters the prunable
|
|
1262
|
+
* set, because deleting it would destroy the only copy of material chant
|
|
1263
|
+
* never held. Reported loudly, never deleted; deletion is an explicit act
|
|
1264
|
+
* (`kubectl delete`, or a future gated op), never a sweep's.
|
|
1119
1265
|
*/
|
|
1120
|
-
outcome: "deleted" | "failed" | "not-prunable";
|
|
1121
|
-
/** The error for `failed`, the reason for `not-prunable`. */
|
|
1266
|
+
outcome: "deleted" | "failed" | "not-prunable" | "retained";
|
|
1267
|
+
/** The error for `failed`, the reason for `not-prunable`/`retained`. */
|
|
1122
1268
|
detail?: string;
|
|
1123
1269
|
}
|
|
1124
1270
|
|
|
@@ -31,11 +31,30 @@ import { unobservedReasonText, type UnobservedReason } from "../observation";
|
|
|
31
31
|
* not drift, just the runtime doing its job. `runtimeOwner` names the
|
|
32
32
|
* declared entity it belongs to.
|
|
33
33
|
* - `noop` — declared and live with no drift, or already reconciled.
|
|
34
|
+
* - `effect` — a declared effect receipt (#1832) whose live value is absent or
|
|
35
|
+
* differs from the resolved expectation: the effect step will fire. Never a
|
|
36
|
+
* `create` or `update` — the generic apply path is observe-only to receipts,
|
|
37
|
+
* and the `effect()` step is the sole writer (epic #1703, decision 3). Read
|
|
38
|
+
* `effect` for the effect's identity and `effectReason` for why it fires.
|
|
34
39
|
* - `unobserved` — declared, and the lexicon could not look (#1089). Not a
|
|
35
40
|
* proposal at all: it is the plan admitting a hole. Never a create, never a
|
|
36
41
|
* delete. Read `unobservedReason` for which hole.
|
|
37
42
|
*/
|
|
38
|
-
export type ChangeAction = "create" | "update" | "delete" | "adopt" | "runtime" | "noop" | "unobserved";
|
|
43
|
+
export type ChangeAction = "create" | "update" | "delete" | "adopt" | "runtime" | "noop" | "effect" | "unobserved";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Why an `effect` entry proposes a fire (#1832).
|
|
47
|
+
*
|
|
48
|
+
* - `receipt-absent` — the provider confirmed the receipt absent: the effect
|
|
49
|
+
* has never recorded a run (or the run crashed before the write — the
|
|
50
|
+
* at-least-once case this classification exists to preserve).
|
|
51
|
+
* - `receipt-stale` — the receipt is live but its value differs from the
|
|
52
|
+
* resolved expectation: the effect's inputs changed since the last run.
|
|
53
|
+
* - `unresolved-input` — a reference input could not resolve at plan time, so
|
|
54
|
+
* the expectation cannot be computed. The fire is proposed rather than
|
|
55
|
+
* guessed away; the effect step resolves again at run.
|
|
56
|
+
*/
|
|
57
|
+
export type EffectFireReason = "receipt-absent" | "receipt-stale" | "unresolved-input";
|
|
39
58
|
|
|
40
59
|
/**
|
|
41
60
|
* Who answers "is this resource chant's?". `unknown` until a live ownership
|
|
@@ -100,6 +119,16 @@ export interface ChangeSetEntry {
|
|
|
100
119
|
queried?: string;
|
|
101
120
|
/** The declared entity this resource's owner chain resolves to, for `action: "runtime"` (#1077). */
|
|
102
121
|
runtimeOwner?: string;
|
|
122
|
+
/**
|
|
123
|
+
* The effect a receipt witnesses (#1832), for entries derived from an effect
|
|
124
|
+
* receipt. On `action: "effect"` it names what will fire; on a receipt's
|
|
125
|
+
* `noop`/`unobserved` rows it keeps the attribution.
|
|
126
|
+
*/
|
|
127
|
+
effect?: string;
|
|
128
|
+
/** Why the effect fires, for `action: "effect"` (#1832). */
|
|
129
|
+
effectReason?: EffectFireReason;
|
|
130
|
+
/** Human-readable backing for `effectReason` (the digests that differ, the unresolved path). */
|
|
131
|
+
effectDetail?: string;
|
|
103
132
|
}
|
|
104
133
|
|
|
105
134
|
export interface ChangeSet {
|
|
@@ -234,13 +263,14 @@ export function buildChangeSet(env: string, input: DiffLiveInput, options?: Chan
|
|
|
234
263
|
return { env, entries };
|
|
235
264
|
}
|
|
236
265
|
|
|
237
|
-
const ACTION_ORDER: ChangeAction[] = ["create", "update", "delete", "adopt", "runtime", "noop", "unobserved"];
|
|
266
|
+
const ACTION_ORDER: ChangeAction[] = ["create", "update", "effect", "delete", "adopt", "runtime", "noop", "unobserved"];
|
|
238
267
|
|
|
239
268
|
/** Count entries per action. */
|
|
240
269
|
export function summarize(cs: ChangeSet): Record<ChangeAction, number> {
|
|
241
270
|
const counts: Record<ChangeAction, number> = {
|
|
242
271
|
create: 0,
|
|
243
272
|
update: 0,
|
|
273
|
+
effect: 0,
|
|
244
274
|
delete: 0,
|
|
245
275
|
adopt: 0,
|
|
246
276
|
runtime: 0,
|
|
@@ -257,10 +287,10 @@ export function summarize(cs: ChangeSet): Record<ChangeAction, number> {
|
|
|
257
287
|
* GitLab renders an `artifacts:reports:terraform` artifact in the merge-request
|
|
258
288
|
* UI as "N to add, M to change, K to delete". The format is generic — any tool
|
|
259
289
|
* that emits this JSON gets the widget — and the chant plan maps onto it
|
|
260
|
-
* directly. Only the mutating actions count: `adopt`, `runtime`, `noop
|
|
261
|
-
* `unobserved` are excluded, since the widget has no column for
|
|
262
|
-
* undeclared", "expected runtime child" (#1077), "no change",
|
|
263
|
-
* look" (#1089). The widget is therefore a floor, not a complete plan: read
|
|
290
|
+
* directly. Only the mutating actions count: `adopt`, `runtime`, `noop`,
|
|
291
|
+
* `effect` and `unobserved` are excluded, since the widget has no column for
|
|
292
|
+
* "live but undeclared", "expected runtime child" (#1077), "no change", "an
|
|
293
|
+
* effect will fire" (#1832), or "could not look" (#1089). The widget is therefore a floor, not a complete plan: read
|
|
264
294
|
* the full change set when entities are unobserved or classified runtime.
|
|
265
295
|
*
|
|
266
296
|
* The widget label reads "Terraform" regardless of producer; that is GitLab's
|
|
@@ -292,9 +322,18 @@ export function renderChangeSet(cs: ChangeSet): string {
|
|
|
292
322
|
? "\nUNOBSERVED (declared; chant could not read live state — no action proposed):"
|
|
293
323
|
: action === "runtime"
|
|
294
324
|
? "\nRUNTIME (owned by a declared resource; not drift, never a delete/adopt candidate):"
|
|
295
|
-
:
|
|
325
|
+
: action === "effect"
|
|
326
|
+
? "\nEFFECT (receipt absent or stale; the effect step fires — the generic apply never writes a receipt):"
|
|
327
|
+
: `\n${action.toUpperCase()}:`,
|
|
296
328
|
);
|
|
297
329
|
for (const e of group) {
|
|
330
|
+
if (e.action === "effect") {
|
|
331
|
+
lines.push(
|
|
332
|
+
` effect will fire: ${e.effect ?? e.name} — receipt ${e.name}${e.type ? ` (${e.type})` : ""}` +
|
|
333
|
+
`${e.effectDetail ? ` — ${e.effectDetail}` : ""}`,
|
|
334
|
+
);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
298
337
|
const own = e.ownership === "unknown" ? "" : ` [${e.ownership}]`;
|
|
299
338
|
const why = e.unobservedReason
|
|
300
339
|
? ` — ${unobservedReasonText(e.unobservedReason)}${e.unobservedDetail ? `: ${e.unobservedDetail}` : ""}`
|