@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
|
@@ -122,6 +122,25 @@ describe("buildProvenanceStatement — SLSA in-toto statement from #614 provenan
|
|
|
122
122
|
}),
|
|
123
123
|
).toThrow(SignTargetNotDigestError);
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
it("reserved parameter keys win over caller-supplied collisions (#1943)", () => {
|
|
127
|
+
const statement = buildProvenanceStatement({
|
|
128
|
+
imageRef: DIGEST_REF,
|
|
129
|
+
provenance: { sourceRef: "abc123def", artifactDigest: DIGEST_REF.split("@")[1]! },
|
|
130
|
+
builderId: "builder",
|
|
131
|
+
externalParameters: { sourceRef: "spoofed", agent: "reviewer" },
|
|
132
|
+
internalParameters: { artifactDigest: "spoofed", spriteId: "s-1" },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(statement.predicate.buildDefinition.externalParameters).toEqual({
|
|
136
|
+
sourceRef: "abc123def",
|
|
137
|
+
agent: "reviewer",
|
|
138
|
+
});
|
|
139
|
+
expect(statement.predicate.buildDefinition.internalParameters).toEqual({
|
|
140
|
+
artifactDigest: DIGEST_REF.split("@")[1]!,
|
|
141
|
+
spriteId: "s-1",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
125
144
|
});
|
|
126
145
|
|
|
127
146
|
describe("attest-provenance — keyless cosign attest (#622)", () => {
|
|
@@ -40,6 +40,19 @@
|
|
|
40
40
|
* `requireTool` throws `ToolNotAvailableError` with an actionable message if
|
|
41
41
|
* `cosign` is absent, and no test here ever spawns a real process or talks
|
|
42
42
|
* to Rekor/Fulcio.
|
|
43
|
+
*
|
|
44
|
+
* **#1943 extension (`run-agent` interop).** `BuildProvenanceStatementInput`
|
|
45
|
+
* gained two optional merge fields, `externalParameters`/`internalParameters`
|
|
46
|
+
* — additive only, every existing call site's statement is byte-identical
|
|
47
|
+
* without them. `run-agent` (./run-agent.ts's `buildRunAgentProvenanceStatement`)
|
|
48
|
+
* is the first caller to use them, plus `buildType` (already overridable,
|
|
49
|
+
* unchanged here) set to a `run-agent`-specific recipe URI. `predicateType`
|
|
50
|
+
* stays `"https://slsa.dev/provenance/v1"` for every caller, `run-agent`
|
|
51
|
+
* included — see ./run-agent.ts's own doc comment ("Attestation interop")
|
|
52
|
+
* for why a turn is attested as an SLSA-shaped provenance predicate rather
|
|
53
|
+
* than under a minted `run-agent`-specific `predicateType`, and why that
|
|
54
|
+
* decision means `sign`/`attestProvenance`/`../verbs/verify.ts` needed zero
|
|
55
|
+
* changes to their `cosign` invocations to attest/verify a turn.
|
|
43
56
|
*/
|
|
44
57
|
|
|
45
58
|
import type { Capability } from "../capability";
|
|
@@ -235,6 +248,23 @@ export interface BuildProvenanceStatementInput {
|
|
|
235
248
|
finishedOn?: string;
|
|
236
249
|
/** CI invocation/run id, when available (e.g. a GitHub Actions run id). */
|
|
237
250
|
invocationId?: string;
|
|
251
|
+
/**
|
|
252
|
+
* Additional `buildDefinition.externalParameters` merged in alongside the
|
|
253
|
+
* `{ sourceRef }` default (#1943: `run-agent`'s provenance statement folds
|
|
254
|
+
* in the fountain-supplied *declared* facts — the agent selector today,
|
|
255
|
+
* more once fountain `Agent` resolution lands — without run-agent needing
|
|
256
|
+
* its own predicate builder). `sourceRef` is reserved by this function: a
|
|
257
|
+
* caller-supplied `sourceRef` is overridden, never signed; any other key
|
|
258
|
+
* name is the caller's choice.
|
|
259
|
+
*/
|
|
260
|
+
externalParameters?: Record<string, unknown>;
|
|
261
|
+
/**
|
|
262
|
+
* Additional `buildDefinition.internalParameters` merged in alongside the
|
|
263
|
+
* `{ artifactDigest }` default (#1943: `run-agent` folds in what actually
|
|
264
|
+
* happened — sprite id, checkpoint id, turn status/exit code — the
|
|
265
|
+
* *imperative* facts a build's `internalParameters` are for).
|
|
266
|
+
*/
|
|
267
|
+
internalParameters?: Record<string, unknown>;
|
|
238
268
|
}
|
|
239
269
|
|
|
240
270
|
const DEFAULT_BUILD_TYPE = "https://chant.dev/build-archive/v1";
|
|
@@ -263,8 +293,10 @@ export function buildProvenanceStatement(input: BuildProvenanceStatementInput):
|
|
|
263
293
|
predicate: {
|
|
264
294
|
buildDefinition: {
|
|
265
295
|
buildType: input.buildType ?? DEFAULT_BUILD_TYPE,
|
|
266
|
-
|
|
267
|
-
|
|
296
|
+
// Reserved keys spread LAST so a caller-supplied key can never clobber
|
|
297
|
+
// them into a signed statement that disagrees with resolvedDependencies.
|
|
298
|
+
externalParameters: { ...input.externalParameters, sourceRef: input.provenance.sourceRef },
|
|
299
|
+
internalParameters: { ...input.internalParameters, artifactDigest: input.provenance.artifactDigest },
|
|
268
300
|
resolvedDependencies: [{ uri: input.provenance.sourceRef, digest: { sha256: digest } }],
|
|
269
301
|
},
|
|
270
302
|
runDetails: {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests `wrangler-deploy`/`wrangler-versions-promote` (#1293, epic #1296,
|
|
3
|
+
* ./wrangler.ts) against a `MockProcessRunner` — no live `wrangler`, no
|
|
4
|
+
* network call, ever. Asserts the constructed invocations, the version-id
|
|
5
|
+
* extraction, the wired output -> promote input handoff, and the
|
|
6
|
+
* captured-previous-version rollback (the "no hand-written compensation"
|
|
7
|
+
* claim from #1293's verification section).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
createWranglerDeployCapability,
|
|
13
|
+
createWranglerVersionsPromoteCapability,
|
|
14
|
+
parseWranglerVersionId,
|
|
15
|
+
WranglerVersionIdNotFoundError,
|
|
16
|
+
} from "./wrangler";
|
|
17
|
+
import { ToolNotAvailableError } from "./process-runner";
|
|
18
|
+
import { createMockProcessRunner } from "./__tests__/mock-process-runner";
|
|
19
|
+
|
|
20
|
+
const ctx = { env: "prod", component: "api-worker" };
|
|
21
|
+
const VERSION_A = "07bcb198-9633-4172-a1f0-b09f6f1a1a11";
|
|
22
|
+
const VERSION_B = "aa11bb22-cc33-dd44-ee55-ff6677889900";
|
|
23
|
+
|
|
24
|
+
describe("wrangler-deploy", () => {
|
|
25
|
+
it("shells `wrangler deploy` with --config, extracts the Version ID from stdout", async () => {
|
|
26
|
+
const mock = createMockProcessRunner({
|
|
27
|
+
responses: { "wrangler deploy": `Uploaded api-worker (1.23 sec)\nVersion ID: ${VERSION_A}\n` },
|
|
28
|
+
});
|
|
29
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
30
|
+
|
|
31
|
+
const output = await capability.run(ctx, { config: "api/wrangler.jsonc" });
|
|
32
|
+
|
|
33
|
+
expect(output).toEqual({ versionId: VERSION_A });
|
|
34
|
+
const deployCall = mock.calls.find((c) => c.command.startsWith("wrangler deploy"))!;
|
|
35
|
+
expect(deployCall.command).toBe(`wrangler deploy --config 'api/wrangler.jsonc'`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("passes --env through when supplied", async () => {
|
|
39
|
+
const mock = createMockProcessRunner({
|
|
40
|
+
responses: { "wrangler deploy": `Version ID: ${VERSION_A}\n` },
|
|
41
|
+
});
|
|
42
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
43
|
+
|
|
44
|
+
await capability.run(ctx, { config: "api/wrangler.jsonc", env: "staging" });
|
|
45
|
+
|
|
46
|
+
const deployCall = mock.calls.find((c) => c.command.startsWith("wrangler deploy"))!;
|
|
47
|
+
expect(deployCall.command).toBe(`wrangler deploy --config 'api/wrangler.jsonc' --env 'staging'`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("throws WranglerVersionIdNotFoundError when stdout carries no Version ID", async () => {
|
|
51
|
+
const mock = createMockProcessRunner({ responses: { "wrangler deploy": "Uploaded, nothing else.\n" } });
|
|
52
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
53
|
+
|
|
54
|
+
await expect(capability.run(ctx, { config: "api/wrangler.jsonc" })).rejects.toBeInstanceOf(
|
|
55
|
+
WranglerVersionIdNotFoundError,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("throws ToolNotAvailableError when wrangler is absent, rather than silently no-op'ing", async () => {
|
|
60
|
+
const mock = createMockProcessRunner({ tools: { wrangler: false } });
|
|
61
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
62
|
+
|
|
63
|
+
await expect(capability.run(ctx, { config: "api/wrangler.jsonc" })).rejects.toBeInstanceOf(
|
|
64
|
+
ToolNotAvailableError,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("declares a rollback (native rollback disposition, no rollbackPolicy override needed)", () => {
|
|
69
|
+
const capability = createWranglerDeployCapability(createMockProcessRunner().runner);
|
|
70
|
+
expect(typeof capability.rollback).toBe("function");
|
|
71
|
+
expect(capability.rollbackPolicy).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("rollback is a no-op on a first deploy — nothing was live before it", async () => {
|
|
75
|
+
const mock = createMockProcessRunner({
|
|
76
|
+
responses: { "versions list": "[]", "wrangler deploy": `Version ID: ${VERSION_A}\n` },
|
|
77
|
+
});
|
|
78
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
79
|
+
await capability.run(ctx, { config: "api/wrangler.jsonc" });
|
|
80
|
+
|
|
81
|
+
await capability.rollback!(ctx, { config: "api/wrangler.jsonc" });
|
|
82
|
+
|
|
83
|
+
expect(mock.calls.some((c) => c.command.includes("versions deploy"))).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("rollback promotes back to whichever version was live before this deploy — the wrangler-versions-promote back-to-prior-version claim, with no hand-written compensation", async () => {
|
|
87
|
+
const mock = createMockProcessRunner({
|
|
88
|
+
responses: {
|
|
89
|
+
"versions list": JSON.stringify([{ id: VERSION_A, percentage: 100 }]),
|
|
90
|
+
"wrangler deploy": `Version ID: ${VERSION_B}\n`,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const capability = createWranglerDeployCapability(mock.runner);
|
|
94
|
+
await capability.run(ctx, { config: "api/wrangler.jsonc" });
|
|
95
|
+
|
|
96
|
+
await capability.rollback!(ctx, { config: "api/wrangler.jsonc" });
|
|
97
|
+
|
|
98
|
+
const promoteCall = mock.calls.find((c) => c.command.startsWith("wrangler versions deploy"))!;
|
|
99
|
+
expect(promoteCall.command).toBe(
|
|
100
|
+
`wrangler versions deploy '${VERSION_A}@100' --config 'api/wrangler.jsonc' --yes`,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("wrangler-versions-promote", () => {
|
|
106
|
+
it("shells `wrangler versions deploy <id>@<pct> --yes`, defaulting percentage to 100", async () => {
|
|
107
|
+
const mock = createMockProcessRunner();
|
|
108
|
+
const capability = createWranglerVersionsPromoteCapability(mock.runner);
|
|
109
|
+
|
|
110
|
+
const output = await capability.run(ctx, { config: "api/wrangler.jsonc", versionId: VERSION_A });
|
|
111
|
+
|
|
112
|
+
expect(output).toEqual({ versionId: VERSION_A, percentage: 100 });
|
|
113
|
+
const promoteCall = mock.calls.find((c) => c.command.startsWith("wrangler versions deploy"))!;
|
|
114
|
+
expect(promoteCall.command).toBe(
|
|
115
|
+
`wrangler versions deploy '${VERSION_A}@100' --config 'api/wrangler.jsonc' --yes`,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("consumes a wired wrangler-deploy output as its versionId input", async () => {
|
|
120
|
+
const mock = createMockProcessRunner({
|
|
121
|
+
responses: { "wrangler deploy": `Version ID: ${VERSION_A}\n` },
|
|
122
|
+
});
|
|
123
|
+
const deploy = createWranglerDeployCapability(mock.runner);
|
|
124
|
+
const promote = createWranglerVersionsPromoteCapability(mock.runner);
|
|
125
|
+
|
|
126
|
+
const { versionId } = await deploy.run(ctx, { config: "api/wrangler.jsonc" });
|
|
127
|
+
const promoted = await promote.run(ctx, { config: "api/wrangler.jsonc", versionId, percentage: 10 });
|
|
128
|
+
|
|
129
|
+
expect(promoted).toEqual({ versionId: VERSION_A, percentage: 10 });
|
|
130
|
+
const promoteCall = mock.calls.find((c) => c.command.startsWith("wrangler versions deploy"))!;
|
|
131
|
+
expect(promoteCall.command).toContain(`'${VERSION_A}@10'`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("declares a rollback (native): re-promotes to whichever version was live before this promote call", async () => {
|
|
135
|
+
const mock = createMockProcessRunner({
|
|
136
|
+
responses: { "versions list": JSON.stringify([{ id: VERSION_A, percentage: 100 }]) },
|
|
137
|
+
});
|
|
138
|
+
const capability = createWranglerVersionsPromoteCapability(mock.runner);
|
|
139
|
+
await capability.run(ctx, { config: "api/wrangler.jsonc", versionId: VERSION_B });
|
|
140
|
+
|
|
141
|
+
await capability.rollback!(ctx, { config: "api/wrangler.jsonc", versionId: VERSION_B });
|
|
142
|
+
|
|
143
|
+
const calls = mock.calls.filter((c) => c.command.startsWith("wrangler versions deploy"));
|
|
144
|
+
expect(calls).toHaveLength(2);
|
|
145
|
+
expect(calls[0]!.command).toContain(`'${VERSION_B}@100'`);
|
|
146
|
+
expect(calls[1]!.command).toContain(`'${VERSION_A}@100'`);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("throws ToolNotAvailableError when wrangler is absent", async () => {
|
|
150
|
+
const mock = createMockProcessRunner({ tools: { wrangler: false } });
|
|
151
|
+
const capability = createWranglerVersionsPromoteCapability(mock.runner);
|
|
152
|
+
|
|
153
|
+
await expect(
|
|
154
|
+
capability.run(ctx, { config: "api/wrangler.jsonc", versionId: VERSION_A }),
|
|
155
|
+
).rejects.toBeInstanceOf(ToolNotAvailableError);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("parseWranglerVersionId", () => {
|
|
160
|
+
it("accepts both 'Version ID:' and bare 'Version:' wording", () => {
|
|
161
|
+
expect(parseWranglerVersionId(`Version ID: ${VERSION_A}`)).toBe(VERSION_A);
|
|
162
|
+
expect(parseWranglerVersionId(`Version: ${VERSION_A}`)).toBe(VERSION_A);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("throws on stdout with no UUID-shaped version token", () => {
|
|
166
|
+
expect(() => parseWranglerVersionId("Version: 2 (a plain counter, not a version id)")).toThrow(
|
|
167
|
+
WranglerVersionIdNotFoundError,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wrangler-deploy` / `wrangler-versions-promote` — the Cloudflare Workers
|
|
3
|
+
* apply leaves (chant #1293, epic #1296). The epic cedes the Workers plane
|
|
4
|
+
* entirely to `wrangler` (first-party, schema-backed, already owns the
|
|
5
|
+
* config format, bindings model, local dev, and deploy) — these two verbs
|
|
6
|
+
* are a typed wrapper over that CLI, not a reimplementation of it.
|
|
7
|
+
*
|
|
8
|
+
* Placement: core, not a cloudflare lexicon. #1293's own analysis weighed
|
|
9
|
+
* three options — core (generic verb, fastest, costs a little conceptual
|
|
10
|
+
* tidiness since `wrangler-deploy` shells a specific vendor CLI), a
|
|
11
|
+
* capability-only cloudflare lexicon (the "correct" home, but `LexiconPlugin`
|
|
12
|
+
* tier-1 completeness — serializer, lint rules, post-synth, LSP, examples,
|
|
13
|
+
* docs — is heavy ceremony for three verbs and no resource types), or
|
|
14
|
+
* generalizing to a `cli-deploy` verb parameterised by tool (tempting, but
|
|
15
|
+
* premature and it re-opens the verb set the bounded-primitives thesis
|
|
16
|
+
* depends on staying closed). Core wins on the same precedent `sign`/
|
|
17
|
+
* `attest-provenance` (./sign.ts) already set: a specific vendor CLI
|
|
18
|
+
* (`cosign`) shelled out through the injectable `ProcessRunner`
|
|
19
|
+
* (./process-runner.ts), no cloud-specific SDK client needed the way AWS's
|
|
20
|
+
* `CloudExecutor` (./cloud-executor.ts) models CloudFormation/ECS/Lambda.
|
|
21
|
+
* `wrangler` fits that exact shape. Revisit if/when the zone-plane lexicon
|
|
22
|
+
* (#1294) lands and gives these three verbs a natural cloudflare-owned home.
|
|
23
|
+
*
|
|
24
|
+
* `wrangler-versions-promote` is also the direct evidence for #1296's
|
|
25
|
+
* "three new verbs cover a whole new cloud" claim: Cloudflare's native
|
|
26
|
+
* Worker version rollback maps onto the existing `RollbackPolicy: "native"`
|
|
27
|
+
* (../capability.ts) with no compensation code to hand-write — both verbs
|
|
28
|
+
* here declare a `rollback` (auto-derived "native" per ../capability.ts's
|
|
29
|
+
* `Capability.rollbackPolicy` doc) that re-promotes to whichever version was
|
|
30
|
+
* live before the step ran, the same best-effort captured-previous-state
|
|
31
|
+
* pattern lexicons/aws/src/components/apply.ts's `lambda-deploy` uses for
|
|
32
|
+
* its alias rollback.
|
|
33
|
+
*
|
|
34
|
+
* No real Cloudflare control-plane emulator exists yet (#1295, epic #1296),
|
|
35
|
+
* so — like ./sign.ts — every real path here shells out through the
|
|
36
|
+
* injectable `ProcessRunner` and every test substitutes `MockProcessRunner`
|
|
37
|
+
* (./__tests__/mock-process-runner.ts): no live `wrangler`, no network, ever,
|
|
38
|
+
* in a test run. CI coverage is plan-shape/invocation-shape assertions only,
|
|
39
|
+
* per #1293's own "Verification" section.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import type { Capability } from "../capability";
|
|
43
|
+
import { defaultProcessRunner, q, requireTool, type ProcessRunner } from "./process-runner";
|
|
44
|
+
|
|
45
|
+
const WRANGLER_TOOL = "wrangler";
|
|
46
|
+
|
|
47
|
+
/** Distinguishes one deploy target (a wrangler config + optional named environment) from another, so the best-effort previous-version tracking below never conflates two different Workers sharing a process. */
|
|
48
|
+
function targetKey(config: string, env?: string): string {
|
|
49
|
+
return env ? `${config}#${env}` : config;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildWranglerDeployArgs(input: WranglerDeployInput): string {
|
|
53
|
+
const args = ["wrangler", "deploy", "--config", q(input.config)];
|
|
54
|
+
if (input.env) args.push("--env", q(input.env));
|
|
55
|
+
return args.join(" ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildVersionsListArgs(config: string, env?: string): string {
|
|
59
|
+
const args = ["wrangler", "versions", "list", "--config", q(config), "--json"];
|
|
60
|
+
if (env) args.push("--env", q(env));
|
|
61
|
+
return args.join(" ");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildVersionsPromoteArgs(config: string, versionId: string, percentage: number, env?: string): string {
|
|
65
|
+
const args = ["wrangler", "versions", "deploy", q(`${versionId}@${percentage}`), "--config", q(config), "--yes"];
|
|
66
|
+
if (env) args.push("--env", q(env));
|
|
67
|
+
return args.join(" ");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Thrown when `wrangler deploy`'s stdout carries no parseable Version ID — fail-closed rather than returning a capability output downstream steps (`wrangler-versions-promote`, wired via `"@Deploy.versionId"`) would silently receive as `undefined`. */
|
|
71
|
+
export class WranglerVersionIdNotFoundError extends Error {
|
|
72
|
+
constructor(public readonly stdout: string) {
|
|
73
|
+
super(`wrangler-deploy: could not find a Version ID in wrangler's output. Got:\n${stdout}`);
|
|
74
|
+
this.name = "WranglerVersionIdNotFoundError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// wrangler's deploy/versions-upload output names the new version as e.g.
|
|
79
|
+
// "Version ID: 07bcb198-... " (gradual deployments) — accept either "Version
|
|
80
|
+
// ID:" or the bare "Version:" wording across wrangler versions, and require a
|
|
81
|
+
// UUID-shaped token so a stray "Version: 2" summary line can't be mistaken
|
|
82
|
+
// for it.
|
|
83
|
+
const VERSION_ID_RE = /Version(?: ID)?:\s*([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/;
|
|
84
|
+
|
|
85
|
+
/** Extract the deployed Worker version id from `wrangler deploy`'s stdout. Exported so tests can assert on it directly, mirroring ./sign.ts's `buildSignArgs`. */
|
|
86
|
+
export function parseWranglerVersionId(stdout: string): string {
|
|
87
|
+
const match = VERSION_ID_RE.exec(stdout);
|
|
88
|
+
if (!match) throw new WranglerVersionIdNotFoundError(stdout);
|
|
89
|
+
return match[1]!;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface WranglerVersionsListEntry {
|
|
93
|
+
id: string;
|
|
94
|
+
percentage?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Best-effort: the currently-live (100%) version id, from `wrangler versions
|
|
99
|
+
* list --json` — `undefined` for a first deploy (no versions yet) or if
|
|
100
|
+
* listing fails/parses oddly. Never throws: capturing "what to roll back to"
|
|
101
|
+
* must not block the deploy/promote itself, the same fail-soft stance
|
|
102
|
+
* `lambda-deploy`'s alias-version capture takes.
|
|
103
|
+
*/
|
|
104
|
+
async function currentLiveVersionId(
|
|
105
|
+
runner: ProcessRunner,
|
|
106
|
+
config: string,
|
|
107
|
+
env?: string,
|
|
108
|
+
): Promise<string | undefined> {
|
|
109
|
+
try {
|
|
110
|
+
const { stdout } = await runner.run(buildVersionsListArgs(config, env));
|
|
111
|
+
const entries = JSON.parse(stdout) as WranglerVersionsListEntry[];
|
|
112
|
+
return entries.find((entry) => entry.percentage === 100)?.id;
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── wrangler-deploy ──────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export interface WranglerDeployInput {
|
|
121
|
+
/** Path to the Worker's `wrangler.jsonc`/`wrangler.toml`. */
|
|
122
|
+
config: string;
|
|
123
|
+
/** Named environment within the config (`wrangler deploy --env <env>`). */
|
|
124
|
+
env?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface WranglerDeployOutput {
|
|
128
|
+
/** The version id wrangler assigned this deploy — wire into `wrangler-versions-promote` (e.g. `"@Deploy.versionId"`). */
|
|
129
|
+
versionId: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Deploy a Worker from a `wrangler.jsonc`/`wrangler.toml` via `wrangler
|
|
134
|
+
* deploy`, and return the version id it published — the analogue of
|
|
135
|
+
* `lambda-deploy` (../../lexicons/aws/src/components/apply.ts) for the
|
|
136
|
+
* Workers plane.
|
|
137
|
+
*
|
|
138
|
+
* Rollback: `wrangler-versions-promote` back to whichever version was live
|
|
139
|
+
* before this step ran (captured up front via `wrangler versions list`, the
|
|
140
|
+
* same best-effort captured-previous pattern `lambda-deploy` uses for its
|
|
141
|
+
* alias). A no-op on a first deploy — nothing was live to restore.
|
|
142
|
+
*/
|
|
143
|
+
export function createWranglerDeployCapability(
|
|
144
|
+
processRunner: ProcessRunner = defaultProcessRunner(),
|
|
145
|
+
): Capability<WranglerDeployInput, WranglerDeployOutput> {
|
|
146
|
+
const previousVersionByTarget = new Map<string, string | undefined>();
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
kind: "wrangler-deploy",
|
|
150
|
+
async run(_ctx, input) {
|
|
151
|
+
await requireTool(processRunner, WRANGLER_TOOL, `deploy the Worker in ${input.config}`);
|
|
152
|
+
const target = targetKey(input.config, input.env);
|
|
153
|
+
if (!previousVersionByTarget.has(target)) {
|
|
154
|
+
previousVersionByTarget.set(target, await currentLiveVersionId(processRunner, input.config, input.env));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { stdout } = await processRunner.run(buildWranglerDeployArgs(input));
|
|
158
|
+
return { versionId: parseWranglerVersionId(stdout) };
|
|
159
|
+
},
|
|
160
|
+
async rollback(_ctx, input) {
|
|
161
|
+
const target = targetKey(input.config, input.env);
|
|
162
|
+
const previousVersionId = previousVersionByTarget.get(target);
|
|
163
|
+
if (!previousVersionId) return; // first deploy — nothing was live before it.
|
|
164
|
+
await requireTool(processRunner, WRANGLER_TOOL, `roll back the Worker in ${input.config}`);
|
|
165
|
+
await processRunner.run(buildVersionsPromoteArgs(input.config, previousVersionId, 100, input.env));
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Default `wrangler-deploy` capability, backed by the real `ProcessRunner`. */
|
|
171
|
+
export const wranglerDeployCapability: Capability<WranglerDeployInput, WranglerDeployOutput> =
|
|
172
|
+
createWranglerDeployCapability();
|
|
173
|
+
|
|
174
|
+
// ── wrangler-versions-promote ─────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
export interface WranglerVersionsPromoteInput {
|
|
177
|
+
/** Path to the Worker's `wrangler.jsonc`/`wrangler.toml`. */
|
|
178
|
+
config: string;
|
|
179
|
+
/** Version id to promote — typically wired from a prior `wrangler-deploy` step (`"@Deploy.versionId"`) or a prior version id when this step composes as an explicit rollback. */
|
|
180
|
+
versionId: string;
|
|
181
|
+
/** Traffic percentage to route to `versionId`. Default: 100 (full promote/rollback). Below 100 is the gradual-deployment lever. */
|
|
182
|
+
percentage?: number;
|
|
183
|
+
/** Named environment within the config (`wrangler versions deploy --env <env>`). */
|
|
184
|
+
env?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface WranglerVersionsPromoteOutput {
|
|
188
|
+
/** The version id that was promoted. */
|
|
189
|
+
versionId: string;
|
|
190
|
+
/** The traffic percentage actually routed to it. */
|
|
191
|
+
percentage: number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Promote a Worker version to (some percentage of) live traffic via
|
|
196
|
+
* `wrangler versions deploy <version-id>@<percentage> --yes` — both the
|
|
197
|
+
* gradual-deployment lever (`percentage` < 100) and, at `percentage: 100`,
|
|
198
|
+
* the rollback mechanism: Cloudflare's native version rollback is a promote
|
|
199
|
+
* to a prior version id, not a redeploy, so this same verb composes as the
|
|
200
|
+
* explicit compensation step a component wires up (#1293's "verification"
|
|
201
|
+
* example: forced failure after `wrangler-deploy` -> `wrangler-versions-promote`
|
|
202
|
+
* back to the prior version, no hand-written compensation needed).
|
|
203
|
+
*
|
|
204
|
+
* Also declares its own `rollback` (native, no `rollbackPolicy` override
|
|
205
|
+
* needed — see ../capability.ts): re-promotes to whichever version was live
|
|
206
|
+
* before *this* promote call, for the case where the promote step itself is
|
|
207
|
+
* composed directly (not just as `wrangler-deploy`'s compensation).
|
|
208
|
+
*/
|
|
209
|
+
export function createWranglerVersionsPromoteCapability(
|
|
210
|
+
processRunner: ProcessRunner = defaultProcessRunner(),
|
|
211
|
+
): Capability<WranglerVersionsPromoteInput, WranglerVersionsPromoteOutput> {
|
|
212
|
+
const previousVersionByTarget = new Map<string, string | undefined>();
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
kind: "wrangler-versions-promote",
|
|
216
|
+
async run(_ctx, input) {
|
|
217
|
+
await requireTool(processRunner, WRANGLER_TOOL, `promote version ${input.versionId} in ${input.config}`);
|
|
218
|
+
const percentage = input.percentage ?? 100;
|
|
219
|
+
const target = targetKey(input.config, input.env);
|
|
220
|
+
if (!previousVersionByTarget.has(target)) {
|
|
221
|
+
previousVersionByTarget.set(target, await currentLiveVersionId(processRunner, input.config, input.env));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await processRunner.run(buildVersionsPromoteArgs(input.config, input.versionId, percentage, input.env));
|
|
225
|
+
return { versionId: input.versionId, percentage };
|
|
226
|
+
},
|
|
227
|
+
async rollback(_ctx, input) {
|
|
228
|
+
const target = targetKey(input.config, input.env);
|
|
229
|
+
const previousVersionId = previousVersionByTarget.get(target);
|
|
230
|
+
if (!previousVersionId) return; // nothing was live before this promote.
|
|
231
|
+
await requireTool(processRunner, WRANGLER_TOOL, `roll back version promotion in ${input.config}`);
|
|
232
|
+
await processRunner.run(buildVersionsPromoteArgs(input.config, previousVersionId, 100, input.env));
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Default `wrangler-versions-promote` capability, backed by the real `ProcessRunner`. */
|
|
238
|
+
export const wranglerVersionsPromoteCapability: Capability<
|
|
239
|
+
WranglerVersionsPromoteInput,
|
|
240
|
+
WranglerVersionsPromoteOutput
|
|
241
|
+
> = createWranglerVersionsPromoteCapability();
|
package/src/composite.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isDeclarable, type Declarable } from "./declarable";
|
|
2
|
-
import { setProvenance } from "./provenance";
|
|
2
|
+
import { setPathProvenance, setProvenance } from "./provenance";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Marker symbol for Composite type identification.
|
|
@@ -150,10 +150,16 @@ export function Composite<P, M extends CompositeFactoryMembers = CompositeMember
|
|
|
150
150
|
/**
|
|
151
151
|
* Expands a CompositeInstance into a flat Map of prefixed entity names to Declarables.
|
|
152
152
|
* Handles nested composites recursively.
|
|
153
|
+
*
|
|
154
|
+
* @param instanceName - The composite INSTANCE every member belongs to (chant
|
|
155
|
+
* #1443), which at the top call is the export name and stays that name
|
|
156
|
+
* through nesting — `prefix` grows per member and is not an instance
|
|
157
|
+
* identity. Defaults to `prefix` so the top call needs no extra argument.
|
|
153
158
|
*/
|
|
154
159
|
export function expandComposite(
|
|
155
160
|
prefix: string,
|
|
156
161
|
instance: CompositeInstance,
|
|
162
|
+
instanceName: string = prefix,
|
|
157
163
|
): Map<string, Declarable> {
|
|
158
164
|
const result = new Map<string, Declarable>();
|
|
159
165
|
const shared = (instance as unknown as Record<symbol, unknown>)[SHARED_PROPS] as Record<string, unknown> | undefined;
|
|
@@ -164,7 +170,7 @@ export function expandComposite(
|
|
|
164
170
|
const fullName = `${prefix}${memberName[0].toUpperCase()}${memberName.slice(1)}`;
|
|
165
171
|
|
|
166
172
|
if (isCompositeInstance(member)) {
|
|
167
|
-
const nested = expandComposite(fullName, member);
|
|
173
|
+
const nested = expandComposite(fullName, member, instanceName);
|
|
168
174
|
for (const [nestedName, nestedEntity] of nested) {
|
|
169
175
|
// Inner composite already stamped; `??=` keeps the most-specific one.
|
|
170
176
|
if (compositeName) setProvenance(nestedEntity, { composite: compositeName });
|
|
@@ -176,6 +182,15 @@ export function expandComposite(
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
// Whole-entity origin (chant #1443). Recorded after the loop so a nested
|
|
186
|
+
// member carries its INNER composite here — the recursive call above already
|
|
187
|
+
// ran and `setPathProvenance` keeps the first writer.
|
|
188
|
+
if (compositeName) {
|
|
189
|
+
for (const entity of result.values()) {
|
|
190
|
+
setPathProvenance(entity, "", { kind: "composite", composite: compositeName, instance: instanceName });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
179
194
|
if (shared) {
|
|
180
195
|
for (const entity of result.values()) {
|
|
181
196
|
if ("props" in entity) {
|
|
@@ -211,6 +226,20 @@ export function expandComposite(
|
|
|
211
226
|
Object.defineProperty(entity, "props", {
|
|
212
227
|
value: merged, enumerable: false, configurable: true,
|
|
213
228
|
});
|
|
229
|
+
// A key the member did not set is the propagating instance's alone, so
|
|
230
|
+
// it is attributable exactly (chant #1443). A key both sides wrote —
|
|
231
|
+
// an override, or the array concatenation above — is left to the
|
|
232
|
+
// whole-entity origin rather than credited to one contributor.
|
|
233
|
+
if (compositeName) {
|
|
234
|
+
for (const k of Object.keys(shared)) {
|
|
235
|
+
if (shared[k] === undefined || existing[k] !== undefined) continue;
|
|
236
|
+
setPathProvenance(entity, k, {
|
|
237
|
+
kind: "composite",
|
|
238
|
+
composite: compositeName,
|
|
239
|
+
instance: instanceName,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
214
243
|
}
|
|
215
244
|
}
|
|
216
245
|
}
|
package/src/config.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
resolveAutoReleaseDisabled,
|
|
7
7
|
resolveFoldEnabled,
|
|
8
8
|
resolveSbomFormat,
|
|
9
|
+
resolveKnowledgeDir,
|
|
9
10
|
environmentName,
|
|
10
11
|
environmentNames,
|
|
11
12
|
environmentEndpoint,
|
|
@@ -433,3 +434,17 @@ describe("resolveSbomFormat (#606)", () => {
|
|
|
433
434
|
expect(resolveSbomFormat({}, "cyclonedx")).toBe("cyclonedx");
|
|
434
435
|
});
|
|
435
436
|
});
|
|
437
|
+
|
|
438
|
+
describe("resolveKnowledgeDir (#1864, design #1059)", () => {
|
|
439
|
+
test("convention: knowledge/ beside the project root when config is silent", () => {
|
|
440
|
+
expect(resolveKnowledgeDir({}, "/proj")).toBe(join("/proj", "knowledge"));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("config override honored", () => {
|
|
444
|
+
expect(resolveKnowledgeDir({ knowledge: { dir: "docs/knowledge" } }, "/proj")).toBe(join("/proj", "docs/knowledge"));
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("an empty knowledge object still falls back to the convention name", () => {
|
|
448
|
+
expect(resolveKnowledgeDir({ knowledge: {} }, "/proj")).toBe(join("/proj", "knowledge"));
|
|
449
|
+
});
|
|
450
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -181,6 +181,9 @@ export const ChantConfigSchema = z.object({
|
|
|
181
181
|
scanner: z.enum(["grype", "trivy"]).optional(),
|
|
182
182
|
vexSources: z.array(z.string()).optional(),
|
|
183
183
|
}).optional(),
|
|
184
|
+
knowledge: z.object({
|
|
185
|
+
dir: z.string().min(1).optional(),
|
|
186
|
+
}).optional(),
|
|
184
187
|
}).passthrough();
|
|
185
188
|
|
|
186
189
|
/**
|
|
@@ -400,6 +403,21 @@ export interface ChantConfig {
|
|
|
400
403
|
/** Default VEX document paths (OpenVEX/CycloneDX) applied to every gate. Read where the gate step is composed. */
|
|
401
404
|
vexSources?: string[];
|
|
402
405
|
};
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* OKF knowledge bundle location (#1864, design #1059, epic #1057) — the
|
|
409
|
+
* *input* side of `chant explain --format okf` (#1058): a project may
|
|
410
|
+
* author knowledge as an OKF v0.2 bundle (a directory of markdown files
|
|
411
|
+
* with YAML frontmatter) that `./okf-read.ts`'s `loadOkfBundle` reads and
|
|
412
|
+
* binds to discovered entities via each concept's `binds` frontmatter key.
|
|
413
|
+
* Convention-first: `knowledge/` beside `chant.config.ts` is used when this
|
|
414
|
+
* is unset. Set `dir` only when that name is already taken by something
|
|
415
|
+
* else in the project. See {@link resolveKnowledgeDir}.
|
|
416
|
+
*/
|
|
417
|
+
knowledge?: {
|
|
418
|
+
/** Bundle directory, relative to the project root. Defaults to `"knowledge"`. */
|
|
419
|
+
dir?: string;
|
|
420
|
+
};
|
|
403
421
|
}
|
|
404
422
|
|
|
405
423
|
/**
|
|
@@ -783,6 +801,18 @@ export function resolveVulnPolicy(config: ChantConfig): Partial<VulnPolicy> {
|
|
|
783
801
|
return out;
|
|
784
802
|
}
|
|
785
803
|
|
|
804
|
+
/**
|
|
805
|
+
* Resolve the OKF knowledge bundle directory (#1864, design #1059):
|
|
806
|
+
* `knowledge.dir` relative to `projectPath` when configured, else the
|
|
807
|
+
* `knowledge/` convention beside `chant.config.ts`. Never checks existence —
|
|
808
|
+
* a project with no bundle yet resolves a path all the same, and
|
|
809
|
+
* `okf-read.ts`'s `loadOkfBundle` treats a missing directory as an empty
|
|
810
|
+
* bundle rather than an error.
|
|
811
|
+
*/
|
|
812
|
+
export function resolveKnowledgeDir(config: ChantConfig, projectPath: string): string {
|
|
813
|
+
return join(projectPath, config.knowledge?.dir ?? "knowledge");
|
|
814
|
+
}
|
|
815
|
+
|
|
786
816
|
/**
|
|
787
817
|
* Validate and normalize a raw config object into ChantConfig shape.
|
|
788
818
|
*/
|
|
@@ -48,6 +48,25 @@ describe("normalizeDeepProperties", () => {
|
|
|
48
48
|
expect(Object.keys(out.mid as Record<string, unknown>)).toEqual(["a", "z"]);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
test("the mask hook collapses a structural secret path to MASKED, on either side (#1830)", () => {
|
|
52
|
+
const hooks: DeepNormalizationHooks = {
|
|
53
|
+
mask: (n) => n.entityType === "K8s::Core::Secret" && n.pattern.startsWith("data."),
|
|
54
|
+
};
|
|
55
|
+
for (const side of ["declared", "live"] as const) {
|
|
56
|
+
const out = normalizeDeepProperties(
|
|
57
|
+
{ data: { "app.conf": "c2VjcmV0LWJ5dGVz" }, type: "Opaque" },
|
|
58
|
+
{ entityType: "K8s::Core::Secret", side, hooks },
|
|
59
|
+
);
|
|
60
|
+
expect(out).toEqual({ data: { "app.conf": MASKED }, type: "Opaque" });
|
|
61
|
+
}
|
|
62
|
+
// Another entity type sails through the same hook untouched.
|
|
63
|
+
const other = normalizeDeepProperties(
|
|
64
|
+
{ data: { "app.conf": "plain" } },
|
|
65
|
+
{ entityType: "K8s::Core::ConfigMap", side: "live", hooks },
|
|
66
|
+
);
|
|
67
|
+
expect(other).toEqual({ data: { "app.conf": "plain" } });
|
|
68
|
+
});
|
|
69
|
+
|
|
51
70
|
test("leaves array order alone with no ordering hook", () => {
|
|
52
71
|
const out = normalizeDeepProperties({ Tags: [{ Key: "z" }, { Key: "a" }] }, { entityType: "T", side: "live" });
|
|
53
72
|
expect(out.Tags).toEqual([{ Key: "z" }, { Key: "a" }]);
|