@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
package/src/cli/handlers/run.ts
CHANGED
|
@@ -1,156 +1,188 @@
|
|
|
1
|
-
import { resolve,
|
|
2
|
-
import {
|
|
3
|
-
import { createConnection } from "node:net";
|
|
4
|
-
import { spawn as spawnChild, type ChildProcess } from "node:child_process";
|
|
1
|
+
import { resolve, dirname } from "node:path";
|
|
2
|
+
import { writeFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
3
|
import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
|
|
6
4
|
import { discoverOps } from "../../op/discover";
|
|
7
5
|
import type { OpConfig } from "../../op/types";
|
|
8
6
|
import { loadActivities, loadProfiles } from "../../op/activity-registry";
|
|
9
|
-
import { runOpLocally,
|
|
7
|
+
import { runOpLocally, findPolicyGateStep, OpRunFailure, type StepRecord } from "../../op/local-executor";
|
|
8
|
+
import { approveCommand } from "../../op/gate";
|
|
9
|
+
import { createLocalOpRuntime } from "../../op/runtimes/local";
|
|
10
|
+
import type { OpRuntimeProvider, OpRunStatus } from "../../op/runtime";
|
|
10
11
|
import { renderHuman, renderJson } from "../../op/local-output";
|
|
12
|
+
import { loadPlugins } from "../plugins";
|
|
13
|
+
import { recordGateApproval } from "./operator";
|
|
11
14
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
12
15
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
13
16
|
import type { CommandContext } from "../registry";
|
|
14
|
-
import {
|
|
15
|
-
loadTemporalClient,
|
|
16
|
-
connectionOptions,
|
|
17
|
-
resolveWorkflowId,
|
|
18
|
-
resolveProfile,
|
|
19
|
-
fetchNormalizedHistory,
|
|
20
|
-
type WorkflowHandleRaw,
|
|
21
|
-
type WorkflowExecutionDescription,
|
|
22
|
-
type WorkflowHistoryRaw,
|
|
23
|
-
} from "./run-client";
|
|
24
|
-
import { generateReport, writeReport } from "./run-report";
|
|
25
|
-
import { extractIndexedStepRecords, stepRecordEmitter, countActivities, queryGateState } from "./op-progress";
|
|
26
|
-
import { runComponents, resolveComponentTargets, findComponentGate, listComponents } from "../../components/cli-support";
|
|
27
17
|
import { renderDriverHuman, renderDriverJson } from "../../components/driver-output";
|
|
28
18
|
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { maybeRecordAutoRelease, extractRunDigestFromPhaseOutputs } from "../../components/auto-release";
|
|
32
|
-
import { maybePersistBuildManifest, extractRunManifestFromPhaseOutputs } from "../../components/manifest-persistence";
|
|
19
|
+
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
20
|
+
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
33
21
|
import type { DriverComponentResult } from "../../components/driver";
|
|
34
|
-
import type { BuildParamProvenance } from "../../provenance";
|
|
35
22
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The exit code a run that stopped at an unapproved gate uses (#2119).
|
|
25
|
+
* Deliberately not 1: a gate is a standing fact waiting on a human, and a CI
|
|
26
|
+
* job that treats it as a failure would page someone for a decision nobody has
|
|
27
|
+
* made yet.
|
|
28
|
+
*/
|
|
29
|
+
export const GATED_EXIT_CODE = 3;
|
|
43
30
|
|
|
44
31
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
32
|
+
* `run list/status/log/cancel --components` reported a component's *durable*
|
|
33
|
+
* run state — a run that outlives the CLI process and can be queried, signalled
|
|
34
|
+
* or cancelled afterwards (#589). #2116 removed the runtime that provided it.
|
|
35
|
+
* The local driver runs a component to completion inside this process, so there
|
|
36
|
+
* is no separate run left to ask about once the command returns.
|
|
37
|
+
*
|
|
38
|
+
* Returns 1; the caller returns it straight back.
|
|
49
39
|
*/
|
|
50
|
-
function
|
|
51
|
-
if (ctx.args.temporal) return true;
|
|
40
|
+
function refuseDurableComponentSubcommand(what: string, hint: string): number {
|
|
52
41
|
console.error(formatError({
|
|
53
|
-
message: `\`${what}\`
|
|
54
|
-
hint
|
|
42
|
+
message: `\`${what}\` read a component's durable run state, which #2116 removed`,
|
|
43
|
+
hint,
|
|
55
44
|
}));
|
|
56
|
-
return
|
|
45
|
+
return 1;
|
|
57
46
|
}
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
const { config } = await loadChantConfig(projectPath);
|
|
61
|
-
const profile = resolveProfile(config as Record<string, unknown>, profileName);
|
|
62
|
-
const { Connection, Client } = await loadTemporalClient();
|
|
63
|
-
const connection = await Connection.connect(connectionOptions(profile));
|
|
64
|
-
const client = new Client({ connection, namespace: profile.namespace });
|
|
65
|
-
return { client, profile, config };
|
|
66
|
-
}
|
|
48
|
+
// ── The runtime seam (#2121) ────────────────────────────────────────────────
|
|
67
49
|
|
|
68
50
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
51
|
+
* Resolve the one runtime this invocation talks to.
|
|
52
|
+
*
|
|
53
|
+
* `--on <name>` picks the named lexicon's `opRuntime` (`../../lexicon.ts`);
|
|
54
|
+
* without it, core's built-in `local` provider runs the Op in this process.
|
|
55
|
+
* Every `chant run` subcommand goes through whichever comes back, so there is
|
|
56
|
+
* one dispatch path rather than a branch per runtime.
|
|
57
|
+
*
|
|
58
|
+
* A lexicon already loaded into the command context wins the lookup — that is
|
|
59
|
+
* how a test hands in a stub, and how a command that loaded plugins for its
|
|
60
|
+
* own reasons avoids loading them twice. Otherwise the project's configured
|
|
61
|
+
* lexicons are loaded on demand: `chant run` is not a `requiresPlugins`
|
|
62
|
+
* command, and a plain local run must not start paying for plugin resolution.
|
|
63
|
+
*
|
|
64
|
+
* Returns `undefined` after printing an actionable error — an unconfigured
|
|
65
|
+
* name is answered with the configured list, a configured lexicon that hosts
|
|
66
|
+
* nothing is named outright.
|
|
75
67
|
*/
|
|
76
|
-
async function
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
68
|
+
async function resolveOpRuntime(ctx: CommandContext): Promise<OpRuntimeProvider | undefined> {
|
|
69
|
+
const on = ctx.args.on;
|
|
70
|
+
if (!on || on === "local") return createLocalOpRuntime({ projectPath: resolve(".") });
|
|
71
|
+
|
|
72
|
+
// Read the configured list straight from `chant.config.ts` rather than
|
|
73
|
+
// through `resolveProjectLexicons`: `--on` names a *configured* lexicon, and
|
|
74
|
+
// that helper's fallback is a source scan of the whole project, which is a
|
|
75
|
+
// long wait to be told a name is wrong.
|
|
76
|
+
let configured: string[] = [];
|
|
77
|
+
try {
|
|
78
|
+
configured = (await loadChantConfig(resolve("."))).config.lexicons ?? [];
|
|
79
|
+
} catch {
|
|
80
|
+
// No/unreadable chant.config.ts — the error below says so by listing nothing.
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let plugin = ctx.plugins.find((p) => p.name === on);
|
|
84
|
+
if (!plugin && configured.includes(on)) {
|
|
85
|
+
plugin = (await loadPlugins([on]).catch(() => []))[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!plugin) {
|
|
89
|
+
const known = [...new Set([...ctx.plugins.map((p) => p.name), ...configured])];
|
|
90
|
+
console.error(formatError({
|
|
91
|
+
message: `--on ${on}: "${on}" is not a configured lexicon`,
|
|
92
|
+
hint: known.length > 0
|
|
93
|
+
? `Configured lexicons: ${known.join(", ")}. Omit --on to run on the built-in local runtime.`
|
|
94
|
+
: "chant.config.ts configures no lexicons. Omit --on to run on the built-in local runtime.",
|
|
95
|
+
}));
|
|
96
|
+
return undefined;
|
|
91
97
|
}
|
|
98
|
+
|
|
99
|
+
if (!plugin.opRuntime) {
|
|
100
|
+
console.error(formatError({
|
|
101
|
+
message: `--on ${on}: lexicon "${on}" does not host Op runs`,
|
|
102
|
+
hint: "It declares no opRuntime. Omit --on to run on the built-in local runtime.",
|
|
103
|
+
}));
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return plugin.opRuntime;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** An ISO-8601 instant trimmed to what a table cell has room for. */
|
|
111
|
+
function shortInstant(iso: string | undefined): string {
|
|
112
|
+
return iso ? iso.slice(0, 19).replace("T", " ") : "—";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Print what a runtime reports for a settled or in-flight run. */
|
|
116
|
+
function renderRuntimeStatus(label: string, name: string, runtime: string, status: OpRunStatus): void {
|
|
117
|
+
console.log(formatBold(`${label}: ${name}`));
|
|
118
|
+
console.log(` Runtime : ${runtime}`);
|
|
119
|
+
console.log(` Run ID : ${status.runId}`);
|
|
120
|
+
console.log(` State : ${status.state}`);
|
|
121
|
+
console.log(` Started : ${shortInstant(status.startedAt)}`);
|
|
122
|
+
if (status.endedAt) console.log(` Ended : ${shortInstant(status.endedAt)}`);
|
|
123
|
+
if (status.records) {
|
|
124
|
+
const settled = status.records.filter((r) => r.status !== "skipped").length;
|
|
125
|
+
console.log(` Steps : ${settled}/${status.records.length} settled`);
|
|
126
|
+
}
|
|
127
|
+
if (status.gate) {
|
|
128
|
+
console.log(` Gate : ${status.gate.name} (pending since ${status.gate.since})`);
|
|
129
|
+
}
|
|
130
|
+
if (status.error) console.log(` Error : ${status.error}`);
|
|
92
131
|
}
|
|
93
132
|
|
|
94
133
|
// ── chant run list ──────────────────────────────────���─────────────────────────
|
|
95
134
|
|
|
96
135
|
export async function runOpList(ctx: CommandContext): Promise<number> {
|
|
97
|
-
if (ctx.args.components) return runComponentsList(
|
|
136
|
+
if (ctx.args.components) return runComponentsList();
|
|
137
|
+
return runOpListOnRuntime(ctx);
|
|
138
|
+
}
|
|
98
139
|
|
|
99
|
-
|
|
100
|
-
|
|
140
|
+
/**
|
|
141
|
+
* `chant run list` on the resolved runtime (#2121) — discover every Op, ask
|
|
142
|
+
* the runtime what it knows about each, print one row per Op. The runtime
|
|
143
|
+
* answers for all of them in one call (`list`), so a hosted runtime can do it
|
|
144
|
+
* in one round trip instead of N.
|
|
145
|
+
*
|
|
146
|
+
* An Op the runtime has no run for prints with no state annotation.
|
|
147
|
+
*/
|
|
148
|
+
async function runOpListOnRuntime(ctx: CommandContext): Promise<number> {
|
|
149
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
150
|
+
if (!runtime) return 1;
|
|
101
151
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
152
|
+
const { ops, errors } = await discoverOps();
|
|
153
|
+
for (const err of errors) console.error(formatError({ message: err }));
|
|
105
154
|
|
|
106
155
|
if (ops.size === 0) {
|
|
107
156
|
console.error(formatWarning({ message: "No Op definitions found (*.op.ts)" }));
|
|
108
157
|
return 0;
|
|
109
158
|
}
|
|
110
159
|
|
|
160
|
+
let states: Map<string, OpRunStatus | undefined>;
|
|
161
|
+
try {
|
|
162
|
+
states = await runtime.list([...ops.values()].map((d) => d.config));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
111
168
|
console.log(
|
|
112
|
-
"NAME".padEnd(
|
|
169
|
+
"NAME".padEnd(26) +
|
|
113
170
|
"PHASES".padEnd(8) +
|
|
114
|
-
"TASK-QUEUE".padEnd(20) +
|
|
115
171
|
"DEPENDS".padEnd(20) +
|
|
116
172
|
"OVERVIEW",
|
|
117
173
|
);
|
|
118
174
|
|
|
119
|
-
let runStatus: Map<string, string> | undefined;
|
|
120
|
-
try {
|
|
121
|
-
const projectPath = resolve(".");
|
|
122
|
-
const { config } = await loadChantConfig(projectPath);
|
|
123
|
-
const profile = resolveProfile(config as Record<string, unknown>, ctx.args.profile);
|
|
124
|
-
const { Connection, Client } = await loadTemporalClient();
|
|
125
|
-
const connection = await Connection.connect(connectionOptions(profile));
|
|
126
|
-
const client = new Client({ connection, namespace: profile.namespace });
|
|
127
|
-
runStatus = new Map();
|
|
128
|
-
for (const [name] of ops) {
|
|
129
|
-
try {
|
|
130
|
-
const desc = await client.workflow.getHandle(resolveWorkflowId(name)).describe();
|
|
131
|
-
runStatus.set(name, desc.status.name);
|
|
132
|
-
} catch {
|
|
133
|
-
runStatus.set(name, "—");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
// Temporal not available — degrade gracefully
|
|
138
|
-
}
|
|
139
|
-
|
|
140
175
|
for (const [name, { config }] of ops) {
|
|
141
176
|
const phases = String(config.phases.length);
|
|
142
|
-
const tq = config.taskQueue ?? config.name;
|
|
143
177
|
const deps = config.depends?.join(",") ?? "—";
|
|
144
178
|
const overview = config.overview.length > 36
|
|
145
179
|
? config.overview.slice(0, 33) + "..."
|
|
146
180
|
: config.overview;
|
|
147
|
-
const
|
|
148
|
-
const statusStr = status ? ` [${status}]` : "";
|
|
181
|
+
const state = states.get(name)?.state;
|
|
149
182
|
|
|
150
183
|
console.log(
|
|
151
|
-
(name +
|
|
184
|
+
(name + (state ? ` [${state}]` : "")).padEnd(26) +
|
|
152
185
|
phases.padEnd(8) +
|
|
153
|
-
tq.padEnd(20) +
|
|
154
186
|
deps.padEnd(20) +
|
|
155
187
|
overview,
|
|
156
188
|
);
|
|
@@ -160,89 +192,21 @@ export async function runOpList(ctx: CommandContext): Promise<number> {
|
|
|
160
192
|
}
|
|
161
193
|
|
|
162
194
|
/**
|
|
163
|
-
* `chant run list --components` (#599)
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* point of this subcommand is the run-status annotation, and components have
|
|
168
|
-
* no local notion of a "run" to list either (mirrors the "Temporal-only
|
|
169
|
-
* subcommand guards" test coverage for the Op list/status/log/signal/cancel
|
|
170
|
-
* set).
|
|
171
|
-
*
|
|
172
|
-
* Discovery + column shape reuses `listComponents` (../../components/cli-
|
|
173
|
-
* support.ts), the same helper `chant list --components` already uses, so
|
|
174
|
-
* archetype inference/field projection isn't duplicated here — this function
|
|
175
|
-
* only adds the Temporal status column. Status lookups use
|
|
176
|
-
* `componentWorkflowId`, the id space `runOpSignal`/`runOpCancel` established
|
|
177
|
-
* for components (#589) — kept distinct from `resolveWorkflowId`'s Op id
|
|
178
|
-
* space so an Op and a component can share a name.
|
|
195
|
+
* `chant run list --components` (#599) listed discovered components annotated
|
|
196
|
+
* with each one's durable run status. Discovery alone is what `chant list
|
|
197
|
+
* --components` already prints, so with the status column gone (#2116) this
|
|
198
|
+
* subcommand has nothing of its own left to say.
|
|
179
199
|
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (!result.success) {
|
|
187
|
-
for (const err of result.errors) console.error(formatError({ message: err }));
|
|
188
|
-
return 1;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (result.components.length === 0) {
|
|
192
|
-
console.error(formatWarning({ message: "No component definitions found (*.component.ts)" }));
|
|
193
|
-
return 0;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
console.log(
|
|
197
|
-
"NAME".padEnd(22) +
|
|
198
|
-
"ARCHETYPE".padEnd(18) +
|
|
199
|
-
"PHASES".padEnd(8) +
|
|
200
|
-
"DEPENDS".padEnd(20) +
|
|
201
|
-
"FILE",
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
let runStatus: Map<string, string> | undefined;
|
|
205
|
-
try {
|
|
206
|
-
const { config } = await loadChantConfig(projectPath);
|
|
207
|
-
const profile = resolveProfile(config as Record<string, unknown>, ctx.args.profile);
|
|
208
|
-
const { Connection, Client } = await loadTemporalClient();
|
|
209
|
-
const connection = await Connection.connect(connectionOptions(profile));
|
|
210
|
-
const client = new Client({ connection, namespace: profile.namespace });
|
|
211
|
-
runStatus = new Map();
|
|
212
|
-
for (const { name } of result.components) {
|
|
213
|
-
try {
|
|
214
|
-
const desc = await client.workflow.getHandle(componentWorkflowId(name)).describe();
|
|
215
|
-
runStatus.set(name, desc.status.name);
|
|
216
|
-
} catch {
|
|
217
|
-
runStatus.set(name, "—");
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
} catch {
|
|
221
|
-
// Temporal not available — degrade gracefully
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
for (const c of result.components) {
|
|
225
|
-
const phases = String(c.phases.length);
|
|
226
|
-
const deps = c.dependsOn.join(",") || "—";
|
|
227
|
-
const status = runStatus?.get(c.name);
|
|
228
|
-
const statusStr = status ? ` [${status}]` : "";
|
|
229
|
-
|
|
230
|
-
console.log(
|
|
231
|
-
(c.name + statusStr).padEnd(22) +
|
|
232
|
-
c.archetype.padEnd(18) +
|
|
233
|
-
phases.padEnd(8) +
|
|
234
|
-
deps.padEnd(20) +
|
|
235
|
-
c.filePath,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return 0;
|
|
200
|
+
function runComponentsList(): Promise<number> {
|
|
201
|
+
return Promise.resolve(refuseDurableComponentSubcommand(
|
|
202
|
+
"chant run list --components",
|
|
203
|
+
"Run `chant list --components` to see discovered components.",
|
|
204
|
+
));
|
|
240
205
|
}
|
|
241
206
|
|
|
242
207
|
// ── chant run status <name> ───────────────────────────────────────────────────
|
|
243
208
|
|
|
244
209
|
export async function runOpStatus(ctx: CommandContext): Promise<number> {
|
|
245
|
-
if (!requireTemporalMode(ctx, "chant run status")) return 1;
|
|
246
210
|
const name = ctx.args.extraPositional;
|
|
247
211
|
if (!name) {
|
|
248
212
|
const label = ctx.args.components ? "Component" : "Op";
|
|
@@ -250,84 +214,44 @@ export async function runOpStatus(ctx: CommandContext): Promise<number> {
|
|
|
250
214
|
return 1;
|
|
251
215
|
}
|
|
252
216
|
|
|
253
|
-
if (ctx.args.components)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
({ client } = await makeTemporalClient(ctx.args.profile, projectPath));
|
|
259
|
-
handle = client.workflow.getHandle(resolveWorkflowId(name));
|
|
260
|
-
desc = await handle.describe();
|
|
261
|
-
history = await fetchNormalizedHistory(handle);
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
264
|
-
return 1;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
console.log(formatBold(`Op: ${name}`));
|
|
268
|
-
console.log(` Workflow ID : ${desc.workflowId}`);
|
|
269
|
-
console.log(` Run ID : ${desc.runId}`);
|
|
270
|
-
console.log(` Status : ${desc.status.name}`);
|
|
271
|
-
console.log(` Task Queue : ${desc.taskQueue}`);
|
|
272
|
-
console.log(` Started : ${desc.startTime.toISOString()}`);
|
|
273
|
-
if (desc.closeTime) console.log(` Closed : ${desc.closeTime.toISOString()}`);
|
|
274
|
-
|
|
275
|
-
const { completed, scheduled } = countActivities(history);
|
|
276
|
-
if (scheduled > 0) {
|
|
277
|
-
console.log(` Activities : ${completed}/${scheduled} completed`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Queryable gate state (#1676) — undefined means the query itself failed
|
|
281
|
-
// (an ungated Op never registers a "gateState" handler), so print nothing;
|
|
282
|
-
// null means the query answered "no gate pending" — also nothing to print.
|
|
283
|
-
const gate = await queryGateState(handle);
|
|
284
|
-
if (gate) {
|
|
285
|
-
console.log(` Gate : ${gate.signalName}${gate.description ? ` — ${gate.description}` : ""} (waiting since ${gate.since})`);
|
|
217
|
+
if (ctx.args.components) {
|
|
218
|
+
return refuseDurableComponentSubcommand(
|
|
219
|
+
"chant run status --components",
|
|
220
|
+
"Run `chant components status` for a component's release and build state.",
|
|
221
|
+
);
|
|
286
222
|
}
|
|
287
|
-
|
|
288
|
-
return 0;
|
|
223
|
+
return runOpStatusOnRuntime(ctx, name);
|
|
289
224
|
}
|
|
290
225
|
|
|
291
226
|
/**
|
|
292
|
-
* `chant run status <name
|
|
293
|
-
* of
|
|
294
|
-
*
|
|
295
|
-
* `resolveWorkflowId`) and the "Component:" label, mirroring how
|
|
296
|
-
* `runOpSignal`/`runOpCancel` already distinguish the two id spaces (#589).
|
|
227
|
+
* `chant run status <name>` on the resolved runtime (#2121). A runtime with
|
|
228
|
+
* no record of the Op is not an error: it prints one line saying so and exits
|
|
229
|
+
* 0, because "this runtime has never run it" is a true answer to the question.
|
|
297
230
|
*/
|
|
298
|
-
async function
|
|
299
|
-
const
|
|
300
|
-
|
|
231
|
+
async function runOpStatusOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
232
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
233
|
+
if (!runtime) return 1;
|
|
234
|
+
|
|
235
|
+
let status: OpRunStatus | undefined;
|
|
301
236
|
try {
|
|
302
|
-
|
|
303
|
-
const handle = client.workflow.getHandle(componentWorkflowId(name));
|
|
304
|
-
desc = await handle.describe();
|
|
305
|
-
history = await fetchNormalizedHistory(handle);
|
|
237
|
+
status = await runtime.status(name);
|
|
306
238
|
} catch (err) {
|
|
307
239
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
308
240
|
return 1;
|
|
309
241
|
}
|
|
310
242
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
console.log(` Status : ${desc.status.name}`);
|
|
315
|
-
console.log(` Task Queue : ${desc.taskQueue}`);
|
|
316
|
-
console.log(` Started : ${desc.startTime.toISOString()}`);
|
|
317
|
-
if (desc.closeTime) console.log(` Closed : ${desc.closeTime.toISOString()}`);
|
|
318
|
-
|
|
319
|
-
const { completed, scheduled } = countActivities(history);
|
|
320
|
-
if (scheduled > 0) {
|
|
321
|
-
console.log(` Activities : ${completed}/${scheduled} completed`);
|
|
243
|
+
if (!status) {
|
|
244
|
+
console.error(formatInfo(`No run of Op "${name}" is recorded on the "${runtime.name}" runtime.`));
|
|
245
|
+
return 0;
|
|
322
246
|
}
|
|
323
247
|
|
|
248
|
+
renderRuntimeStatus("Op", name, runtime.name, status);
|
|
324
249
|
return 0;
|
|
325
250
|
}
|
|
326
251
|
|
|
327
252
|
// ── chant run log <name> ──────────────────────────────────────────────────────
|
|
328
253
|
|
|
329
254
|
export async function runOpLog(ctx: CommandContext): Promise<number> {
|
|
330
|
-
if (!requireTemporalMode(ctx, "chant run log")) return 1;
|
|
331
255
|
const name = ctx.args.extraPositional;
|
|
332
256
|
if (!name) {
|
|
333
257
|
const label = ctx.args.components ? "Component" : "Op";
|
|
@@ -335,151 +259,128 @@ export async function runOpLog(ctx: CommandContext): Promise<number> {
|
|
|
335
259
|
return 1;
|
|
336
260
|
}
|
|
337
261
|
|
|
338
|
-
if (ctx.args.components)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
({ client } = await makeTemporalClient(ctx.args.profile, projectPath));
|
|
344
|
-
} catch (err) {
|
|
345
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
346
|
-
return 1;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
console.log(
|
|
350
|
-
"RUN-ID".padEnd(36) +
|
|
351
|
-
"STATUS".padEnd(16) +
|
|
352
|
-
"STARTED".padEnd(26) +
|
|
353
|
-
"CLOSED",
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
const fnName = workflowFnName(name);
|
|
358
|
-
for await (const run of client.workflow.list({ query: `WorkflowType = "${fnName}"` })) {
|
|
359
|
-
const start = run.startTime.toISOString().slice(0, 19).replace("T", " ");
|
|
360
|
-
const close = run.closeTime ? run.closeTime.toISOString().slice(0, 19).replace("T", " ") : "—";
|
|
361
|
-
console.log(
|
|
362
|
-
run.runId.padEnd(36) +
|
|
363
|
-
run.status.name.padEnd(16) +
|
|
364
|
-
start.padEnd(26) +
|
|
365
|
-
close,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
} catch (err) {
|
|
369
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
370
|
-
return 1;
|
|
262
|
+
if (ctx.args.components) {
|
|
263
|
+
return refuseDurableComponentSubcommand(
|
|
264
|
+
"chant run log --components",
|
|
265
|
+
"Run `chant components status` for a component's recorded releases.",
|
|
266
|
+
);
|
|
371
267
|
}
|
|
372
|
-
|
|
373
|
-
return 0;
|
|
268
|
+
return runOpLogOnRuntime(ctx, name);
|
|
374
269
|
}
|
|
375
270
|
|
|
376
271
|
/**
|
|
377
|
-
* `chant run log <name
|
|
378
|
-
*
|
|
379
|
-
* component's generated workflow function name rather than
|
|
380
|
-
* `workflowFnName`'s Op convention, so this loads the Temporal component
|
|
381
|
-
* codegen module (`../../components/temporal-codegen-loader.ts`, the same
|
|
382
|
-
* loader `runComponentTemporal` already uses) purely for its
|
|
383
|
-
* `componentWorkflowFnName` helper — no compilation happens here, `log` only
|
|
384
|
-
* reads past run history.
|
|
272
|
+
* `chant run log <name>` on the resolved runtime (#2121) — the runtime's own
|
|
273
|
+
* run history, newest first, `--limit <n>` rows at most.
|
|
385
274
|
*/
|
|
386
|
-
async function
|
|
387
|
-
const
|
|
388
|
-
|
|
275
|
+
async function runOpLogOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
276
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
277
|
+
if (!runtime) return 1;
|
|
278
|
+
|
|
279
|
+
let records;
|
|
389
280
|
try {
|
|
390
|
-
|
|
281
|
+
records = await runtime.log(name, ctx.args.limit === undefined ? undefined : { limit: ctx.args.limit });
|
|
391
282
|
} catch (err) {
|
|
392
283
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
393
284
|
return 1;
|
|
394
285
|
}
|
|
395
286
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
fnName = codegen.componentWorkflowFnName(name);
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
402
|
-
return 1;
|
|
287
|
+
if (records.length === 0) {
|
|
288
|
+
console.error(formatInfo(`No run of Op "${name}" is recorded on the "${runtime.name}" runtime.`));
|
|
289
|
+
return 0;
|
|
403
290
|
}
|
|
404
291
|
|
|
405
292
|
console.log(
|
|
406
|
-
"RUN-ID".padEnd(
|
|
407
|
-
"STATUS".padEnd(
|
|
293
|
+
"RUN-ID".padEnd(38) +
|
|
294
|
+
"STATUS".padEnd(10) +
|
|
408
295
|
"STARTED".padEnd(26) +
|
|
409
|
-
"
|
|
296
|
+
"ENDED",
|
|
410
297
|
);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
run.status.name.padEnd(16) +
|
|
419
|
-
start.padEnd(26) +
|
|
420
|
-
close,
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
} catch (err) {
|
|
424
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
425
|
-
return 1;
|
|
298
|
+
for (const record of records) {
|
|
299
|
+
console.log(
|
|
300
|
+
record.id.padEnd(38) +
|
|
301
|
+
record.status.padEnd(10) +
|
|
302
|
+
shortInstant(record.started).padEnd(26) +
|
|
303
|
+
shortInstant(record.ended),
|
|
304
|
+
);
|
|
426
305
|
}
|
|
427
306
|
|
|
428
307
|
return 0;
|
|
429
308
|
}
|
|
430
309
|
|
|
431
|
-
// ── chant run
|
|
310
|
+
// ── chant run approve <op> <gate> ────────────────────────────────────────────
|
|
432
311
|
|
|
433
312
|
/**
|
|
434
|
-
* `chant run
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
313
|
+
* `chant run approve <op> <gate> [--approver] [--url] [--note]` (#2121) — the
|
|
314
|
+
* rename of `chant run signal`, and a different act.
|
|
315
|
+
*
|
|
316
|
+
* A gate resolution is a fact, not a message: the ledger write is the whole
|
|
317
|
+
* of it, and it is the same write `chant approve` performs
|
|
318
|
+
* (`recordGateApproval`, ./operator.ts), so a resolution recorded here reads
|
|
319
|
+
* back identically. The runtime is told afterwards, through its optional
|
|
320
|
+
* `resolveGate`, purely so a runtime that parks a run mid-flight can wake it
|
|
321
|
+
* now rather than on its next tick. A runtime without that method is not a
|
|
322
|
+
* failure — the fact is recorded either way, and the next run re-reads it.
|
|
441
323
|
*/
|
|
442
|
-
export async function
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (!name || !signalName) {
|
|
448
|
-
console.error(formatError({ message: "Usage: chant run signal <name> <signal-name>" }));
|
|
324
|
+
export async function runOpApprove(ctx: CommandContext): Promise<number> {
|
|
325
|
+
const opName = ctx.args.extraPositional;
|
|
326
|
+
const gate = ctx.args.extraPositional2;
|
|
327
|
+
if (!opName || !gate) {
|
|
328
|
+
console.error(formatError({ message: "Usage: chant run approve <op> <gate>" }));
|
|
449
329
|
return 1;
|
|
450
330
|
}
|
|
451
331
|
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
332
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
333
|
+
if (!runtime) return 1;
|
|
334
|
+
|
|
335
|
+
const outcome = await recordGateApproval(opName, gate, {
|
|
336
|
+
actor: ctx.args.approver ?? ctx.args.actor,
|
|
337
|
+
note: ctx.args.note,
|
|
338
|
+
url: ctx.args.url,
|
|
339
|
+
});
|
|
340
|
+
if (!outcome.ok) return 1;
|
|
341
|
+
|
|
342
|
+
if (!runtime.resolveGate) {
|
|
343
|
+
console.error(formatInfo(
|
|
344
|
+
`The "${runtime.name}" runtime reads the resolution when the op next runs — re-run \`chant run ${opName}\`.`,
|
|
345
|
+
));
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
462
349
|
try {
|
|
463
|
-
|
|
464
|
-
handle = client.workflow.getHandle(workflowId);
|
|
465
|
-
await handle.signal(signalName, ...(approver ? [{ approver }] : []));
|
|
350
|
+
await runtime.resolveGate(opName, gate, outcome.record);
|
|
466
351
|
} catch (err) {
|
|
467
|
-
console.error(
|
|
352
|
+
console.error(formatWarning({
|
|
353
|
+
message:
|
|
354
|
+
`The resolution is recorded, but the "${runtime.name}" runtime could not be woken: ` +
|
|
355
|
+
(err instanceof Error ? err.message : String(err)),
|
|
356
|
+
hint: `Re-run \`chant run ${opName} --on ${runtime.name}\` once the runtime is reachable.`,
|
|
357
|
+
}));
|
|
468
358
|
return 1;
|
|
469
359
|
}
|
|
470
360
|
|
|
471
|
-
console.error(formatSuccess(
|
|
472
|
-
`Signal "${signalName}" sent to ${ctx.args.components ? "component" : "Op"} "${name}"` +
|
|
473
|
-
(approver ? ` (approver: ${approver})` : ""),
|
|
474
|
-
));
|
|
361
|
+
console.error(formatSuccess(`Runtime "${runtime.name}" was notified of the resolution.`));
|
|
475
362
|
return 0;
|
|
476
363
|
}
|
|
477
364
|
|
|
365
|
+
/**
|
|
366
|
+
* `chant run signal` was renamed `chant run approve` (#2121). Registered so
|
|
367
|
+
* the old spelling says where the verb went instead of being read as an Op
|
|
368
|
+
* named "signal".
|
|
369
|
+
*/
|
|
370
|
+
export function runOpSignalRenamed(ctx: CommandContext): Promise<number> {
|
|
371
|
+
const opName = ctx.args.extraPositional ?? "<op>";
|
|
372
|
+
const gate = ctx.args.extraPositional2 ?? "<gate>";
|
|
373
|
+
console.error(formatError({
|
|
374
|
+
message: "`chant run signal` is now `chant run approve`",
|
|
375
|
+
hint: `A gate is resolved by recording the fact, not by sending a message: chant run approve ${opName} ${gate}`,
|
|
376
|
+
}));
|
|
377
|
+
return Promise.resolve(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
478
380
|
// ── chant run cancel <name> ───────────────────────────────────────────────────
|
|
479
381
|
|
|
480
|
-
/** `chant run cancel <name
|
|
382
|
+
/** `chant run cancel <name>` — asks the resolved runtime (#2121) to stop the Op's active run. */
|
|
481
383
|
export async function runOpCancel(ctx: CommandContext): Promise<number> {
|
|
482
|
-
if (!requireTemporalMode(ctx, "chant run cancel")) return 1;
|
|
483
384
|
const name = ctx.args.extraPositional;
|
|
484
385
|
if (!name) {
|
|
485
386
|
console.error(formatError({ message: "Op name is required: chant run cancel <name>" }));
|
|
@@ -488,65 +389,52 @@ export async function runOpCancel(ctx: CommandContext): Promise<number> {
|
|
|
488
389
|
|
|
489
390
|
if (!ctx.args.force) {
|
|
490
391
|
console.error(formatWarning({
|
|
491
|
-
message: `Cancelling "${name}" will stop the active
|
|
392
|
+
message: `Cancelling "${name}" will stop the active run`,
|
|
492
393
|
hint: "Use --force to confirm cancellation",
|
|
493
394
|
}));
|
|
494
395
|
return 1;
|
|
495
396
|
}
|
|
496
397
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
398
|
+
if (ctx.args.components) {
|
|
399
|
+
return refuseDurableComponentSubcommand(
|
|
400
|
+
"chant run cancel --components",
|
|
401
|
+
"A component run lives and dies with the `chant run --components` process — interrupt that instead.",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return runOpCancelOnRuntime(ctx, name);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* `chant run cancel <name>` on the resolved runtime (#2121). `--force` is
|
|
410
|
+
* already checked by the caller, so a runtime only ever sees a confirmed
|
|
411
|
+
* request; a runtime with nothing to cancel says so in its own words and the
|
|
412
|
+
* command exits non-zero.
|
|
413
|
+
*/
|
|
414
|
+
async function runOpCancelOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
415
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
416
|
+
if (!runtime) return 1;
|
|
417
|
+
|
|
500
418
|
try {
|
|
501
|
-
|
|
502
|
-
handle = client.workflow.getHandle(workflowId);
|
|
503
|
-
await handle.cancel();
|
|
419
|
+
await runtime.cancel(name, { force: true });
|
|
504
420
|
} catch (err) {
|
|
505
421
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
506
422
|
return 1;
|
|
507
423
|
}
|
|
508
424
|
|
|
509
|
-
console.error(formatSuccess(`Cancellation requested for ${
|
|
425
|
+
console.error(formatSuccess(`Cancellation requested for Op "${name}" on the "${runtime.name}" runtime`));
|
|
510
426
|
return 0;
|
|
511
427
|
}
|
|
512
428
|
|
|
513
429
|
// ── chant run <name> — main command ───────────────────────────────────────────
|
|
514
430
|
|
|
515
|
-
const TERMINAL_STATUSES = new Set(["COMPLETED", "FAILED", "CANCELLED", "TERMINATED", "TIMED_OUT"]);
|
|
516
|
-
const POLL_INTERVAL_MS = 3000;
|
|
517
|
-
|
|
518
|
-
async function waitForTemporalServer(address: string, maxWaitMs = 30_000): Promise<void> {
|
|
519
|
-
const [host, portStr] = address.split(":");
|
|
520
|
-
const port = parseInt(portStr ?? "7233", 10);
|
|
521
|
-
const deadline = Date.now() + maxWaitMs;
|
|
522
|
-
while (Date.now() < deadline) {
|
|
523
|
-
try {
|
|
524
|
-
await new Promise<void>((res, rej) => {
|
|
525
|
-
const socket = createConnection({ host, port }, () => { socket.destroy(); res(); });
|
|
526
|
-
socket.on("error", rej);
|
|
527
|
-
socket.setTimeout(1000, () => { socket.destroy(); rej(new Error("timeout")); });
|
|
528
|
-
});
|
|
529
|
-
return;
|
|
530
|
-
} catch {
|
|
531
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
throw new Error(`Temporal server at ${address} did not become ready within ${maxWaitMs / 1000}s`);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function renderProgress(opName: string, history: WorkflowHistoryRaw): void {
|
|
538
|
-
const { completed, scheduled } = countActivities(history);
|
|
539
|
-
process.stderr.write(
|
|
540
|
-
`\r${formatInfo(`[${opName}]`)} ${completed}/${scheduled} activities completed`,
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
431
|
/**
|
|
545
432
|
* `chant run <name>` dispatcher.
|
|
546
433
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
434
|
+
* Every run goes through the runtime resolved by `--on` (#2121); without it,
|
|
435
|
+
* core's built-in local provider runs the Op in this process, where a `gate`
|
|
436
|
+
* is decided against the gate ledger and ends the run pending approval
|
|
437
|
+
* (#2119), exit code 3.
|
|
550
438
|
*
|
|
551
439
|
* `chant run --components <name|all>` (#585) is a separate target: discovered
|
|
552
440
|
* `Component` declarations dispatched through the interpret driver
|
|
@@ -555,22 +443,19 @@ function renderProgress(opName: string, history: WorkflowHistoryRaw): void {
|
|
|
555
443
|
* runComponentGraph(ctx)` branch (../handlers/graph.ts).
|
|
556
444
|
*
|
|
557
445
|
* chant #1116 — `--report` with `--components` is checked and hard-errored
|
|
558
|
-
* before that dispatch
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* live reaching an actual cloud shell-out. Erroring here is the safe minimum
|
|
563
|
-
* called out on the issue; a real preview is future work.
|
|
446
|
+
* before that dispatch, and #2116 retired `--report` on the Op path too: it
|
|
447
|
+
* rendered a past durable run's workflow history, which no runtime keeps any
|
|
448
|
+
* more. Both refuse rather than falling through to a real dispatch — the
|
|
449
|
+
* component case was observed live reaching an actual cloud shell-out.
|
|
564
450
|
*/
|
|
565
451
|
/**
|
|
566
452
|
* Pre-flight for `chant run <op> --sandbox` on an Op containing a `policyGate`
|
|
567
453
|
* step (chant #2003). `--sandbox` is a global flag, and `../main.ts` arms the
|
|
568
454
|
* process-wide policy latch off it for every command — so the gate's
|
|
569
455
|
* `loadPolicyChecks` refuses mid-run with a message addressed to a chant
|
|
570
|
-
* maintainer ("This is a chant bug"),
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
* combination and what to do about it.
|
|
456
|
+
* maintainer ("This is a chant bug"), which is not an answer a user can act
|
|
457
|
+
* on. Refuse here instead, before anything runs, naming the combination and
|
|
458
|
+
* what to do about it.
|
|
574
459
|
*
|
|
575
460
|
* Keyed on the flag rather than on whether the project declares
|
|
576
461
|
* `lint.policies`: the flag is what arms the latch, and a gate that builds the
|
|
@@ -597,18 +482,14 @@ export async function runOp(ctx: CommandContext): Promise<number> {
|
|
|
597
482
|
return 1;
|
|
598
483
|
}
|
|
599
484
|
if (ctx.args.components) return runOpComponents(ctx);
|
|
600
|
-
if (ctx.args.
|
|
485
|
+
if (ctx.args.report) {
|
|
601
486
|
console.error(formatError({
|
|
602
|
-
message: "
|
|
603
|
-
hint: "
|
|
487
|
+
message: "`chant run --report` rendered a past durable run, which #2116 removed",
|
|
488
|
+
hint: "Run `chant run log <op>` for the runtime's own run history.",
|
|
604
489
|
}));
|
|
605
490
|
return 1;
|
|
606
491
|
}
|
|
607
|
-
|
|
608
|
-
if (!requireTemporalMode(ctx, "chant run --report")) return 1;
|
|
609
|
-
return runOpTemporal(ctx);
|
|
610
|
-
}
|
|
611
|
-
return ctx.args.temporal ? runOpTemporal(ctx) : runOpLocal(ctx);
|
|
492
|
+
return runOpOnRuntime(ctx);
|
|
612
493
|
}
|
|
613
494
|
|
|
614
495
|
// ── Auto-release recording post-run (#597) ──────────────────────────────────
|
|
@@ -689,28 +570,18 @@ async function recordAutoReleasesForRun(
|
|
|
689
570
|
// ── chant run --components <name|all> ────────────────────────────────────────
|
|
690
571
|
|
|
691
572
|
/**
|
|
692
|
-
* `chant run --components <name|all> [--env <env>]
|
|
693
|
-
*
|
|
694
|
-
* is the component name (or `all`), matching `chant run
|
|
695
|
-
* convention exactly (`args.path` is the Op name there
|
|
696
|
-
* project directory — components are always discovered from
|
|
697
|
-
* working directory, the same way Op discovery (`discoverOps()`)
|
|
698
|
-
* a project-path argument either.
|
|
699
|
-
*
|
|
700
|
-
* Local mode (the default) resolves the selector through `runComponents`
|
|
701
|
-
* (`../../components/cli-support.ts`) and runs it on the local in-process
|
|
702
|
-
* executor; gated components are rejected before any step runs (matching
|
|
703
|
-
* `runOpLocal`'s pre-flight `findGate` guard) with an actionable message
|
|
704
|
-
* pointing at `--temporal`.
|
|
573
|
+
* `chant run --components <name|all> [--env <env>]` (#585) — the interpret
|
|
574
|
+
* driver's CLI entrypoint, and since #2116 the only component run path there
|
|
575
|
+
* is. `args.path` is the component name (or `all`), matching `chant run
|
|
576
|
+
* <name>`'s Op-dispatch convention exactly (`args.path` is the Op name there
|
|
577
|
+
* too) rather than a project directory — components are always discovered from
|
|
578
|
+
* the current working directory, the same way Op discovery (`discoverOps()`)
|
|
579
|
+
* never takes a project-path argument either.
|
|
705
580
|
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
* in-process block. Scoped to a single named component (`all --temporal` is
|
|
711
|
-
* out of scope for #589: coordinating N durable workflows' cross-component
|
|
712
|
-
* `dependsOn` durably is a follow-up, not part of "compile a component's
|
|
713
|
-
* `deploy` composition into a durable orchestrator").
|
|
581
|
+
* The selector resolves through the runtime's `runComponents` and runs on the
|
|
582
|
+
* local in-process driver; a `gate` is decided against the gate ledger when
|
|
583
|
+
* the driver reaches it (#2119), and one nobody has approved ends the run with
|
|
584
|
+
* exit code 3 and the `chant approve` line, exactly as `chant run <op>` does.
|
|
714
585
|
*
|
|
715
586
|
* On a successful run, auto-emits one release-ledger record per component
|
|
716
587
|
* that published a digest (#597, `recordAutoReleasesForRun` above) — a
|
|
@@ -723,8 +594,8 @@ async function recordAutoReleasesForRun(
|
|
|
723
594
|
* chant #1108 — resolves this invocation's declared build-time parameters
|
|
724
595
|
* (`chant.config.ts`'s `buildParams`, against `--param`/`--params-file`/a
|
|
725
596
|
* declared `env` mapping) the exact same way `chant build` does
|
|
726
|
-
* (`resolveCliBuildParams`, shared with `buildCommand`), BEFORE
|
|
727
|
-
*
|
|
597
|
+
* (`resolveCliBuildParams`, shared with `buildCommand`), BEFORE anything
|
|
598
|
+
* discovers/imports a `*.component.ts` file.
|
|
728
599
|
* Before this, `params.*` (`@intentius/chant/params`) was always `{}` under
|
|
729
600
|
* this command, no matter what a component's `chant.config.ts` declared or a
|
|
730
601
|
* CI job's environment supplied — see chant #1108. `chant.config.ts` is
|
|
@@ -754,7 +625,15 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
754
625
|
return 1;
|
|
755
626
|
}
|
|
756
627
|
|
|
757
|
-
|
|
628
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
629
|
+
if (!runtime) return 1;
|
|
630
|
+
if (!runtime.runComponents) {
|
|
631
|
+
console.error(formatError({
|
|
632
|
+
message: `--components is not supported on the "${runtime.name}" runtime`,
|
|
633
|
+
hint: "Omit --on to run components on the built-in local runtime.",
|
|
634
|
+
}));
|
|
635
|
+
return 1;
|
|
636
|
+
}
|
|
758
637
|
|
|
759
638
|
const env = ctx.args.env ?? "local";
|
|
760
639
|
// Seed cross-component/cross-stack outputs from upstream jobs' dumped files
|
|
@@ -778,7 +657,7 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
778
657
|
// `undefined` and every `onProgress?.(...)` call in the driver is a no-op —
|
|
779
658
|
// behavior is byte-for-byte unchanged from before this flag existed.
|
|
780
659
|
const onProgress = ctx.args.progressJson ? ndjsonProgressSink() : undefined;
|
|
781
|
-
const result = await runComponents(projectPath, selector, {
|
|
660
|
+
const result = await runtime.runComponents(projectPath, selector, {
|
|
782
661
|
env: ctx.args.env,
|
|
783
662
|
componentOutputs: seededOutputs,
|
|
784
663
|
onProgress,
|
|
@@ -794,17 +673,7 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
794
673
|
writeFileSync(dumpPath, JSON.stringify(result.run.componentOutputs, null, 2));
|
|
795
674
|
}
|
|
796
675
|
|
|
797
|
-
if (result.
|
|
798
|
-
console.error(formatError({
|
|
799
|
-
message:
|
|
800
|
-
`component "${result.gateUnsupported.component}": gate "${result.gateUnsupported.signalName}" is not ` +
|
|
801
|
-
`supported on the local executor — gates need a durable runtime.`,
|
|
802
|
-
hint: "Re-run with `chant run --components " + result.gateUnsupported.component + " --temporal`.",
|
|
803
|
-
}));
|
|
804
|
-
return 1;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (!result.success && !result.run) {
|
|
676
|
+
if (!result.success && !result.run && !result.gated) {
|
|
808
677
|
console.error(formatError({ message: result.error ?? "Failed to run component(s)" }));
|
|
809
678
|
return 1;
|
|
810
679
|
}
|
|
@@ -813,6 +682,19 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
813
682
|
if (ctx.args.json) renderDriverJson(result.run); else renderDriverHuman(result.run);
|
|
814
683
|
}
|
|
815
684
|
|
|
685
|
+
// Gated (#2119): the same fact-and-stop the Op path takes, and the same
|
|
686
|
+
// exit code. Nothing to release — the component stopped short of finishing.
|
|
687
|
+
if (result.gated) {
|
|
688
|
+
const { gate } = result.gated;
|
|
689
|
+
console.error(formatWarning({
|
|
690
|
+
message: `component "${result.gated.component}" is gated on "${gate.gate}" — pending approval`,
|
|
691
|
+
}));
|
|
692
|
+
console.error(formatInfo(`approve : ${approveCommand(gate.op, gate.gate)}`));
|
|
693
|
+
if (gate.url) console.error(formatInfo(`approve at: ${gate.url}`));
|
|
694
|
+
console.error(formatInfo(`expires : ${gate.expiresAt}`));
|
|
695
|
+
return GATED_EXIT_CODE;
|
|
696
|
+
}
|
|
697
|
+
|
|
816
698
|
if (result.success && result.run) {
|
|
817
699
|
const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord);
|
|
818
700
|
await recordAutoReleasesForRun(result.run.results, env, `local-${Date.now()}`, disabled);
|
|
@@ -821,255 +703,24 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
821
703
|
return result.success ? 0 : 1;
|
|
822
704
|
}
|
|
823
705
|
|
|
824
|
-
// ── chant run --components <name> --temporal (#589) ─────────────────────────
|
|
825
|
-
|
|
826
|
-
function componentWorkflowId(componentName: string): string {
|
|
827
|
-
return `chant-component-${componentName}`;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
706
|
/**
|
|
831
|
-
* `chant run
|
|
832
|
-
*
|
|
833
|
-
* `runOpTemporal`'s shape exactly (build+spawn worker, submit workflow, poll,
|
|
834
|
-
* report) but "build" here means compiling the component's composition
|
|
835
|
-
* on-the-fly via `loadComponentTemporalCodegen` (the Temporal lexicon's
|
|
836
|
-
* `serializeComponent`, mirroring `serializeOps`) rather than reading a
|
|
837
|
-
* pre-existing `dist/ops/<name>/worker.ts` — components have no separate
|
|
838
|
-
* `chant build` step that already produced generated files the way Ops do, so
|
|
839
|
-
* this generates `dist/components/<name>/{workflow,worker,activities}.ts`
|
|
840
|
-
* itself before spawning.
|
|
841
|
-
*
|
|
842
|
-
* Unlike the local executor, a `gate` anywhere in the component's
|
|
843
|
-
* composition is fully supported: the generated workflow durably waits for
|
|
844
|
-
* the signal (see `serializeComponent`'s codegen), survivable across a worker
|
|
845
|
-
* crash and clearable via `chant run signal <name> <signal> --components
|
|
846
|
-
* --temporal` (`runOpSignal` above, extended for components alongside this
|
|
847
|
-
* issue).
|
|
707
|
+
* `chant run <name>` on the resolved runtime (#2121) — the built-in `local`
|
|
708
|
+
* provider by default, a lexicon's `opRuntime` under `--on <name>`.
|
|
848
709
|
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
ctx: CommandContext,
|
|
857
|
-
selector: string,
|
|
858
|
-
config: ChantConfig,
|
|
859
|
-
buildParams: BuildParamProvenance[],
|
|
860
|
-
): Promise<number> {
|
|
861
|
-
if (selector === "all") {
|
|
862
|
-
console.error(formatError({
|
|
863
|
-
message: "`chant run --components all --temporal` is not supported",
|
|
864
|
-
hint: "Pass a single component name — durable execution is compiled and run per component (see epic #551 #589).",
|
|
865
|
-
}));
|
|
866
|
-
return 1;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const projectPath = resolve(".");
|
|
870
|
-
const resolved = await resolveComponentTargets(projectPath, selector, ctx.args.sandbox, buildParams);
|
|
871
|
-
if (!resolved.success || resolved.targets.length === 0) {
|
|
872
|
-
console.error(formatError({ message: resolved.error ?? `Component "${selector}" not found` }));
|
|
873
|
-
return 1;
|
|
874
|
-
}
|
|
875
|
-
const component = resolved.targets[0];
|
|
876
|
-
|
|
877
|
-
// Config + profile (mirrors runOpTemporal). `config` was already loaded by
|
|
878
|
-
// the caller to resolve build-time parameters (chant #1108); reused here
|
|
879
|
-
// rather than loading chant.config.ts a second time.
|
|
880
|
-
const chantConfig = config;
|
|
881
|
-
let profile;
|
|
882
|
-
try {
|
|
883
|
-
profile = resolveProfile(chantConfig as Record<string, unknown>, ctx.args.profile);
|
|
884
|
-
} catch (err) {
|
|
885
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
886
|
-
return 1;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Compile the component to workflow/worker/activities under dist/components/<name>/.
|
|
890
|
-
let codegen;
|
|
891
|
-
try {
|
|
892
|
-
codegen = await loadComponentTemporalCodegen();
|
|
893
|
-
} catch (err) {
|
|
894
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
895
|
-
return 1;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// (#629) Fill `chant.config.ts`'s `sbom`/`signing`/`vulnPolicy` defaults into
|
|
899
|
-
// every recognized step BEFORE codegen — same pass the interpret/local path
|
|
900
|
-
// runs in `runComponents` (../../components/cli-support.ts). The Temporal path
|
|
901
|
-
// *inlines* the resolved composition into the generated workflow/activities,
|
|
902
|
-
// so if we serialized the raw component the durable path would silently drop
|
|
903
|
-
// project-level supply-chain defaults the local path honors. (The GitLab
|
|
904
|
-
// generate path needs no equivalent: it emits thin `chant run --components`
|
|
905
|
-
// trigger jobs, so defaults are applied at run time inside the triggered job.)
|
|
906
|
-
const resolvedComponent = applyConfigDefaults(component, chantConfig);
|
|
907
|
-
const files = codegen.serializeComponent(resolvedComponent, { env: ctx.args.env });
|
|
908
|
-
for (const [relPath, content] of Object.entries(files)) {
|
|
909
|
-
const outPath = join(projectPath, "dist", relPath);
|
|
910
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
911
|
-
writeFileSync(outPath, content);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const workerPath = join(projectPath, "dist", "components", component.name, "worker.ts");
|
|
915
|
-
|
|
916
|
-
// autoStart: spin up temporal server if needed (mirrors runOpTemporal).
|
|
917
|
-
if (profile.autoStart) {
|
|
918
|
-
console.error(formatInfo("autoStart: checking Temporal server..."));
|
|
919
|
-
try {
|
|
920
|
-
await waitForTemporalServer(profile.address, 2000);
|
|
921
|
-
console.error(formatInfo("Temporal server already running."));
|
|
922
|
-
} catch {
|
|
923
|
-
console.error(formatInfo("Starting temporal server start-dev..."));
|
|
924
|
-
spawnChild("temporal", ["server", "start-dev"], {
|
|
925
|
-
cwd: projectPath,
|
|
926
|
-
stdio: "ignore",
|
|
927
|
-
detached: true,
|
|
928
|
-
}).unref();
|
|
929
|
-
await waitForTemporalServer(profile.address, 30_000);
|
|
930
|
-
console.error(formatSuccess("Temporal server ready."));
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
let client;
|
|
935
|
-
try {
|
|
936
|
-
({ client } = await makeTemporalClient(ctx.args.profile, projectPath));
|
|
937
|
-
} catch (err) {
|
|
938
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
939
|
-
return 1;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Register the search attributes the generated component workflow upserts
|
|
943
|
-
// (ComponentName/Phase) on the autoStart dev server (mirrors runOpTemporal).
|
|
944
|
-
if (profile.autoStart) {
|
|
945
|
-
await ensureSearchAttributes(client, profile.namespace, ["ComponentName", "Phase"]);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const profileName = ctx.args.profile ??
|
|
949
|
-
(((chantConfig as Record<string, unknown>).temporal as Record<string, unknown> | undefined)?.defaultProfile as string | undefined) ??
|
|
950
|
-
"local";
|
|
951
|
-
|
|
952
|
-
console.error(formatInfo(`Spawning worker for component "${component.name}" (profile: ${profileName})...`));
|
|
953
|
-
const workerProcess: ChildProcess = spawnChild("npx", ["tsx", workerPath], {
|
|
954
|
-
cwd: projectPath,
|
|
955
|
-
env: { ...process.env, TEMPORAL_PROFILE: profileName },
|
|
956
|
-
stdio: ["ignore", "ignore", "inherit"],
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
const workflowId = componentWorkflowId(component.name);
|
|
960
|
-
const fnName = codegen.componentWorkflowFnName(component.name);
|
|
961
|
-
const taskQueue = profile.taskQueue ?? component.name;
|
|
962
|
-
|
|
963
|
-
let handle: WorkflowHandleRaw;
|
|
964
|
-
try {
|
|
965
|
-
handle = await client.workflow.start(fnName, {
|
|
966
|
-
taskQueue,
|
|
967
|
-
workflowId,
|
|
968
|
-
workflowIdConflictPolicy: "FAIL",
|
|
969
|
-
});
|
|
970
|
-
console.error(formatSuccess(`Workflow started: ${workflowId}`));
|
|
971
|
-
} catch (err) {
|
|
972
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
973
|
-
try { workerProcess.kill(); } catch { /* ignore */ }
|
|
974
|
-
return 1;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const gate = findComponentGate(component);
|
|
978
|
-
if (gate) {
|
|
979
|
-
console.error(formatInfo(
|
|
980
|
-
`Component has a gate ("${gate.signalName}") — unblock it with: chant run signal ${component.name} ${gate.signalName} --components --temporal`,
|
|
981
|
-
));
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
let finalDesc: WorkflowExecutionDescription | undefined;
|
|
985
|
-
|
|
986
|
-
try {
|
|
987
|
-
while (true) {
|
|
988
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
989
|
-
const desc = await handle.describe();
|
|
990
|
-
const history = await fetchNormalizedHistory(handle);
|
|
991
|
-
renderProgress(component.name, history);
|
|
992
|
-
if (TERMINAL_STATUSES.has(desc.status.name)) {
|
|
993
|
-
process.stderr.write("\n");
|
|
994
|
-
finalDesc = desc;
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
} catch (err) {
|
|
999
|
-
process.stderr.write("\n");
|
|
1000
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1001
|
-
return 1;
|
|
1002
|
-
} finally {
|
|
1003
|
-
try { workerProcess.kill(); } catch { /* ignore */ }
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
if (!finalDesc) return 1;
|
|
1007
|
-
|
|
1008
|
-
const status = finalDesc.status.name;
|
|
1009
|
-
console.error(status === "COMPLETED"
|
|
1010
|
-
? formatSuccess(`Component "${component.name}" completed successfully.`)
|
|
1011
|
-
: formatError({ message: `Component "${component.name}" ended with status: ${status}` }),
|
|
1012
|
-
);
|
|
1013
|
-
|
|
1014
|
-
if (status === "COMPLETED") {
|
|
1015
|
-
// (#597) Auto-emit a release record post-run — never inside the generated
|
|
1016
|
-
// workflow (determinism). The workflow returns its final phaseOutputs
|
|
1017
|
-
// (see serializeComponent's codegen) precisely so this read is possible;
|
|
1018
|
-
// handle.result() on an already-COMPLETED workflow is a plain historical
|
|
1019
|
-
// read, not a new activity/side effect.
|
|
1020
|
-
let digest: string | undefined;
|
|
1021
|
-
let manifest: ReturnType<typeof extractRunManifestFromPhaseOutputs>;
|
|
1022
|
-
try {
|
|
1023
|
-
const workflowResult = await handle.result() as { phaseOutputs?: Record<string, Record<string, unknown>> } | undefined;
|
|
1024
|
-
digest = extractRunDigestFromPhaseOutputs(workflowResult?.phaseOutputs);
|
|
1025
|
-
manifest = extractRunManifestFromPhaseOutputs(workflowResult?.phaseOutputs);
|
|
1026
|
-
} catch {
|
|
1027
|
-
digest = undefined;
|
|
1028
|
-
manifest = undefined;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
const { config } = await loadChantConfig(projectPath).catch(() => ({ config: {} }));
|
|
1032
|
-
const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord);
|
|
1033
|
-
const env = ctx.args.env ?? "local";
|
|
1034
|
-
const outcome = await maybeRecordAutoRelease(
|
|
1035
|
-
// A Temporal workflow run id lives in the orchestrator's id space (#2045).
|
|
1036
|
-
{ component: component.name, env, success: true, digest, runId: finalDesc.runId, runOrigin: { forge: "op" } },
|
|
1037
|
-
{ disabled },
|
|
1038
|
-
);
|
|
1039
|
-
if (!outcome.recorded && outcome.reason === "error") {
|
|
1040
|
-
console.error(formatWarning({ message: `release record for "${component.name}"@${env} was not recorded: ${outcome.error}` }));
|
|
1041
|
-
} else if (outcome.recorded) {
|
|
1042
|
-
console.error(formatInfo(
|
|
1043
|
-
`Recorded release: ${formatBold(component.name)}@${env} -> ${outcome.record.digest} (commit ${outcome.commit.slice(0, 7)})`,
|
|
1044
|
-
));
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
// (#609) Persist the run's build manifest post-run, same opt-out flag as
|
|
1048
|
-
// the release record above — see ../../components/manifest-persistence.ts.
|
|
1049
|
-
const manifestOutcome = await maybePersistBuildManifest({ success: true, manifest }, { disabled });
|
|
1050
|
-
if (!manifestOutcome.persisted && manifestOutcome.reason === "error") {
|
|
1051
|
-
console.error(formatWarning({ message: `build manifest for "${component.name}"@${env} was not persisted: ${manifestOutcome.error}` }));
|
|
1052
|
-
} else if (manifestOutcome.persisted) {
|
|
1053
|
-
console.error(formatInfo(
|
|
1054
|
-
`Persisted build manifest: ${formatBold(component.name)} -> ${manifestOutcome.manifestDigest} (commit ${manifestOutcome.commit.slice(0, 7)})`,
|
|
1055
|
-
));
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
return status === "COMPLETED" ? 0 : 1;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* Run an Op in-process via the local executor — no Temporal worker, server, or
|
|
1064
|
-
* built `worker.ts`. Reads the Op config straight from discovery and resolves
|
|
1065
|
-
* activities from the Temporal lexicon package.
|
|
710
|
+
* This function knows nothing about how a run happens. It discovers the Op,
|
|
711
|
+
* runs the CLI-level pre-flights that are about flags rather than execution
|
|
712
|
+
* (`--sandbox` over a `policyGate` step, #2003), hands the config to the
|
|
713
|
+
* runtime, and renders whatever comes back. On the local runtime that is the
|
|
714
|
+
* executor's own `OpRunResult`, so `--json` and the human render are exactly
|
|
715
|
+
* what they were before the seam existed; a runtime that reports only a state
|
|
716
|
+
* gets the terser render instead.
|
|
1066
717
|
*/
|
|
1067
|
-
export async function
|
|
718
|
+
export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
1068
719
|
const opName = ctx.args.path;
|
|
1069
720
|
if (!opName || opName === ".") {
|
|
1070
721
|
console.error(formatError({
|
|
1071
722
|
message: "Op name is required: chant run <name>",
|
|
1072
|
-
hint: "Run `chant run list
|
|
723
|
+
hint: "Run `chant run list` to see available Ops",
|
|
1073
724
|
}));
|
|
1074
725
|
return 1;
|
|
1075
726
|
}
|
|
@@ -1094,32 +745,12 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1094
745
|
// Pre-flight: --sandbox cannot cover a policyGate step (#2003).
|
|
1095
746
|
if (refusesPolicyGateUnderSandbox(ctx, config, opName)) return 1;
|
|
1096
747
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (gate) {
|
|
1100
|
-
console.error(formatError({
|
|
1101
|
-
message: new LocalGateUnsupportedError(gate.signalName).message,
|
|
1102
|
-
}));
|
|
1103
|
-
return 1;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
// The project's configured lexicons decide which cloud appliers to load
|
|
1107
|
-
// (aws → floci, gcp → gcpApply, azure → az group). Best-effort: an unreadable
|
|
1108
|
-
// config just yields the temporal base activities.
|
|
1109
|
-
let lexicons: string[] = [];
|
|
1110
|
-
try {
|
|
1111
|
-
lexicons = (await loadChantConfig(process.cwd())).config.lexicons ?? [];
|
|
1112
|
-
} catch {
|
|
1113
|
-
// No/invalid chant.config — fall back to base activities only.
|
|
1114
|
-
}
|
|
748
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
749
|
+
if (!runtime) return 1;
|
|
1115
750
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
} catch (err) {
|
|
1120
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1121
|
-
return 1;
|
|
1122
|
-
}
|
|
751
|
+
// `--progress-json` streams one NDJSON StepRecord per settled step, fed by
|
|
752
|
+
// whatever the runtime reports through `progress`.
|
|
753
|
+
const progress = ctx.args.progressJson ? ndjsonProgressSink<StepRecord>() : undefined;
|
|
1123
754
|
|
|
1124
755
|
// Ctrl-C aborts in-flight activities (kills their child processes) instead of
|
|
1125
756
|
// orphaning them. The handler is removed in `finally` so it never leaks.
|
|
@@ -1131,18 +762,38 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1131
762
|
process.once("SIGINT", onSigint);
|
|
1132
763
|
|
|
1133
764
|
try {
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
765
|
+
const handle = await runtime.start(config, {
|
|
766
|
+
env: ctx.args.env,
|
|
767
|
+
...(ctx.args.profile !== undefined ? { profile: ctx.args.profile } : {}),
|
|
768
|
+
progress,
|
|
769
|
+
signal: controller.signal,
|
|
770
|
+
});
|
|
771
|
+
const status = await handle.result();
|
|
772
|
+
|
|
773
|
+
if (status.result) {
|
|
774
|
+
// The executor's own render already prints a gate, its approve line
|
|
775
|
+
// and its expiry (#2119).
|
|
776
|
+
if (ctx.args.json) renderJson(status.result); else renderHuman(status.result);
|
|
777
|
+
} else {
|
|
778
|
+
renderRuntimeStatus("Op", opName, runtime.name, status);
|
|
779
|
+
if (status.state === "gated" && status.gate) {
|
|
780
|
+
console.error(formatWarning({
|
|
781
|
+
message: `Op "${opName}" is waiting on gate "${status.gate.name}"`,
|
|
782
|
+
hint: `Record the resolution with: ${approveCommand(opName, status.gate.name)}`,
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Exit 3 for a gated run (#2119) — a distinct code so CI can tell
|
|
788
|
+
// "waiting on a human" from a broken op, and retry the one but not the
|
|
789
|
+
// other.
|
|
790
|
+
if (status.state === "gated") return GATED_EXIT_CODE;
|
|
791
|
+
return status.state === "completed" ? 0 : 1;
|
|
1137
792
|
} catch (err) {
|
|
1138
793
|
if (err instanceof OpRunFailure) {
|
|
1139
794
|
if (ctx.args.json) renderJson(err.result); else renderHuman(err.result);
|
|
1140
795
|
return 1;
|
|
1141
796
|
}
|
|
1142
|
-
if (err instanceof LocalGateUnsupportedError) {
|
|
1143
|
-
console.error(formatError({ message: err.message }));
|
|
1144
|
-
return 1;
|
|
1145
|
-
}
|
|
1146
797
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1147
798
|
return 1;
|
|
1148
799
|
} finally {
|
|
@@ -1150,213 +801,12 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1150
801
|
}
|
|
1151
802
|
}
|
|
1152
803
|
|
|
1153
|
-
async function runOpTemporal(ctx: CommandContext): Promise<number> {
|
|
1154
|
-
const opName = ctx.args.path;
|
|
1155
|
-
|
|
1156
|
-
if (!opName || opName === ".") {
|
|
1157
|
-
console.error(formatError({
|
|
1158
|
-
message: "Op name is required: chant run <name>",
|
|
1159
|
-
hint: "Run `chant run list` to see available Ops",
|
|
1160
|
-
}));
|
|
1161
|
-
return 1;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// Discover Ops
|
|
1165
|
-
const { ops, errors } = await discoverOps();
|
|
1166
|
-
for (const err of errors) console.error(formatWarning({ message: err }));
|
|
1167
|
-
|
|
1168
|
-
const discovered = ops.get(opName);
|
|
1169
|
-
if (!discovered) {
|
|
1170
|
-
const names = [...ops.keys()];
|
|
1171
|
-
console.error(formatError({
|
|
1172
|
-
message: `Op "${opName}" not found`,
|
|
1173
|
-
hint: names.length > 0
|
|
1174
|
-
? `Available: ${names.join(", ")}`
|
|
1175
|
-
: "No *.op.ts files found — create one or run `chant run list`",
|
|
1176
|
-
}));
|
|
1177
|
-
return 1;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
const { config } = discovered;
|
|
1181
|
-
|
|
1182
|
-
// Pre-flight: --sandbox cannot cover a policyGate step (#2003). Refused here
|
|
1183
|
-
// too, so the durable path fails as a CLI error rather than as a
|
|
1184
|
-
// non-retryable activity failure buried in workflow history.
|
|
1185
|
-
if (refusesPolicyGateUnderSandbox(ctx, config, opName)) return 1;
|
|
1186
|
-
|
|
1187
|
-
const projectPath = resolve(".");
|
|
1188
|
-
|
|
1189
|
-
// Load config + profile
|
|
1190
|
-
const { config: chantConfig } = await loadChantConfig(projectPath);
|
|
1191
|
-
let profile;
|
|
1192
|
-
try {
|
|
1193
|
-
profile = resolveProfile(chantConfig as Record<string, unknown>, ctx.args.profile);
|
|
1194
|
-
} catch (err) {
|
|
1195
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1196
|
-
return 1;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Handle --report flag: just print the last run report
|
|
1200
|
-
if (ctx.args.report) {
|
|
1201
|
-
let client, desc: WorkflowExecutionDescription, history: WorkflowHistoryRaw;
|
|
1202
|
-
try {
|
|
1203
|
-
({ client } = await makeTemporalClient(ctx.args.profile, projectPath));
|
|
1204
|
-
const handle = client.workflow.getHandle(resolveWorkflowId(opName));
|
|
1205
|
-
desc = await handle.describe();
|
|
1206
|
-
history = await fetchNormalizedHistory(handle);
|
|
1207
|
-
} catch (err) {
|
|
1208
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1209
|
-
return 1;
|
|
1210
|
-
}
|
|
1211
|
-
const md = generateReport(opName, config, desc, history);
|
|
1212
|
-
process.stdout.write(md);
|
|
1213
|
-
return 0;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// Check built worker exists
|
|
1217
|
-
const workerPath = join(projectPath, "dist", "ops", opName, "worker.ts");
|
|
1218
|
-
if (!existsSync(workerPath)) {
|
|
1219
|
-
console.error(formatError({
|
|
1220
|
-
message: `dist/ops/${opName}/worker.ts not found`,
|
|
1221
|
-
hint: "Run `chant build` first to generate the worker",
|
|
1222
|
-
}));
|
|
1223
|
-
return 1;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
// autoStart: spin up temporal server if needed
|
|
1227
|
-
if (profile.autoStart) {
|
|
1228
|
-
console.error(formatInfo("autoStart: checking Temporal server..."));
|
|
1229
|
-
try {
|
|
1230
|
-
await waitForTemporalServer(profile.address, 2000);
|
|
1231
|
-
console.error(formatInfo("Temporal server already running."));
|
|
1232
|
-
} catch {
|
|
1233
|
-
console.error(formatInfo("Starting temporal server start-dev..."));
|
|
1234
|
-
spawnChild("temporal", ["server", "start-dev"], {
|
|
1235
|
-
cwd: projectPath,
|
|
1236
|
-
stdio: "ignore",
|
|
1237
|
-
detached: true,
|
|
1238
|
-
}).unref();
|
|
1239
|
-
await waitForTemporalServer(profile.address, 30_000);
|
|
1240
|
-
console.error(formatSuccess("Temporal server ready."));
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
// Load Temporal client
|
|
1245
|
-
let client;
|
|
1246
|
-
try {
|
|
1247
|
-
({ client } = await makeTemporalClient(ctx.args.profile, projectPath));
|
|
1248
|
-
} catch (err) {
|
|
1249
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1250
|
-
return 1;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// On the autoStart dev server, register the search attributes the generated
|
|
1254
|
-
// workflow upserts (OpName/Phase + any op-declared) so the first workflow task
|
|
1255
|
-
// does not fail with BadSearchAttributes.
|
|
1256
|
-
if (profile.autoStart) {
|
|
1257
|
-
await ensureSearchAttributes(client, profile.namespace, [
|
|
1258
|
-
"OpName",
|
|
1259
|
-
"Phase",
|
|
1260
|
-
...Object.keys((config as { searchAttributes?: Record<string, string> }).searchAttributes ?? {}),
|
|
1261
|
-
]);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// Spawn worker process
|
|
1265
|
-
const profileName = ctx.args.profile ??
|
|
1266
|
-
(((chantConfig as Record<string, unknown>).temporal as Record<string, unknown> | undefined)?.defaultProfile as string | undefined) ??
|
|
1267
|
-
"local";
|
|
1268
|
-
|
|
1269
|
-
console.error(formatInfo(`Spawning worker for Op "${opName}" (profile: ${profileName})...`));
|
|
1270
|
-
const workerProcess: ChildProcess = spawnChild("npx", ["tsx", workerPath], {
|
|
1271
|
-
cwd: projectPath,
|
|
1272
|
-
env: { ...process.env, TEMPORAL_PROFILE: profileName },
|
|
1273
|
-
stdio: ["ignore", "ignore", "inherit"],
|
|
1274
|
-
});
|
|
1275
|
-
|
|
1276
|
-
// Submit workflow
|
|
1277
|
-
const workflowId = resolveWorkflowId(opName);
|
|
1278
|
-
const fnName = workflowFnName(opName);
|
|
1279
|
-
const taskQueue = profile.taskQueue ?? opName;
|
|
1280
|
-
|
|
1281
|
-
let handle: WorkflowHandleRaw;
|
|
1282
|
-
try {
|
|
1283
|
-
handle = await client.workflow.start(fnName, {
|
|
1284
|
-
taskQueue,
|
|
1285
|
-
workflowId,
|
|
1286
|
-
workflowIdConflictPolicy: "FAIL",
|
|
1287
|
-
});
|
|
1288
|
-
console.error(formatSuccess(`Workflow started: ${workflowId}`));
|
|
1289
|
-
} catch (err) {
|
|
1290
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1291
|
-
return 1;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
// Poll for progress until terminal state.
|
|
1295
|
-
//
|
|
1296
|
-
// `--progress-json` (chant #1676): stream one NDJSON StepRecord per line to
|
|
1297
|
-
// stdout as each declared step settles — the durable-path counterpart to
|
|
1298
|
-
// `chant run <name> --json`'s local-executor record shape (op-progress.ts).
|
|
1299
|
-
// Purely additive: when the flag is absent `emitNewRecords` stays undefined
|
|
1300
|
-
// and every call site is a no-op, so poll-loop behavior is unchanged.
|
|
1301
|
-
//
|
|
1302
|
-
// Dedup is by declared-step identity, not by an emitted count (chant #2032):
|
|
1303
|
-
// the terminal `final: true` pass *inserts* a `"skipped"` record at each
|
|
1304
|
-
// never-reached step's declared position, so the list is not append-only and
|
|
1305
|
-
// a count watermark shifts off the end of it.
|
|
1306
|
-
const emitProgress = ctx.args.progressJson ? ndjsonProgressSink<StepRecord>() : undefined;
|
|
1307
|
-
const emitNewRecords = emitProgress ? stepRecordEmitter(emitProgress) : undefined;
|
|
1308
|
-
|
|
1309
|
-
let finalDesc: WorkflowExecutionDescription | undefined;
|
|
1310
|
-
let finalHistory: WorkflowHistoryRaw | undefined;
|
|
1311
|
-
|
|
1312
|
-
try {
|
|
1313
|
-
while (true) {
|
|
1314
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1315
|
-
const desc = await handle.describe();
|
|
1316
|
-
const history = await fetchNormalizedHistory(handle);
|
|
1317
|
-
|
|
1318
|
-
renderProgress(opName, history);
|
|
1319
|
-
emitNewRecords?.(extractIndexedStepRecords(config, history));
|
|
1320
|
-
|
|
1321
|
-
if (TERMINAL_STATUSES.has(desc.status.name)) {
|
|
1322
|
-
process.stderr.write("\n");
|
|
1323
|
-
emitNewRecords?.(extractIndexedStepRecords(config, history, { final: true }));
|
|
1324
|
-
finalDesc = desc;
|
|
1325
|
-
finalHistory = history;
|
|
1326
|
-
break;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
} catch (err) {
|
|
1330
|
-
process.stderr.write("\n");
|
|
1331
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1332
|
-
return 1;
|
|
1333
|
-
} finally {
|
|
1334
|
-
// Kill worker process (best-effort)
|
|
1335
|
-
try { workerProcess.kill(); } catch { /* ignore */ }
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
if (!finalDesc || !finalHistory) return 1;
|
|
1339
|
-
|
|
1340
|
-
const status = finalDesc.status.name;
|
|
1341
|
-
console.error(status === "COMPLETED"
|
|
1342
|
-
? formatSuccess(`Op "${opName}" completed successfully.`)
|
|
1343
|
-
: formatError({ message: `Op "${opName}" ended with status: ${status}` }),
|
|
1344
|
-
);
|
|
1345
|
-
|
|
1346
|
-
// Write deployment report
|
|
1347
|
-
const md = generateReport(opName, config, finalDesc, finalHistory);
|
|
1348
|
-
const reportPath = writeReport(opName, md);
|
|
1349
|
-
console.error(formatInfo(`Report written to ${reportPath}`));
|
|
1350
|
-
|
|
1351
|
-
return status === "COMPLETED" ? 0 : 1;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
804
|
// ── fallback ────────────────────────────────────────────────────────────────���─
|
|
1355
805
|
|
|
1356
806
|
export function runOpUnknown(ctx: CommandContext): Promise<number> {
|
|
1357
807
|
console.error(formatError({
|
|
1358
808
|
message: `Unknown run subcommand: ${ctx.args.extraPositional ?? ctx.args.path}`,
|
|
1359
|
-
hint: "Available: chant run <name>, run list, run status, run
|
|
809
|
+
hint: "Available: chant run <name>, run list, run status, run approve, run cancel, run log",
|
|
1360
810
|
}));
|
|
1361
811
|
return Promise.resolve(1);
|
|
1362
812
|
}
|