@intentius/chant 0.57.0 → 0.58.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 +51 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +20 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +14 -8
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/lineage.d.ts +10 -0
- package/dist/audit/lineage.d.ts.map +1 -0
- package/dist/audit/prior-art.d.ts +385 -0
- package/dist/audit/prior-art.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +16 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +8 -1
- 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 +3 -0
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +15 -0
- package/dist/audit/secrets.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +46 -0
- package/dist/audit/terraform-state.d.ts.map +1 -0
- package/dist/build.d.ts +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/claimed-fields.d.ts +161 -0
- package/dist/claimed-fields.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +2 -2
- package/dist/cli/command-group.d.ts +14 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +17 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +2 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +82 -16
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -42
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts +28 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts +16 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resources/context.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +26 -9
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
- package/dist/components/auto-release.d.ts +10 -20
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +4 -4
- package/dist/components/cli-support.d.ts +20 -21
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +3 -3
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver-output.d.ts.map +1 -1
- package/dist/components/driver.d.ts +66 -36
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/manifest-persistence.d.ts +1 -9
- package/dist/components/manifest-persistence.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +6 -6
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +9 -15
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts +2 -2
- package/dist/deep-observation.d.ts +17 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/files.d.ts +4 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +33 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/config-wire.d.ts +2 -2
- package/dist/fold/foldable-helpers.d.ts +5 -1
- package/dist/fold/foldable-helpers.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +236 -0
- package/dist/fold-provenance.d.ts.map +1 -0
- package/dist/graph-ops.d.ts +1 -1
- package/dist/held-elsewhere.d.ts +85 -0
- package/dist/held-elsewhere.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts +3 -3
- package/dist/lexicon-config.d.ts +4 -4
- package/dist/lexicon-plugin-helpers.d.ts +1 -1
- package/dist/lexicon.d.ts +104 -14
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +13 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +133 -6
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +71 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +2 -2
- package/dist/lifecycle/run-ledger.d.ts +53 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +3 -3
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/config.d.ts +81 -2
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +55 -2
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +1 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +16 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
- package/dist/lint/rules/op/support.d.ts +12 -0
- package/dist/lint/rules/op/support.d.ts.map +1 -0
- package/dist/lint/suppressions.d.ts +108 -0
- package/dist/lint/suppressions.d.ts.map +1 -0
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/op/activities/activity-contracts.d.ts +54 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -0
- package/dist/op/activities/apply.d.ts +262 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +13 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +110 -0
- package/dist/op/activities/converge.d.ts.map +1 -0
- package/dist/op/activities/env-teardown.d.ts +76 -0
- package/dist/op/activities/env-teardown.d.ts.map +1 -0
- package/dist/op/activities/guard-validate.d.ts +64 -0
- package/dist/op/activities/guard-validate.d.ts.map +1 -0
- package/dist/op/activities/http-check.d.ts +34 -0
- package/dist/op/activities/http-check.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +31 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +89 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +7 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +28 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts +76 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +25 -18
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts +28 -0
- package/dist/op/activity-failure.d.ts.map +1 -0
- package/dist/op/activity-profiles.d.ts +118 -0
- package/dist/op/activity-profiles.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +22 -38
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +3 -14
- package/dist/op/activity-runtime.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -29
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +113 -0
- package/dist/op/composites/apply-op.d.ts.map +1 -0
- package/dist/op/composites/converge-op.d.ts +120 -0
- package/dist/op/composites/converge-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +26 -0
- package/dist/op/composites/index.d.ts.map +1 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
- package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
- package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/op/composites/reconcile-op.d.ts +57 -0
- package/dist/op/composites/reconcile-op.d.ts.map +1 -0
- package/dist/op/composites/watch-op.d.ts +60 -0
- package/dist/op/composites/watch-op.d.ts.map +1 -0
- package/dist/op/composites/workflow-audit-op.d.ts +57 -0
- package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/op/converge-rule.d.ts +12 -10
- package/dist/op/converge-rule.d.ts.map +1 -1
- package/dist/op/cron.d.ts +51 -0
- package/dist/op/cron.d.ts.map +1 -0
- package/dist/op/discover.d.ts +9 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/duration.d.ts +11 -0
- package/dist/op/duration.d.ts.map +1 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate.d.ts +90 -0
- package/dist/op/gate.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +9 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/index.d.ts +23 -7
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +114 -28
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +11 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +184 -0
- package/dist/op/op-ir.d.ts.map +1 -0
- package/dist/op/op-verb-class.d.ts +10 -2
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +30 -5
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/receipt-store.d.ts +1 -1
- package/dist/op/receipt-store.d.ts.map +1 -1
- package/dist/op/resource.d.ts +15 -1
- package/dist/op/resource.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +200 -0
- package/dist/op/runtime.d.ts.map +1 -0
- package/dist/op/runtimes/local.d.ts +29 -0
- package/dist/op/runtimes/local.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +15 -13
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/types.d.ts +79 -27
- package/dist/op/types.d.ts.map +1 -1
- package/dist/ownership.d.ts +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/audit/catalog.test.ts +79 -3
- package/src/audit/catalog.ts +111 -13
- package/src/audit/core.test.ts +26 -0
- package/src/audit/core.ts +61 -17
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +91 -15
- package/src/audit/lineage.ts +100 -0
- package/src/audit/prior-art.ts +153 -0
- package/src/audit/proof.test.ts +67 -0
- package/src/audit/proof.ts +70 -0
- package/src/audit/report-html.ts +9 -4
- package/src/audit/report-model.ts +16 -2
- package/src/audit/report.test.ts +33 -0
- package/src/audit/report.ts +5 -3
- package/src/audit/rules-doc.test.ts +34 -2
- package/src/audit/rules-doc.ts +82 -9
- package/src/audit/secrets.ts +15 -3
- package/src/audit/terraform-state.test.ts +144 -0
- package/src/audit/terraform-state.ts +131 -0
- package/src/build.ts +31 -1
- package/src/claimed-fields.test.ts +145 -0
- package/src/claimed-fields.ts +208 -0
- package/src/cli/build-params-cli.ts +2 -2
- package/src/cli/command-group.ts +14 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
- package/src/cli/commands/audit.test.ts +189 -11
- package/src/cli/commands/audit.ts +73 -27
- package/src/cli/commands/build.test.ts +292 -9
- package/src/cli/commands/build.ts +156 -24
- package/src/cli/commands/check-lexicon-docs.ts +1 -1
- package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
- package/src/cli/commands/check-lexicon-examples.ts +42 -10
- package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
- package/src/cli/commands/init-lexicon.test.ts +97 -2
- package/src/cli/commands/init-lexicon.ts +4 -2
- package/src/cli/commands/init.test.ts +35 -1
- package/src/cli/commands/init.ts +14 -3
- package/src/cli/commands/lint.test.ts +48 -0
- package/src/cli/commands/lint.ts +123 -0
- package/src/cli/commands/onboard.test.ts +75 -0
- package/src/cli/commands/onboard.ts +54 -23
- package/src/cli/handlers/build.test.ts +81 -1
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/graph.test.ts +3 -3
- package/src/cli/handlers/lifecycle.test.ts +195 -12
- package/src/cli/handlers/lifecycle.ts +123 -6
- package/src/cli/handlers/misc.ts +1 -1
- package/src/cli/handlers/operator.test.ts +183 -11
- package/src/cli/handlers/operator.ts +260 -55
- package/src/cli/handlers/run.test.ts +431 -1287
- package/src/cli/handlers/run.ts +365 -915
- package/src/cli/main.test.ts +23 -15
- package/src/cli/main.ts +88 -33
- package/src/cli/mcp/op-tools.ts +147 -88
- package/src/cli/mcp/resource-handlers.ts +10 -37
- package/src/cli/mcp/resources/context.ts +11 -12
- package/src/cli/mcp/server.test.ts +25 -27
- package/src/cli/mcp/server.ts +2 -2
- package/src/cli/registry.ts +26 -9
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.ts +1 -1
- package/src/components/README.md +3 -2
- package/src/components/auto-release.test.ts +4 -20
- package/src/components/auto-release.ts +17 -42
- package/src/components/capability.ts +4 -4
- package/src/components/cli-support.test.ts +10 -5
- package/src/components/cli-support.ts +52 -44
- package/src/components/component.schema.json +2 -2
- package/src/components/component.ts +3 -3
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +92 -22
- package/src/components/driver.ts +193 -83
- package/src/components/index.ts +1 -6
- package/src/components/manifest-persistence.test.ts +2 -20
- package/src/components/manifest-persistence.ts +4 -24
- package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
- package/src/components/run-progress.ts +6 -6
- package/src/components/verbs/run-agent.ts +11 -17
- package/src/components/verbs/verify.ts +2 -2
- package/src/deep-observation.test.ts +75 -0
- package/src/deep-observation.ts +48 -0
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.ts +105 -2
- package/src/discovery/param-deps.test.ts +112 -1
- package/src/discovery/param-deps.ts +193 -0
- package/src/discovery/sandbox/bundle.ts +15 -1
- package/src/discovery/sandbox/config-wire.test.ts +2 -2
- package/src/discovery/sandbox/config-wire.ts +2 -2
- package/src/fold/fold.test.ts +56 -0
- package/src/fold/foldable-helpers.ts +81 -1
- package/src/fold-provenance-exclusion.test.ts +186 -0
- package/src/fold-provenance.test.ts +495 -0
- package/src/fold-provenance.ts +441 -0
- package/src/graph-ops.test.ts +2 -2
- package/src/graph-ops.ts +1 -1
- package/src/held-elsewhere.test.ts +102 -0
- package/src/held-elsewhere.ts +114 -0
- package/src/index.ts +3 -0
- package/src/kubectl-context.ts +3 -3
- package/src/lexicon-config.test.ts +5 -5
- package/src/lexicon-config.ts +4 -4
- package/src/lexicon-plugin-helpers.ts +1 -1
- package/src/lexicon.ts +120 -14
- package/src/lifecycle/change-set.test.ts +49 -0
- package/src/lifecycle/change-set.ts +67 -0
- package/src/lifecycle/converge-ledger.ts +1 -1
- package/src/lifecycle/deep-diff.test.ts +267 -14
- package/src/lifecycle/deep-diff.ts +217 -14
- package/src/lifecycle/deep-observe.test.ts +3 -1
- package/src/lifecycle/deep-observe.ts +16 -6
- package/src/lifecycle/gate-ledger.test.ts +74 -2
- package/src/lifecycle/gate-ledger.ts +143 -36
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/release-ledger.ts +2 -2
- package/src/lifecycle/run-ledger.test.ts +268 -0
- package/src/lifecycle/run-ledger.ts +201 -0
- package/src/lifecycle/symptoms.ts +3 -3
- package/src/lint/config.test.ts +82 -1
- package/src/lint/config.ts +112 -3
- package/src/lint/policy.test.ts +1 -1
- package/src/lint/post-synth.test.ts +31 -0
- package/src/lint/post-synth.ts +54 -1
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
- package/src/lint/rules/comp/comp.test.ts +1 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
- package/src/lint/rules/comp/index.ts +3 -3
- package/src/lint/rules/op/index.ts +23 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
- package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
- package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
- package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
- package/src/lint/rules/op/support.ts +11 -0
- package/src/lint/suppressions.test.ts +163 -0
- package/src/lint/suppressions.ts +204 -0
- package/src/live-endpoint.ts +1 -1
- package/src/op/activities/activity-contracts.ts +66 -0
- package/src/op/activities/apply.test.ts +567 -0
- package/src/op/activities/apply.ts +662 -0
- package/src/op/activities/build.ts +26 -0
- package/src/op/activities/converge.test.ts +330 -0
- package/src/op/activities/converge.ts +449 -0
- package/src/op/activities/env-teardown.test.ts +187 -0
- package/src/op/activities/env-teardown.ts +171 -0
- package/src/op/activities/guard-validate.test.ts +139 -0
- package/src/op/activities/guard-validate.ts +223 -0
- package/src/op/activities/http-check.test.ts +48 -0
- package/src/op/activities/http-check.ts +55 -0
- package/src/op/activities/index.ts +119 -0
- package/src/op/activities/lexicon-upgrade.test.ts +492 -0
- package/src/op/activities/lexicon-upgrade.ts +673 -0
- package/src/op/activities/lifecycle.ts +85 -0
- package/src/op/activities/pipeline-audit.test.ts +67 -0
- package/src/op/activities/pipeline-audit.ts +201 -0
- package/src/op/activities/policy.test.ts +19 -0
- package/src/op/activities/policy.ts +37 -0
- package/src/op/activities/reconcile.test.ts +81 -0
- package/src/op/activities/reconcile.ts +187 -0
- package/src/op/activities/shell.ts +26 -0
- package/src/op/activities/teardown.ts +22 -0
- package/src/op/activities/util.ts +6 -0
- package/src/op/activities/wait.ts +69 -0
- package/src/op/activities/workflow-audit.test.ts +90 -0
- package/src/op/activities/workflow-audit.ts +203 -0
- package/src/op/activity-contract-registry.test.ts +98 -0
- package/src/op/activity-contract-registry.ts +114 -0
- package/src/op/activity-contract.test.ts +1 -1
- package/src/op/activity-contract.ts +37 -33
- package/src/op/activity-failure.ts +34 -0
- package/src/op/activity-profiles.test.ts +56 -0
- package/src/op/activity-profiles.ts +116 -0
- package/src/op/activity-registry.test.ts +37 -28
- package/src/op/activity-registry.ts +27 -56
- package/src/op/activity-runtime.ts +3 -47
- package/src/op/builders-exports.test.ts +0 -49
- package/src/op/builders-typed.test.ts +164 -0
- package/src/op/builders.ts +123 -39
- package/src/op/composites/apply-op.ts +208 -0
- package/src/op/composites/composites.test.ts +366 -0
- package/src/op/composites/converge-op.test.ts +168 -0
- package/src/op/composites/converge-op.ts +214 -0
- package/src/op/composites/index.ts +26 -0
- package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
- package/src/op/composites/lexicon-upgrade-op.ts +134 -0
- package/src/op/composites/pipeline-audit-op.test.ts +25 -0
- package/src/op/composites/pipeline-audit-op.ts +78 -0
- package/src/op/composites/reconcile-op.test.ts +51 -0
- package/src/op/composites/reconcile-op.ts +107 -0
- package/src/op/composites/watch-op.ts +108 -0
- package/src/op/composites/workflow-audit-op.test.ts +25 -0
- package/src/op/composites/workflow-audit-op.ts +88 -0
- package/src/op/converge-rule.ts +12 -10
- package/src/op/cron.test.ts +128 -0
- package/src/op/cron.ts +149 -0
- package/src/op/discover-root.test.ts +105 -1
- package/src/op/discover.test.ts +6 -2
- package/src/op/discover.ts +84 -16
- package/src/op/duration.ts +21 -0
- package/src/op/effect-step.test.ts +26 -11
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate.ts +155 -0
- package/src/op/generate-pipeline.test.ts +65 -3
- package/src/op/generate-pipeline.ts +19 -2
- package/src/op/index.ts +39 -6
- package/src/op/local-executor.test.ts +163 -34
- package/src/op/local-executor.ts +370 -102
- package/src/op/local-output.test.ts +95 -11
- package/src/op/local-output.ts +33 -4
- package/src/op/op-ir.test.ts +281 -0
- package/src/op/op-ir.ts +417 -0
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +26 -5
- package/src/op/operator.test.ts +175 -6
- package/src/op/operator.ts +63 -16
- package/src/op/receipt-store.ts +1 -1
- package/src/op/resource.ts +20 -2
- package/src/op/runtime.ts +203 -0
- package/src/op/runtimes/local.test.ts +181 -0
- package/src/op/runtimes/local.ts +212 -0
- package/src/op/step-output-ref.ts +15 -13
- package/src/op/types.ts +84 -24
- package/src/ownership.ts +1 -1
- package/src/provenance.test.ts +6 -0
- package/src/provenance.ts +21 -1
- package/src/serializer-walker.test.ts +42 -0
- package/src/serializer-walker.ts +21 -2
- package/dist/cli/handlers/op-progress.d.ts +0 -90
- package/dist/cli/handlers/op-progress.d.ts.map +0 -1
- package/dist/cli/handlers/run-client.d.ts +0 -127
- package/dist/cli/handlers/run-client.d.ts.map +0 -1
- package/dist/cli/handlers/run-report.d.ts +0 -5
- package/dist/cli/handlers/run-report.d.ts.map +0 -1
- package/dist/components/temporal-codegen-loader.d.ts +0 -27
- package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
- package/src/cli/handlers/op-progress.test.ts +0 -268
- package/src/cli/handlers/op-progress.ts +0 -248
- package/src/cli/handlers/run-client.test.ts +0 -82
- package/src/cli/handlers/run-client.ts +0 -219
- package/src/cli/handlers/run-report.test.ts +0 -62
- package/src/cli/handlers/run-report.ts +0 -122
- package/src/components/temporal-codegen-loader.ts +0 -41
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
export interface ShellCmdArgs {
|
|
7
|
+
cmd: string;
|
|
8
|
+
/** Additional environment variables. */
|
|
9
|
+
env?: Record<string, string>;
|
|
10
|
+
/** Working directory. Default: process.cwd(). */
|
|
11
|
+
cwd?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Run an arbitrary shell command.
|
|
16
|
+
* Uses fastIdempotent profile — 5m timeout, 3 retries.
|
|
17
|
+
*/
|
|
18
|
+
export async function shellCmd(args: ShellCmdArgs, signal?: AbortSignal): Promise<string> {
|
|
19
|
+
const { stdout, stderr } = await execAsync(args.cmd, {
|
|
20
|
+
cwd: args.cwd,
|
|
21
|
+
env: { ...process.env, ...args.env },
|
|
22
|
+
signal,
|
|
23
|
+
});
|
|
24
|
+
if (stderr) console.error(stderr);
|
|
25
|
+
return stdout.trim();
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
export interface ChantTeardownArgs {
|
|
7
|
+
/** Path to the chant project to tear down. */
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run `chant teardown` in the given project path.
|
|
13
|
+
* Uses longInfra profile — 20m timeout.
|
|
14
|
+
*/
|
|
15
|
+
export async function chantTeardown(args: ChantTeardownArgs, signal?: AbortSignal): Promise<void> {
|
|
16
|
+
const { stdout, stderr } = await execAsync("npm run teardown", {
|
|
17
|
+
cwd: args.path,
|
|
18
|
+
signal,
|
|
19
|
+
});
|
|
20
|
+
if (stdout) console.log(stdout);
|
|
21
|
+
if (stderr) console.error(stderr);
|
|
22
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { sleep } from "./util";
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export interface WaitForStackArgs {
|
|
8
|
+
/** Stack name — used to locate the kubectl deployment/statefulset to poll. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Kubernetes namespace. */
|
|
11
|
+
namespace?: string;
|
|
12
|
+
/** kubectl context. */
|
|
13
|
+
context?: string;
|
|
14
|
+
/** Poll interval in ms. Default: 10000. */
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Poll until a Kubernetes Deployment or StatefulSet named `name` is fully rolled out.
|
|
20
|
+
* Uses the `k8sWait` profile — 15m timeout.
|
|
21
|
+
*
|
|
22
|
+
* Progress goes to stdout, one line per poll, which is the executor's step-record
|
|
23
|
+
* channel: the local executor streams an activity's output alongside the step it
|
|
24
|
+
* belongs to, so a long rollout is visible without a liveness protocol. Nothing
|
|
25
|
+
* mid-run is durable here by design (chant #2114) — the op re-runs and converges.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT migrated onto the generic `waitForReady` (#957): `kubectl
|
|
28
|
+
* rollout status` encodes rollout-specific semantics — progress-deadline
|
|
29
|
+
* timeouts, partial/paused rollouts, generation tracking — that a status-field
|
|
30
|
+
* predicate spec does not replicate, and it targets built-in workloads rather
|
|
31
|
+
* than arbitrary operator CRDs. `waitForReady` is the right tool for CRD
|
|
32
|
+
* readiness; `waitForStack` stays the right tool for workload rollouts.
|
|
33
|
+
*/
|
|
34
|
+
export async function waitForStack(args: WaitForStackArgs, signal?: AbortSignal): Promise<void> {
|
|
35
|
+
const ns = args.namespace ? `-n ${args.namespace}` : "";
|
|
36
|
+
const ctx = args.context ? `--context ${args.context}` : "";
|
|
37
|
+
const interval = args.intervalMs ?? 10_000;
|
|
38
|
+
let attempt = 0;
|
|
39
|
+
|
|
40
|
+
while (true) {
|
|
41
|
+
if (signal?.aborted) throw new Error("waitForStack aborted");
|
|
42
|
+
attempt++;
|
|
43
|
+
console.log(`[waitForStack] ${args.name}: poll ${attempt}`);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await execAsync(
|
|
47
|
+
`kubectl rollout status deployment/${args.name} ${ns} ${ctx} --timeout=30s`,
|
|
48
|
+
{ signal },
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
} catch {
|
|
52
|
+
if (signal?.aborted) throw new Error("waitForStack aborted");
|
|
53
|
+
// Not ready yet — wait and retry
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await execAsync(
|
|
58
|
+
`kubectl rollout status statefulset/${args.name} ${ns} ${ctx} --timeout=30s`,
|
|
59
|
+
{ signal },
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
} catch {
|
|
63
|
+
if (signal?.aborted) throw new Error("waitForStack aborted");
|
|
64
|
+
// Not ready yet
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await sleep(interval, signal);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
workflowSupplyChainAudit,
|
|
4
|
+
collectAuditRefs,
|
|
5
|
+
type ActionRefResolution,
|
|
6
|
+
type ActionRefResolver,
|
|
7
|
+
} from "./workflow-audit";
|
|
8
|
+
|
|
9
|
+
const SHA = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b";
|
|
10
|
+
|
|
11
|
+
const WORKFLOW = `name: CI
|
|
12
|
+
on:
|
|
13
|
+
push:
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@${SHA} # v4.1.0
|
|
19
|
+
- uses: evil/typo-action@v1
|
|
20
|
+
- uses: archived/old-action@v2
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
/** Recorded upstream responses — no live network. */
|
|
24
|
+
function recordedResolver(records: Record<string, ActionRefResolution>): ActionRefResolver {
|
|
25
|
+
return async (slug, ref) =>
|
|
26
|
+
records[`${slug}@${ref}`] ?? { exists: true, tags: [], archived: false, advisories: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("collectAuditRefs (#292)", () => {
|
|
30
|
+
test("collects external refs with version comments, skipping local/docker", () => {
|
|
31
|
+
const yaml = `jobs:
|
|
32
|
+
a:
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@${SHA} # v4.1.0
|
|
35
|
+
- uses: ./.github/actions/local
|
|
36
|
+
- uses: docker://alpine:3.19
|
|
37
|
+
- uses: org/reusable/.github/workflows/x.yml@v1
|
|
38
|
+
`;
|
|
39
|
+
const refs = collectAuditRefs(yaml);
|
|
40
|
+
const slugs = refs.map((r) => r.slug);
|
|
41
|
+
expect(slugs).toContain("actions/checkout");
|
|
42
|
+
expect(slugs).toContain("org/reusable");
|
|
43
|
+
expect(slugs).not.toContain("./.github");
|
|
44
|
+
expect(refs.find((r) => r.slug === "actions/checkout")?.comment).toBe("v4.1.0");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("workflowSupplyChainAudit (#292)", () => {
|
|
49
|
+
test("flags stale pin, impostor, archived, and pin/comment mismatch", async () => {
|
|
50
|
+
const resolver = recordedResolver({
|
|
51
|
+
// SHA no longer on any tag → stale; actual tag differs from comment → mismatch
|
|
52
|
+
[`actions/checkout@${SHA}`]: { exists: true, tags: ["v4.2.0"], archived: false, advisories: [] },
|
|
53
|
+
"evil/typo-action@v1": { exists: false, tags: [], archived: false, advisories: [] },
|
|
54
|
+
"archived/old-action@v2": { exists: true, tags: ["v2"], archived: true, advisories: ["GHSA-xxxx"] },
|
|
55
|
+
});
|
|
56
|
+
const result = await workflowSupplyChainAudit({ yaml: WORKFLOW, resolver, mode: "issue" });
|
|
57
|
+
const kinds = result.findings.map((f) => `${f.slug}:${f.kind}`);
|
|
58
|
+
|
|
59
|
+
expect(kinds).toContain("evil/typo-action:impostor");
|
|
60
|
+
expect(kinds).toContain("archived/old-action:archived");
|
|
61
|
+
expect(kinds).toContain("archived/old-action:advisory");
|
|
62
|
+
expect(kinds).toContain("actions/checkout:pin-comment-mismatch");
|
|
63
|
+
expect(result.mode).toBe("issue");
|
|
64
|
+
expect(result.summary).toContain("Workflow supply-chain audit");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("flags a stale SHA pin with no upstream tag", async () => {
|
|
68
|
+
const resolver = recordedResolver({
|
|
69
|
+
[`actions/checkout@${SHA}`]: { exists: true, tags: [], archived: false, advisories: [] },
|
|
70
|
+
"evil/typo-action@v1": { exists: true, tags: ["v1"], archived: false, advisories: [] },
|
|
71
|
+
"archived/old-action@v2": { exists: true, tags: ["v2"], archived: false, advisories: [] },
|
|
72
|
+
});
|
|
73
|
+
const result = await workflowSupplyChainAudit({ yaml: WORKFLOW, resolver });
|
|
74
|
+
expect(result.findings.some((f) => f.slug === "actions/checkout" && f.kind === "stale-pin")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("clean workflow yields no findings", async () => {
|
|
78
|
+
const yaml = `jobs:
|
|
79
|
+
build:
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@${SHA}
|
|
82
|
+
`;
|
|
83
|
+
const resolver = recordedResolver({
|
|
84
|
+
[`actions/checkout@${SHA}`]: { exists: true, tags: ["v4"], archived: false, advisories: [] },
|
|
85
|
+
});
|
|
86
|
+
const result = await workflowSupplyChainAudit({ yaml, resolver });
|
|
87
|
+
expect(result.findings).toHaveLength(0);
|
|
88
|
+
expect(result.summary).toContain("No live drift");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workflowSupplyChainAudit — the live counterpart to the github post-synth
|
|
3
|
+
* supply-chain checks (#286-#291).
|
|
4
|
+
*
|
|
5
|
+
* Some workflow-reference checks can only be answered against a moving external
|
|
6
|
+
* truth: whether a pinned SHA still corresponds to a real tag/release upstream,
|
|
7
|
+
* whether a referenced commit exists in the repo it claims, whether a ref is
|
|
8
|
+
* confusable as both a tag and a branch, whether a pin matches its version
|
|
9
|
+
* comment, or whether a new advisory now covers an action already in use. These
|
|
10
|
+
* change over time independent of the source, so they cannot live in the
|
|
11
|
+
* deterministic build — this activity owns *only* the checks that require live
|
|
12
|
+
* resolution.
|
|
13
|
+
*
|
|
14
|
+
* It is dependency-free and primitives-only: it reads the emitted workflow YAML
|
|
15
|
+
* (passed as a string) and resolves references through an injectable
|
|
16
|
+
* `ActionRefResolver`, so `chant run` can schedule it and tests can run
|
|
17
|
+
* it against recorded responses with no live network.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** What the audit produces on findings, mirroring ReconcileOp's modes. */
|
|
21
|
+
export type WorkflowAuditMode = "report" | "issue" | "pull-request";
|
|
22
|
+
|
|
23
|
+
/** Live resolution of a single action reference against its upstream. */
|
|
24
|
+
export interface ActionRefResolution {
|
|
25
|
+
/** Does the ref/SHA exist in the claimed repo? */
|
|
26
|
+
exists: boolean;
|
|
27
|
+
/** Tag/release names that point at this commit (empty for a SHA ⇒ stale pin). */
|
|
28
|
+
tags: string[];
|
|
29
|
+
/** Is the upstream repository archived? */
|
|
30
|
+
archived: boolean;
|
|
31
|
+
/** Disclosed advisory identifiers affecting this action. */
|
|
32
|
+
advisories: string[];
|
|
33
|
+
/** Is the ref resolvable as BOTH a tag and a branch upstream? */
|
|
34
|
+
ambiguousRef?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Pluggable upstream resolver — overridden in tests with recorded responses. */
|
|
38
|
+
export type ActionRefResolver = (slug: string, ref: string) => Promise<ActionRefResolution>;
|
|
39
|
+
|
|
40
|
+
export type WorkflowAuditFindingKind =
|
|
41
|
+
| "stale-pin"
|
|
42
|
+
| "impostor"
|
|
43
|
+
| "ambiguous-ref"
|
|
44
|
+
| "pin-comment-mismatch"
|
|
45
|
+
| "advisory"
|
|
46
|
+
| "archived";
|
|
47
|
+
|
|
48
|
+
export interface WorkflowAuditFinding {
|
|
49
|
+
/** owner/repo slug. */
|
|
50
|
+
slug: string;
|
|
51
|
+
/** The pinned ref/SHA. */
|
|
52
|
+
ref: string;
|
|
53
|
+
kind: WorkflowAuditFindingKind;
|
|
54
|
+
detail: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WorkflowAuditArgs {
|
|
58
|
+
/** One emitted workflow YAML document. */
|
|
59
|
+
yaml?: string;
|
|
60
|
+
/** Several emitted workflow YAML documents. */
|
|
61
|
+
yamls?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Directory of emitted workflow files (`*.yml`/`*.yaml`) to read at run time
|
|
64
|
+
* when no `yaml`/`yamls` is given — the form used inside a scheduled Op.
|
|
65
|
+
* Default `.github/workflows`.
|
|
66
|
+
*/
|
|
67
|
+
workflowsDir?: string;
|
|
68
|
+
/** What to produce. Default: report. */
|
|
69
|
+
mode?: WorkflowAuditMode;
|
|
70
|
+
/** Injectable upstream resolver. Defaults to the live GitHub REST resolver. */
|
|
71
|
+
resolver?: ActionRefResolver;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read `*.yml`/`*.yaml` files from a directory (best-effort). */
|
|
75
|
+
async function readWorkflowDir(dir: string): Promise<string[]> {
|
|
76
|
+
try {
|
|
77
|
+
const { readdir, readFile } = await import("node:fs/promises");
|
|
78
|
+
const { join } = await import("node:path");
|
|
79
|
+
const files = (await readdir(dir)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
80
|
+
return Promise.all(files.map((f) => readFile(join(dir, f), "utf-8")));
|
|
81
|
+
} catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface WorkflowAuditResult {
|
|
87
|
+
mode: WorkflowAuditMode;
|
|
88
|
+
findings: WorkflowAuditFinding[];
|
|
89
|
+
/** Markdown summary used as the PR/issue body or printed in report mode. */
|
|
90
|
+
summary: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface CollectedRef {
|
|
94
|
+
slug: string;
|
|
95
|
+
ref: string;
|
|
96
|
+
/** Adjacent `# vX.Y.Z` comment, when present. */
|
|
97
|
+
comment?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const SHA_RE = /^[0-9a-f]{40}$/;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Collect external action references (`uses: owner/repo@ref`, with an optional
|
|
104
|
+
* trailing version comment) from a workflow YAML. Local (`./`) and `docker://`
|
|
105
|
+
* references are excluded.
|
|
106
|
+
*/
|
|
107
|
+
export function collectAuditRefs(yaml: string): CollectedRef[] {
|
|
108
|
+
const out: CollectedRef[] = [];
|
|
109
|
+
const re = /uses:\s*['"]?([\w.-]+\/[\w.-]+(?:\/[\w./-]+)?)@([\w.-]+)['"]?\s*(?:#\s*(\S+))?/g;
|
|
110
|
+
let m: RegExpExecArray | null;
|
|
111
|
+
while ((m = re.exec(yaml)) !== null) {
|
|
112
|
+
out.push({ slug: m[1].split("/").slice(0, 2).join("/"), ref: m[2], comment: m[3] });
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Default resolver: query the GitHub REST API for whether the ref exists, what
|
|
119
|
+
* tags point at it, archive status, and advisories. Best-effort and resilient —
|
|
120
|
+
* a network/HTTP error yields an "unknown" resolution (exists=true, no findings)
|
|
121
|
+
* rather than a false positive.
|
|
122
|
+
*/
|
|
123
|
+
export const defaultActionRefResolver: ActionRefResolver = async (slug, ref) => {
|
|
124
|
+
const base = `https://api.github.com/repos/${slug}`;
|
|
125
|
+
const headers: Record<string, string> = { Accept: "application/vnd.github+json" };
|
|
126
|
+
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
127
|
+
const unknown: ActionRefResolution = { exists: true, tags: [], archived: false, advisories: [] };
|
|
128
|
+
try {
|
|
129
|
+
const repoResp = await fetch(base, { headers });
|
|
130
|
+
if (repoResp.status === 404) return { exists: false, tags: [], archived: false, advisories: [] };
|
|
131
|
+
if (!repoResp.ok) return unknown;
|
|
132
|
+
const repo = (await repoResp.json()) as { archived?: boolean };
|
|
133
|
+
|
|
134
|
+
const refResp = await fetch(`${base}/commits/${ref}`, { headers });
|
|
135
|
+
if (refResp.status === 404) return { exists: false, tags: [], archived: !!repo.archived, advisories: [] };
|
|
136
|
+
|
|
137
|
+
let tags: string[] = [];
|
|
138
|
+
if (SHA_RE.test(ref)) {
|
|
139
|
+
const tagsResp = await fetch(`${base}/tags?per_page=100`, { headers });
|
|
140
|
+
if (tagsResp.ok) {
|
|
141
|
+
const tagList = (await tagsResp.json()) as Array<{ name: string; commit?: { sha?: string } }>;
|
|
142
|
+
tags = tagList.filter((t) => t.commit?.sha === ref).map((t) => t.name);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { exists: refResp.ok, tags, archived: !!repo.archived, advisories: [] };
|
|
146
|
+
} catch {
|
|
147
|
+
return unknown;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
function renderSummary(findings: WorkflowAuditFinding[]): string {
|
|
152
|
+
if (findings.length === 0) return "## Workflow supply-chain audit\n\nNo live drift detected against upstream references.\n";
|
|
153
|
+
let out = `## Workflow supply-chain audit\n\n${findings.length} finding(s) against live upstream truth:\n\n`;
|
|
154
|
+
out += "| Action | Ref | Finding | Detail |\n|---|---|---|---|\n";
|
|
155
|
+
for (const f of findings) out += `| ${f.slug} | ${f.ref} | ${f.kind} | ${f.detail} |\n`;
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolve each pinned external reference in the emitted workflows against live
|
|
161
|
+
* upstreams and report drift.
|
|
162
|
+
*/
|
|
163
|
+
export async function workflowSupplyChainAudit(args: WorkflowAuditArgs): Promise<WorkflowAuditResult> {
|
|
164
|
+
const resolver = args.resolver ?? defaultActionRefResolver;
|
|
165
|
+
const mode = args.mode ?? "report";
|
|
166
|
+
let yamls = args.yamls ?? (args.yaml ? [args.yaml] : []);
|
|
167
|
+
if (yamls.length === 0) {
|
|
168
|
+
yamls = await readWorkflowDir(args.workflowsDir ?? ".github/workflows");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const findings: WorkflowAuditFinding[] = [];
|
|
172
|
+
const seen = new Set<string>();
|
|
173
|
+
for (const yaml of yamls) {
|
|
174
|
+
for (const { slug, ref, comment } of collectAuditRefs(yaml)) {
|
|
175
|
+
const key = `${slug}@${ref}`;
|
|
176
|
+
if (seen.has(key)) continue;
|
|
177
|
+
seen.add(key);
|
|
178
|
+
|
|
179
|
+
const res = await resolver(slug, ref);
|
|
180
|
+
if (!res.exists) {
|
|
181
|
+
findings.push({ slug, ref, kind: "impostor", detail: `ref does not exist in ${slug} — possible impostor or deleted ref` });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (SHA_RE.test(ref) && res.tags.length === 0) {
|
|
185
|
+
findings.push({ slug, ref, kind: "stale-pin", detail: "pinned SHA is no longer on any tag/release upstream" });
|
|
186
|
+
}
|
|
187
|
+
if (res.ambiguousRef) {
|
|
188
|
+
findings.push({ slug, ref, kind: "ambiguous-ref", detail: "ref resolves as both a tag and a branch upstream (symbolic-ref confusion)" });
|
|
189
|
+
}
|
|
190
|
+
if (comment && res.tags.length > 0 && !res.tags.includes(comment)) {
|
|
191
|
+
findings.push({ slug, ref, kind: "pin-comment-mismatch", detail: `pin comment "${comment}" does not match the SHA's actual tag(s): ${res.tags.join(", ")}` });
|
|
192
|
+
}
|
|
193
|
+
if (res.archived) {
|
|
194
|
+
findings.push({ slug, ref, kind: "archived", detail: `${slug} has been archived upstream since pinning` });
|
|
195
|
+
}
|
|
196
|
+
for (const adv of res.advisories) {
|
|
197
|
+
findings.push({ slug, ref, kind: "advisory", detail: `disclosed advisory ${adv} covers ${slug}` });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { mode, findings, summary: renderSummary(findings) };
|
|
203
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { loadActivityContracts, mergeActivityContracts } from "./activity-contract-registry";
|
|
4
|
+
import { activityContract, type ActivityContract } from "./activity-contract";
|
|
5
|
+
|
|
6
|
+
describe("loadActivityContracts", () => {
|
|
7
|
+
test("core's own contracts load with no lexicons configured", async () => {
|
|
8
|
+
const contracts = await loadActivityContracts();
|
|
9
|
+
expect(contracts.get("shellCmd")).toBeDefined();
|
|
10
|
+
expect(contracts.get("httpCheck")?.returns).toBeDefined();
|
|
11
|
+
expect(contracts.get("lifecycleDiff")?.name).toBe("lifecycleDiff");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("a lexicon's contracts are resolved by the same subpath convention loadActivities uses", async () => {
|
|
15
|
+
const contracts = await loadActivityContracts(["terraform"]);
|
|
16
|
+
// terraform ships `@intentius/chant-lexicon-terraform/op/activity-contracts`.
|
|
17
|
+
expect(contracts.get("terraformPlan")).toBeDefined();
|
|
18
|
+
expect(contracts.get("terraformApply")).toBeDefined();
|
|
19
|
+
// Core's own are still there — the lexicon adds, it does not replace.
|
|
20
|
+
expect(contracts.get("shellCmd")).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("terraformPlan's declared return schema carries the fields an Apply step references", async () => {
|
|
24
|
+
const contracts = await loadActivityContracts(["terraform"]);
|
|
25
|
+
const returns = contracts.get("terraformPlan")?.returns as z.ZodObject<z.ZodRawShape>;
|
|
26
|
+
expect(Object.keys(returns.shape)).toEqual(
|
|
27
|
+
expect.arrayContaining(["planFile", "text", "changed", "adds", "changes", "destroys"]),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("an absent lexicon contributes nothing and does not throw", async () => {
|
|
32
|
+
const contracts = await loadActivityContracts(["no-such-lexicon-anywhere"]);
|
|
33
|
+
expect(contracts.get("shellCmd")).toBeDefined();
|
|
34
|
+
expect(contracts.size).toBeGreaterThan(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("a plugin member contributes contracts alongside the conventional subpath", async () => {
|
|
38
|
+
const contracts = await loadActivityContracts([
|
|
39
|
+
{
|
|
40
|
+
name: "no-such-lexicon-anywhere",
|
|
41
|
+
activityContracts: () => [activityContract("bespokeApply", z.strictObject({ target: z.string() }))],
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
expect(contracts.get("bespokeApply")).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("a plugin member that throws contributes nothing rather than failing the load", async () => {
|
|
48
|
+
const contracts = await loadActivityContracts([
|
|
49
|
+
{
|
|
50
|
+
name: "no-such-lexicon-anywhere",
|
|
51
|
+
activityContracts: () => {
|
|
52
|
+
throw new Error("boom");
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
expect(contracts.get("shellCmd")).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("the last lexicon in wins a name collision, as loadActivities does for implementations", async () => {
|
|
60
|
+
const mine = activityContract("shellCmd", z.strictObject({ cmd: z.string(), extra: z.string() }));
|
|
61
|
+
const contracts = await loadActivityContracts([
|
|
62
|
+
{ name: "no-such-lexicon-anywhere", activityContracts: () => [mine] },
|
|
63
|
+
]);
|
|
64
|
+
expect(contracts.get("shellCmd")).toBe(mine);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("mergeActivityContracts", () => {
|
|
69
|
+
const base = new Map<string, ActivityContract>([
|
|
70
|
+
["shellCmd", activityContract("shellCmd", z.strictObject({ cmd: z.string() }))],
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
test("an undefined loaded map returns the base unchanged", () => {
|
|
74
|
+
expect(mergeActivityContracts(base, undefined)).toBe(base);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("an empty loaded map returns the base unchanged", () => {
|
|
78
|
+
expect(mergeActivityContracts(base, new Map())).toBe(base);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("loaded contracts are added without mutating the base", () => {
|
|
82
|
+
const loaded = new Map<string, ActivityContract>([
|
|
83
|
+
["terraformPlan", activityContract("terraformPlan", z.strictObject({ root: z.string() }))],
|
|
84
|
+
]);
|
|
85
|
+
const merged = mergeActivityContracts(base, loaded);
|
|
86
|
+
expect(merged.get("terraformPlan")).toBeDefined();
|
|
87
|
+
expect(merged.get("shellCmd")).toBeDefined();
|
|
88
|
+
expect(base.has("terraformPlan")).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("the loaded map wins a name collision — it is the one resolved against this build's lexicons", () => {
|
|
92
|
+
const loaded = new Map<string, ActivityContract>([
|
|
93
|
+
["shellCmd", activityContract("shellCmd", z.strictObject({ cmd: z.string(), cwd: z.string() }))],
|
|
94
|
+
]);
|
|
95
|
+
const merged = mergeActivityContracts(base, loaded);
|
|
96
|
+
expect(merged.get("shellCmd")).toBe(loaded.get("shellCmd"));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity contract registry — the schema-shaped sibling of
|
|
3
|
+
* `./activity-registry.ts` (chant #2101).
|
|
4
|
+
*
|
|
5
|
+
* `activity-contract.ts` left contract ownership per-caller on purpose: an
|
|
6
|
+
* activity declares its contract alongside its implementation, and whoever
|
|
7
|
+
* builds a contract map decides which activities it covers. What was missing
|
|
8
|
+
* was the other half of that arrangement, a way for the checks that validate
|
|
9
|
+
* Op steps to SEE a contract a lexicon declared. OPS012 and OPS013
|
|
10
|
+
* (`../lint/rules/op/`) walk every Op entity in the build, including steps
|
|
11
|
+
* calling `terraformPlan`, `k3sInstall`, `flyApply` or `gcpApply`, over
|
|
12
|
+
* core's own static table alone. Since #2122 moved those checks into core
|
|
13
|
+
* they fire on every project that declares an Op, so an Op built out of one
|
|
14
|
+
* lexicon's steps failed `chant build` outright even though `loadActivities`
|
|
15
|
+
* resolves the very same names at run time. Both terraform examples that
|
|
16
|
+
* carry an Op kept it outside `src/` because of the older, lexicon-hosted
|
|
17
|
+
* form of the same gap, which meant no example ever proved its Op builds.
|
|
18
|
+
*
|
|
19
|
+
* The resolution mirrors activities exactly, by convention:
|
|
20
|
+
* `@intentius/chant-lexicon-<name>/op/activity-contracts` is imported for
|
|
21
|
+
* every configured lexicon and every {@link ActivityContract} it exports is
|
|
22
|
+
* collected, keyed by the contract's declared `name`. A lexicon that ships
|
|
23
|
+
* no such module is skipped, the same way `loadActivities` skips one that
|
|
24
|
+
* contributes no activities. A plugin may also hand its contracts over
|
|
25
|
+
* directly through {@link LexiconActivityContractContributor}, for a lexicon
|
|
26
|
+
* whose contracts are not reachable at a subpath (a bundled single-file
|
|
27
|
+
* plugin, a project-local one).
|
|
28
|
+
*
|
|
29
|
+
* The merged map reaches a post-synth check as
|
|
30
|
+
* `PostSynthContext.activityContracts` (`../lint/post-synth.ts`), filled in
|
|
31
|
+
* by whoever runs the checks: `chant build`, `chant lint` and
|
|
32
|
+
* `check-lexicon`'s example harness all do. A check merges it over its own
|
|
33
|
+
* base table rather than replacing it, so a context that carries none (a
|
|
34
|
+
* hand-built one in a test) behaves exactly as before this existed.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import * as coreContracts from "./activities/activity-contracts";
|
|
38
|
+
import { collectActivityContracts, type ActivityContract } from "./activity-contract";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The shape {@link loadActivityContracts} reads off a plugin: an optional
|
|
42
|
+
* member returning this lexicon's contracts directly, for one that cannot be
|
|
43
|
+
* reached by the subpath convention. Structural on purpose, so core's op
|
|
44
|
+
* layer does not depend on `../lexicon.ts`.
|
|
45
|
+
*/
|
|
46
|
+
export interface LexiconActivityContractContributor {
|
|
47
|
+
/** The lexicon's name, as in `@intentius/chant-lexicon-<name>`. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** This lexicon's activity contracts, if it declares them in code rather than at the conventional subpath. */
|
|
50
|
+
activityContracts?(): ActivityContract[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the activity-contract map for build-time validation: core's own
|
|
55
|
+
* contracts (`./activities/activity-contracts`, a static import so a project
|
|
56
|
+
* that has installed nothing but chant still gets them) plus every contract
|
|
57
|
+
* the named lexicons contribute.
|
|
58
|
+
*
|
|
59
|
+
* `lexicons` may be plain names or plugin-shaped objects; a plugin's
|
|
60
|
+
* `activityContracts()` is read after its conventional module, so a plugin
|
|
61
|
+
* that declares both wins on a name collision with itself. Across lexicons,
|
|
62
|
+
* last one in wins, which is the same rule `loadActivities` applies to
|
|
63
|
+
* implementations.
|
|
64
|
+
*
|
|
65
|
+
* Never throws: an absent lexicon, a missing subpath, or a plugin member that
|
|
66
|
+
* throws all leave the map as it was.
|
|
67
|
+
*/
|
|
68
|
+
export async function loadActivityContracts(
|
|
69
|
+
lexicons: ReadonlyArray<string | LexiconActivityContractContributor> = [],
|
|
70
|
+
): Promise<Map<string, ActivityContract>> {
|
|
71
|
+
const contracts = new Map<string, ActivityContract>();
|
|
72
|
+
|
|
73
|
+
collectActivityContracts(coreContracts as unknown as Record<string, unknown>, contracts);
|
|
74
|
+
|
|
75
|
+
for (const entry of lexicons) {
|
|
76
|
+
const name = typeof entry === "string" ? entry : entry.name;
|
|
77
|
+
try {
|
|
78
|
+
const spec = `@intentius/chant-lexicon-${name}/op/activity-contracts`;
|
|
79
|
+
collectActivityContracts((await import(spec)) as Record<string, unknown>, contracts);
|
|
80
|
+
} catch {
|
|
81
|
+
// Lexicon absent or declares no contracts at the conventional subpath.
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof entry !== "string" && typeof entry.activityContracts === "function") {
|
|
85
|
+
try {
|
|
86
|
+
for (const contract of entry.activityContracts()) contracts.set(contract.name, contract);
|
|
87
|
+
} catch {
|
|
88
|
+
// A plugin member that throws contributes nothing, the same as one that is absent.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return contracts;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Merge a check's own base contracts with the ones the build loaded, into the
|
|
98
|
+
* map `validateActivitySteps`/`validateStepOutputRefs` take. The loaded set
|
|
99
|
+
* wins on a name collision: it is the one resolved against the lexicons this
|
|
100
|
+
* build actually configured, whereas the base table is whatever the check
|
|
101
|
+
* imported statically.
|
|
102
|
+
*
|
|
103
|
+
* `loaded` being `undefined` is the ordinary case for a hand-built
|
|
104
|
+
* `PostSynthContext` and yields the base table unchanged.
|
|
105
|
+
*/
|
|
106
|
+
export function mergeActivityContracts(
|
|
107
|
+
base: ReadonlyMap<string, ActivityContract>,
|
|
108
|
+
loaded: ReadonlyMap<string, ActivityContract> | undefined,
|
|
109
|
+
): ReadonlyMap<string, ActivityContract> {
|
|
110
|
+
if (!loaded || loaded.size === 0) return base;
|
|
111
|
+
const merged = new Map(base);
|
|
112
|
+
for (const [name, contract] of loaded) merged.set(name, contract);
|
|
113
|
+
return merged;
|
|
114
|
+
}
|
|
@@ -90,7 +90,7 @@ describe("validateActivitySteps() — passing Ops", () => {
|
|
|
90
90
|
|
|
91
91
|
it("a step-output reference standing in for a typed arg does not trip a false-positive type mismatch (#1290)", () => {
|
|
92
92
|
// args.env is z.string() — a StepOutputRef placeholder there is not a
|
|
93
|
-
// string, but it's not a real error either;
|
|
93
|
+
// string, but it's not a real error either; OPS013 validates it against
|
|
94
94
|
// the producer's return schema separately.
|
|
95
95
|
const step = activity("lifecycleDiff", { env: stepOutput("diff", "name") as unknown as string });
|
|
96
96
|
const config = opWith({ phases: [phase("Diff", [step])] });
|