@intentius/chant 0.57.0 → 0.59.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 +36 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +26 -9
- 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/carve-bridge.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 +13 -0
- 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 +12 -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/plugins.d.ts +22 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +40 -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 +24 -22
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +21 -6
- 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 +73 -37
- 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 +10 -22
- 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/detectLexicon.d.ts +13 -0
- package/dist/detectLexicon.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 +273 -16
- 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 +79 -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 +149 -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 +85 -30
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/change-signal.d.ts +91 -0
- package/dist/op/change-signal.d.ts.map +1 -0
- package/dist/op/composites/apply-op.d.ts +118 -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-name.d.ts +40 -0
- package/dist/op/gate-name.d.ts.map +1 -0
- package/dist/op/gate-summary.d.ts +43 -0
- package/dist/op/gate-summary.d.ts.map +1 -0
- 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 +29 -9
- 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 +185 -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 +93 -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 +96 -29
- 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/dist/terraform/bridge.d.ts +26 -7
- package/dist/terraform/bridge.d.ts.map +1 -1
- package/dist/terraform/carve-provider.d.ts +28 -2
- package/dist/terraform/carve-provider.d.ts.map +1 -1
- package/dist/terraform/data-source-shape.d.ts +77 -0
- package/dist/terraform/data-source-shape.d.ts.map +1 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/providers/kubernetes.d.ts +5 -0
- package/dist/terraform/providers/kubernetes.d.ts.map +1 -1
- package/dist/terraform/tier-map.d.ts +16 -4
- package/dist/terraform/tier-map.d.ts.map +1 -1
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.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 +84 -18
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +202 -18
- 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 +191 -11
- package/src/cli/commands/audit.ts +78 -28
- package/src/cli/commands/build.test.ts +330 -9
- package/src/cli/commands/build.ts +170 -24
- package/src/cli/commands/carve-bridge.test.ts +237 -14
- package/src/cli/commands/carve-bridge.ts +21 -17
- package/src/cli/commands/carve-emit-k8s.test.ts +17 -10
- 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 +194 -1
- package/src/cli/commands/lint.ts +205 -7
- 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 +341 -55
- package/src/cli/handlers/run.test.ts +568 -1295
- package/src/cli/handlers/run.ts +446 -912
- package/src/cli/main.test.ts +22 -16
- package/src/cli/main.ts +66 -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/plugins.test.ts +68 -2
- package/src/cli/plugins.ts +39 -0
- package/src/cli/registry.ts +40 -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 +4 -3
- package/src/components/__fixtures__/neo4j-fanout.json +1 -1
- 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 +28 -12
- package/src/components/cli-support.ts +60 -47
- package/src/components/component-schema.test.ts +18 -2
- package/src/components/component.schema.json +18 -5
- package/src/components/component.test.ts +2 -2
- package/src/components/component.ts +27 -7
- package/src/components/config-defaults.test.ts +2 -2
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +109 -20
- package/src/components/driver.ts +201 -84
- 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/neo4j-fanout.pilot.ts +3 -3
- 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.test.ts +19 -0
- package/src/components/verbs/run-agent.ts +12 -24
- 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/detectLexicon.ts +18 -1
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.test.ts +1 -1
- 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 +82 -2
- 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 +3 -3
- 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 +296 -16
- 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 +154 -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/pipeline-change-gate.test.ts +2 -2
- 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 +27 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +76 -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 +161 -0
- package/src/op/activities/reconcile.ts +354 -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 +125 -41
- package/src/op/change-signal.test.ts +117 -0
- package/src/op/change-signal.ts +169 -0
- package/src/op/composites/apply-op.ts +218 -0
- package/src/op/composites/composites.test.ts +379 -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 +110 -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 +29 -14
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate-name.test.ts +65 -0
- package/src/op/gate-name.ts +60 -0
- package/src/op/gate-summary.ts +84 -0
- 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 +48 -8
- package/src/op/local-executor.test.ts +178 -34
- package/src/op/local-executor.ts +371 -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 +292 -0
- package/src/op/op-ir.ts +419 -0
- package/src/op/op-verb-class.test.ts +2 -2
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +28 -7
- package/src/op/operator.test.ts +543 -6
- package/src/op/operator.ts +203 -20
- 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 +106 -26
- 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/src/terraform/aws-resources.test.ts +13 -4
- package/src/terraform/bridge.test.ts +22 -9
- package/src/terraform/bridge.ts +89 -28
- package/src/terraform/carve-provider.ts +38 -2
- package/src/terraform/data-source-shape.ts +95 -0
- package/src/terraform/graph.ts +35 -7
- package/src/terraform/providers/kubernetes.ts +48 -2
- package/src/terraform/tier-map.ts +21 -6
- package/src/terraform/types.ts +8 -0
- 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,237 @@
|
|
|
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 { writeGatedRunSummary, type GatedRunSummary } from "../../op/gate-summary";
|
|
10
|
+
import { createLocalOpRuntime } from "../../op/runtimes/local";
|
|
11
|
+
import type { OpRuntimeProvider, OpRunStatus } from "../../op/runtime";
|
|
10
12
|
import { renderHuman, renderJson } from "../../op/local-output";
|
|
13
|
+
import { loadPlugins } from "../plugins";
|
|
14
|
+
import { recordGateApproval } from "./operator";
|
|
11
15
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
12
16
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
13
17
|
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
18
|
import { renderDriverHuman, renderDriverJson } from "../../components/driver-output";
|
|
28
19
|
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";
|
|
20
|
+
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
21
|
+
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
33
22
|
import type { DriverComponentResult } from "../../components/driver";
|
|
34
|
-
import type { BuildParamProvenance } from "../../provenance";
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The exit code a run that stopped at an unapproved gate uses (#2119).
|
|
26
|
+
* Deliberately not 1: a gate is a standing fact waiting on a human, and a CI
|
|
27
|
+
* job that treats it as a failure would page someone for a decision nobody has
|
|
28
|
+
* made yet.
|
|
29
|
+
*/
|
|
30
|
+
export const GATED_EXIT_CODE = 3;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The exit code this invocation gives a gated run — {@link GATED_EXIT_CODE}
|
|
34
|
+
* unless `--gated-exit <code>` asked for another one (#2243).
|
|
35
|
+
*
|
|
36
|
+
* The mapping lives here rather than in a shell wrapper in every generated
|
|
37
|
+
* pipeline, so one rule covers every forge: GitHub Actions has no neutral
|
|
38
|
+
* conclusion for a `run:` step, so a push-to-main apply that stops at its gate
|
|
39
|
+
* paints the branch red on every merge until someone approves. `--gated-exit
|
|
40
|
+
* 0` is how the job that knows a pending approval is not a failure says so.
|
|
41
|
+
*
|
|
42
|
+
* Only the gated outcome is remapped. A failed run still returns 1, so the
|
|
43
|
+
* flag can never hide a broken apply. Returns `undefined` after printing the
|
|
44
|
+
* refusal when the value is not a process exit status.
|
|
45
|
+
*/
|
|
46
|
+
function resolveGatedExitCode(ctx: CommandContext): number | undefined {
|
|
47
|
+
const raw = ctx.args.gatedExit;
|
|
48
|
+
if (raw === undefined) return GATED_EXIT_CODE;
|
|
49
|
+
if (!Number.isInteger(raw) || raw < 0 || raw > 255) {
|
|
50
|
+
console.error(formatError({
|
|
51
|
+
message: "--gated-exit expects a whole number from 0 to 255",
|
|
52
|
+
hint: `Got ${Number.isNaN(raw) ? "a non-numeric value" : String(raw)}. ` +
|
|
53
|
+
`Pass --gated-exit 0 to make a run that stopped at a gate a success for CI; ` +
|
|
54
|
+
`omit the flag for the default ${GATED_EXIT_CODE}.`,
|
|
55
|
+
}));
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return raw;
|
|
38
59
|
}
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
/**
|
|
62
|
+
* What a gated run leaves behind for CI, beyond the stderr block the renderers
|
|
63
|
+
* already print (#2243): the same gate, approve command and ledger path
|
|
64
|
+
* appended to whatever file `GITHUB_STEP_SUMMARY` names, so the run page says
|
|
65
|
+
* what is pending without anyone opening the log.
|
|
66
|
+
*
|
|
67
|
+
* Also says on stderr that the exit code was remapped, when it was. A job that
|
|
68
|
+
* passes `--gated-exit 0` reports success, and the one line that explains why
|
|
69
|
+
* a zero-exit run applied nothing belongs next to the gate itself.
|
|
70
|
+
*/
|
|
71
|
+
function reportGatedRun(summary: GatedRunSummary, exitCode: number): void {
|
|
72
|
+
writeGatedRunSummary(summary);
|
|
73
|
+
if (exitCode !== GATED_EXIT_CODE) {
|
|
74
|
+
console.error(formatInfo(
|
|
75
|
+
`gated: exiting ${exitCode} because --gated-exit asked for it. Nothing after the gate ran.`,
|
|
76
|
+
));
|
|
77
|
+
}
|
|
42
78
|
}
|
|
43
79
|
|
|
44
80
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
81
|
+
* `run list/status/log/cancel --components` reported a component's *durable*
|
|
82
|
+
* run state — a run that outlives the CLI process and can be queried, signalled
|
|
83
|
+
* or cancelled afterwards (#589). #2116 removed the runtime that provided it.
|
|
84
|
+
* The local driver runs a component to completion inside this process, so there
|
|
85
|
+
* is no separate run left to ask about once the command returns.
|
|
86
|
+
*
|
|
87
|
+
* Returns 1; the caller returns it straight back.
|
|
49
88
|
*/
|
|
50
|
-
function
|
|
51
|
-
if (ctx.args.temporal) return true;
|
|
89
|
+
function refuseDurableComponentSubcommand(what: string, hint: string): number {
|
|
52
90
|
console.error(formatError({
|
|
53
|
-
message: `\`${what}\`
|
|
54
|
-
hint
|
|
91
|
+
message: `\`${what}\` read a component's durable run state, which #2116 removed`,
|
|
92
|
+
hint,
|
|
55
93
|
}));
|
|
56
|
-
return
|
|
94
|
+
return 1;
|
|
57
95
|
}
|
|
58
96
|
|
|
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
|
-
}
|
|
97
|
+
// ── The runtime seam (#2121) ────────────────────────────────────────────────
|
|
67
98
|
|
|
68
99
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
100
|
+
* Resolve the one runtime this invocation talks to.
|
|
101
|
+
*
|
|
102
|
+
* `--on <name>` picks the named lexicon's `opRuntime` (`../../lexicon.ts`);
|
|
103
|
+
* without it, core's built-in `local` provider runs the Op in this process.
|
|
104
|
+
* Every `chant run` subcommand goes through whichever comes back, so there is
|
|
105
|
+
* one dispatch path rather than a branch per runtime.
|
|
106
|
+
*
|
|
107
|
+
* A lexicon already loaded into the command context wins the lookup — that is
|
|
108
|
+
* how a test hands in a stub, and how a command that loaded plugins for its
|
|
109
|
+
* own reasons avoids loading them twice. Otherwise the project's configured
|
|
110
|
+
* lexicons are loaded on demand: `chant run` is not a `requiresPlugins`
|
|
111
|
+
* command, and a plain local run must not start paying for plugin resolution.
|
|
112
|
+
*
|
|
113
|
+
* Returns `undefined` after printing an actionable error — an unconfigured
|
|
114
|
+
* name is answered with the configured list, a configured lexicon that hosts
|
|
115
|
+
* nothing is named outright.
|
|
75
116
|
*/
|
|
76
|
-
async function
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
117
|
+
async function resolveOpRuntime(ctx: CommandContext): Promise<OpRuntimeProvider | undefined> {
|
|
118
|
+
const on = ctx.args.on;
|
|
119
|
+
if (!on || on === "local") return createLocalOpRuntime({ projectPath: resolve(".") });
|
|
120
|
+
|
|
121
|
+
// Read the configured list straight from `chant.config.ts` rather than
|
|
122
|
+
// through `resolveProjectLexicons`: `--on` names a *configured* lexicon, and
|
|
123
|
+
// that helper's fallback is a source scan of the whole project, which is a
|
|
124
|
+
// long wait to be told a name is wrong.
|
|
125
|
+
let configured: string[] = [];
|
|
126
|
+
try {
|
|
127
|
+
configured = (await loadChantConfig(resolve("."))).config.lexicons ?? [];
|
|
128
|
+
} catch {
|
|
129
|
+
// No/unreadable chant.config.ts — the error below says so by listing nothing.
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let plugin = ctx.plugins.find((p) => p.name === on);
|
|
133
|
+
if (!plugin && configured.includes(on)) {
|
|
134
|
+
plugin = (await loadPlugins([on]).catch(() => []))[0];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!plugin) {
|
|
138
|
+
const known = [...new Set([...ctx.plugins.map((p) => p.name), ...configured])];
|
|
139
|
+
console.error(formatError({
|
|
140
|
+
message: `--on ${on}: "${on}" is not a configured lexicon`,
|
|
141
|
+
hint: known.length > 0
|
|
142
|
+
? `Configured lexicons: ${known.join(", ")}. Omit --on to run on the built-in local runtime.`
|
|
143
|
+
: "chant.config.ts configures no lexicons. Omit --on to run on the built-in local runtime.",
|
|
144
|
+
}));
|
|
145
|
+
return undefined;
|
|
91
146
|
}
|
|
147
|
+
|
|
148
|
+
if (!plugin.opRuntime) {
|
|
149
|
+
console.error(formatError({
|
|
150
|
+
message: `--on ${on}: lexicon "${on}" does not host Op runs`,
|
|
151
|
+
hint: "It declares no opRuntime. Omit --on to run on the built-in local runtime.",
|
|
152
|
+
}));
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return plugin.opRuntime;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** An ISO-8601 instant trimmed to what a table cell has room for. */
|
|
160
|
+
function shortInstant(iso: string | undefined): string {
|
|
161
|
+
return iso ? iso.slice(0, 19).replace("T", " ") : "—";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Print what a runtime reports for a settled or in-flight run. */
|
|
165
|
+
function renderRuntimeStatus(label: string, name: string, runtime: string, status: OpRunStatus): void {
|
|
166
|
+
console.log(formatBold(`${label}: ${name}`));
|
|
167
|
+
console.log(` Runtime : ${runtime}`);
|
|
168
|
+
console.log(` Run ID : ${status.runId}`);
|
|
169
|
+
console.log(` State : ${status.state}`);
|
|
170
|
+
console.log(` Started : ${shortInstant(status.startedAt)}`);
|
|
171
|
+
if (status.endedAt) console.log(` Ended : ${shortInstant(status.endedAt)}`);
|
|
172
|
+
if (status.records) {
|
|
173
|
+
const settled = status.records.filter((r) => r.status !== "skipped").length;
|
|
174
|
+
console.log(` Steps : ${settled}/${status.records.length} settled`);
|
|
175
|
+
}
|
|
176
|
+
if (status.gate) {
|
|
177
|
+
console.log(` Gate : ${status.gate.name} (pending since ${status.gate.since})`);
|
|
178
|
+
}
|
|
179
|
+
if (status.error) console.log(` Error : ${status.error}`);
|
|
92
180
|
}
|
|
93
181
|
|
|
94
182
|
// ── chant run list ──────────────────────────────────���─────────────────────────
|
|
95
183
|
|
|
96
184
|
export async function runOpList(ctx: CommandContext): Promise<number> {
|
|
97
|
-
if (ctx.args.components) return runComponentsList(
|
|
185
|
+
if (ctx.args.components) return runComponentsList();
|
|
186
|
+
return runOpListOnRuntime(ctx);
|
|
187
|
+
}
|
|
98
188
|
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
/**
|
|
190
|
+
* `chant run list` on the resolved runtime (#2121) — discover every Op, ask
|
|
191
|
+
* the runtime what it knows about each, print one row per Op. The runtime
|
|
192
|
+
* answers for all of them in one call (`list`), so a hosted runtime can do it
|
|
193
|
+
* in one round trip instead of N.
|
|
194
|
+
*
|
|
195
|
+
* An Op the runtime has no run for prints with no state annotation.
|
|
196
|
+
*/
|
|
197
|
+
async function runOpListOnRuntime(ctx: CommandContext): Promise<number> {
|
|
198
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
199
|
+
if (!runtime) return 1;
|
|
101
200
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
201
|
+
const { ops, errors } = await discoverOps();
|
|
202
|
+
for (const err of errors) console.error(formatError({ message: err }));
|
|
105
203
|
|
|
106
204
|
if (ops.size === 0) {
|
|
107
205
|
console.error(formatWarning({ message: "No Op definitions found (*.op.ts)" }));
|
|
108
206
|
return 0;
|
|
109
207
|
}
|
|
110
208
|
|
|
209
|
+
let states: Map<string, OpRunStatus | undefined>;
|
|
210
|
+
try {
|
|
211
|
+
states = await runtime.list([...ops.values()].map((d) => d.config));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
111
217
|
console.log(
|
|
112
|
-
"NAME".padEnd(
|
|
218
|
+
"NAME".padEnd(26) +
|
|
113
219
|
"PHASES".padEnd(8) +
|
|
114
|
-
"TASK-QUEUE".padEnd(20) +
|
|
115
220
|
"DEPENDS".padEnd(20) +
|
|
116
221
|
"OVERVIEW",
|
|
117
222
|
);
|
|
118
223
|
|
|
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
224
|
for (const [name, { config }] of ops) {
|
|
141
225
|
const phases = String(config.phases.length);
|
|
142
|
-
const tq = config.taskQueue ?? config.name;
|
|
143
226
|
const deps = config.depends?.join(",") ?? "—";
|
|
144
227
|
const overview = config.overview.length > 36
|
|
145
228
|
? config.overview.slice(0, 33) + "..."
|
|
146
229
|
: config.overview;
|
|
147
|
-
const
|
|
148
|
-
const statusStr = status ? ` [${status}]` : "";
|
|
230
|
+
const state = states.get(name)?.state;
|
|
149
231
|
|
|
150
232
|
console.log(
|
|
151
|
-
(name +
|
|
233
|
+
(name + (state ? ` [${state}]` : "")).padEnd(26) +
|
|
152
234
|
phases.padEnd(8) +
|
|
153
|
-
tq.padEnd(20) +
|
|
154
235
|
deps.padEnd(20) +
|
|
155
236
|
overview,
|
|
156
237
|
);
|
|
@@ -160,89 +241,21 @@ export async function runOpList(ctx: CommandContext): Promise<number> {
|
|
|
160
241
|
}
|
|
161
242
|
|
|
162
243
|
/**
|
|
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.
|
|
244
|
+
* `chant run list --components` (#599) listed discovered components annotated
|
|
245
|
+
* with each one's durable run status. Discovery alone is what `chant list
|
|
246
|
+
* --components` already prints, so with the status column gone (#2116) this
|
|
247
|
+
* subcommand has nothing of its own left to say.
|
|
179
248
|
*/
|
|
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;
|
|
249
|
+
function runComponentsList(): Promise<number> {
|
|
250
|
+
return Promise.resolve(refuseDurableComponentSubcommand(
|
|
251
|
+
"chant run list --components",
|
|
252
|
+
"Run `chant list --components` to see discovered components.",
|
|
253
|
+
));
|
|
240
254
|
}
|
|
241
255
|
|
|
242
256
|
// ── chant run status <name> ───────────────────────────────────────────────────
|
|
243
257
|
|
|
244
258
|
export async function runOpStatus(ctx: CommandContext): Promise<number> {
|
|
245
|
-
if (!requireTemporalMode(ctx, "chant run status")) return 1;
|
|
246
259
|
const name = ctx.args.extraPositional;
|
|
247
260
|
if (!name) {
|
|
248
261
|
const label = ctx.args.components ? "Component" : "Op";
|
|
@@ -250,84 +263,44 @@ export async function runOpStatus(ctx: CommandContext): Promise<number> {
|
|
|
250
263
|
return 1;
|
|
251
264
|
}
|
|
252
265
|
|
|
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})`);
|
|
266
|
+
if (ctx.args.components) {
|
|
267
|
+
return refuseDurableComponentSubcommand(
|
|
268
|
+
"chant run status --components",
|
|
269
|
+
"Run `chant components status` for a component's release and build state.",
|
|
270
|
+
);
|
|
286
271
|
}
|
|
287
|
-
|
|
288
|
-
return 0;
|
|
272
|
+
return runOpStatusOnRuntime(ctx, name);
|
|
289
273
|
}
|
|
290
274
|
|
|
291
275
|
/**
|
|
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).
|
|
276
|
+
* `chant run status <name>` on the resolved runtime (#2121). A runtime with
|
|
277
|
+
* no record of the Op is not an error: it prints one line saying so and exits
|
|
278
|
+
* 0, because "this runtime has never run it" is a true answer to the question.
|
|
297
279
|
*/
|
|
298
|
-
async function
|
|
299
|
-
const
|
|
300
|
-
|
|
280
|
+
async function runOpStatusOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
281
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
282
|
+
if (!runtime) return 1;
|
|
283
|
+
|
|
284
|
+
let status: OpRunStatus | undefined;
|
|
301
285
|
try {
|
|
302
|
-
|
|
303
|
-
const handle = client.workflow.getHandle(componentWorkflowId(name));
|
|
304
|
-
desc = await handle.describe();
|
|
305
|
-
history = await fetchNormalizedHistory(handle);
|
|
286
|
+
status = await runtime.status(name);
|
|
306
287
|
} catch (err) {
|
|
307
288
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
308
289
|
return 1;
|
|
309
290
|
}
|
|
310
291
|
|
|
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`);
|
|
292
|
+
if (!status) {
|
|
293
|
+
console.error(formatInfo(`No run of Op "${name}" is recorded on the "${runtime.name}" runtime.`));
|
|
294
|
+
return 0;
|
|
322
295
|
}
|
|
323
296
|
|
|
297
|
+
renderRuntimeStatus("Op", name, runtime.name, status);
|
|
324
298
|
return 0;
|
|
325
299
|
}
|
|
326
300
|
|
|
327
301
|
// ── chant run log <name> ──────────────────────────────────────────────────────
|
|
328
302
|
|
|
329
303
|
export async function runOpLog(ctx: CommandContext): Promise<number> {
|
|
330
|
-
if (!requireTemporalMode(ctx, "chant run log")) return 1;
|
|
331
304
|
const name = ctx.args.extraPositional;
|
|
332
305
|
if (!name) {
|
|
333
306
|
const label = ctx.args.components ? "Component" : "Op";
|
|
@@ -335,151 +308,128 @@ export async function runOpLog(ctx: CommandContext): Promise<number> {
|
|
|
335
308
|
return 1;
|
|
336
309
|
}
|
|
337
310
|
|
|
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;
|
|
311
|
+
if (ctx.args.components) {
|
|
312
|
+
return refuseDurableComponentSubcommand(
|
|
313
|
+
"chant run log --components",
|
|
314
|
+
"Run `chant components status` for a component's recorded releases.",
|
|
315
|
+
);
|
|
371
316
|
}
|
|
372
|
-
|
|
373
|
-
return 0;
|
|
317
|
+
return runOpLogOnRuntime(ctx, name);
|
|
374
318
|
}
|
|
375
319
|
|
|
376
320
|
/**
|
|
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.
|
|
321
|
+
* `chant run log <name>` on the resolved runtime (#2121) — the runtime's own
|
|
322
|
+
* run history, newest first, `--limit <n>` rows at most.
|
|
385
323
|
*/
|
|
386
|
-
async function
|
|
387
|
-
const
|
|
388
|
-
|
|
324
|
+
async function runOpLogOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
325
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
326
|
+
if (!runtime) return 1;
|
|
327
|
+
|
|
328
|
+
let records;
|
|
389
329
|
try {
|
|
390
|
-
|
|
330
|
+
records = await runtime.log(name, ctx.args.limit === undefined ? undefined : { limit: ctx.args.limit });
|
|
391
331
|
} catch (err) {
|
|
392
332
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
393
333
|
return 1;
|
|
394
334
|
}
|
|
395
335
|
|
|
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;
|
|
336
|
+
if (records.length === 0) {
|
|
337
|
+
console.error(formatInfo(`No run of Op "${name}" is recorded on the "${runtime.name}" runtime.`));
|
|
338
|
+
return 0;
|
|
403
339
|
}
|
|
404
340
|
|
|
405
341
|
console.log(
|
|
406
|
-
"RUN-ID".padEnd(
|
|
407
|
-
"STATUS".padEnd(
|
|
342
|
+
"RUN-ID".padEnd(38) +
|
|
343
|
+
"STATUS".padEnd(10) +
|
|
408
344
|
"STARTED".padEnd(26) +
|
|
409
|
-
"
|
|
345
|
+
"ENDED",
|
|
410
346
|
);
|
|
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;
|
|
347
|
+
for (const record of records) {
|
|
348
|
+
console.log(
|
|
349
|
+
record.id.padEnd(38) +
|
|
350
|
+
record.status.padEnd(10) +
|
|
351
|
+
shortInstant(record.started).padEnd(26) +
|
|
352
|
+
shortInstant(record.ended),
|
|
353
|
+
);
|
|
426
354
|
}
|
|
427
355
|
|
|
428
356
|
return 0;
|
|
429
357
|
}
|
|
430
358
|
|
|
431
|
-
// ── chant run
|
|
359
|
+
// ── chant run approve <op> <gate> ────────────────────────────────────────────
|
|
432
360
|
|
|
433
361
|
/**
|
|
434
|
-
* `chant run
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
362
|
+
* `chant run approve <op> <gate> [--approver] [--url] [--note]` (#2121) — the
|
|
363
|
+
* rename of `chant run signal`, and a different act.
|
|
364
|
+
*
|
|
365
|
+
* A gate resolution is a fact, not a message: the ledger write is the whole
|
|
366
|
+
* of it, and it is the same write `chant approve` performs
|
|
367
|
+
* (`recordGateApproval`, ./operator.ts), so a resolution recorded here reads
|
|
368
|
+
* back identically. The runtime is told afterwards, through its optional
|
|
369
|
+
* `resolveGate`, purely so a runtime that parks a run mid-flight can wake it
|
|
370
|
+
* now rather than on its next tick. A runtime without that method is not a
|
|
371
|
+
* failure — the fact is recorded either way, and the next run re-reads it.
|
|
441
372
|
*/
|
|
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>" }));
|
|
373
|
+
export async function runOpApprove(ctx: CommandContext): Promise<number> {
|
|
374
|
+
const opName = ctx.args.extraPositional;
|
|
375
|
+
const gate = ctx.args.extraPositional2;
|
|
376
|
+
if (!opName || !gate) {
|
|
377
|
+
console.error(formatError({ message: "Usage: chant run approve <op> <gate>" }));
|
|
449
378
|
return 1;
|
|
450
379
|
}
|
|
451
380
|
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
381
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
382
|
+
if (!runtime) return 1;
|
|
383
|
+
|
|
384
|
+
const outcome = await recordGateApproval(opName, gate, {
|
|
385
|
+
actor: ctx.args.approver ?? ctx.args.actor,
|
|
386
|
+
note: ctx.args.note,
|
|
387
|
+
url: ctx.args.url,
|
|
388
|
+
});
|
|
389
|
+
if (!outcome.ok) return 1;
|
|
390
|
+
|
|
391
|
+
if (!runtime.resolveGate) {
|
|
392
|
+
console.error(formatInfo(
|
|
393
|
+
`The "${runtime.name}" runtime reads the resolution when the op next runs — re-run \`chant run ${opName}\`.`,
|
|
394
|
+
));
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
462
398
|
try {
|
|
463
|
-
|
|
464
|
-
handle = client.workflow.getHandle(workflowId);
|
|
465
|
-
await handle.signal(signalName, ...(approver ? [{ approver }] : []));
|
|
399
|
+
await runtime.resolveGate(opName, gate, outcome.record);
|
|
466
400
|
} catch (err) {
|
|
467
|
-
console.error(
|
|
401
|
+
console.error(formatWarning({
|
|
402
|
+
message:
|
|
403
|
+
`The resolution is recorded, but the "${runtime.name}" runtime could not be woken: ` +
|
|
404
|
+
(err instanceof Error ? err.message : String(err)),
|
|
405
|
+
hint: `Re-run \`chant run ${opName} --on ${runtime.name}\` once the runtime is reachable.`,
|
|
406
|
+
}));
|
|
468
407
|
return 1;
|
|
469
408
|
}
|
|
470
409
|
|
|
471
|
-
console.error(formatSuccess(
|
|
472
|
-
`Signal "${signalName}" sent to ${ctx.args.components ? "component" : "Op"} "${name}"` +
|
|
473
|
-
(approver ? ` (approver: ${approver})` : ""),
|
|
474
|
-
));
|
|
410
|
+
console.error(formatSuccess(`Runtime "${runtime.name}" was notified of the resolution.`));
|
|
475
411
|
return 0;
|
|
476
412
|
}
|
|
477
413
|
|
|
414
|
+
/**
|
|
415
|
+
* `chant run signal` was renamed `chant run approve` (#2121). Registered so
|
|
416
|
+
* the old spelling says where the verb went instead of being read as an Op
|
|
417
|
+
* named "signal".
|
|
418
|
+
*/
|
|
419
|
+
export function runOpSignalRenamed(ctx: CommandContext): Promise<number> {
|
|
420
|
+
const opName = ctx.args.extraPositional ?? "<op>";
|
|
421
|
+
const gate = ctx.args.extraPositional2 ?? "<gate>";
|
|
422
|
+
console.error(formatError({
|
|
423
|
+
message: "`chant run signal` is now `chant run approve`",
|
|
424
|
+
hint: `A gate is resolved by recording the fact, not by sending a message: chant run approve ${opName} ${gate}`,
|
|
425
|
+
}));
|
|
426
|
+
return Promise.resolve(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
478
429
|
// ── chant run cancel <name> ───────────────────────────────────────────────────
|
|
479
430
|
|
|
480
|
-
/** `chant run cancel <name
|
|
431
|
+
/** `chant run cancel <name>` — asks the resolved runtime (#2121) to stop the Op's active run. */
|
|
481
432
|
export async function runOpCancel(ctx: CommandContext): Promise<number> {
|
|
482
|
-
if (!requireTemporalMode(ctx, "chant run cancel")) return 1;
|
|
483
433
|
const name = ctx.args.extraPositional;
|
|
484
434
|
if (!name) {
|
|
485
435
|
console.error(formatError({ message: "Op name is required: chant run cancel <name>" }));
|
|
@@ -488,65 +438,52 @@ export async function runOpCancel(ctx: CommandContext): Promise<number> {
|
|
|
488
438
|
|
|
489
439
|
if (!ctx.args.force) {
|
|
490
440
|
console.error(formatWarning({
|
|
491
|
-
message: `Cancelling "${name}" will stop the active
|
|
441
|
+
message: `Cancelling "${name}" will stop the active run`,
|
|
492
442
|
hint: "Use --force to confirm cancellation",
|
|
493
443
|
}));
|
|
494
444
|
return 1;
|
|
495
445
|
}
|
|
496
446
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
447
|
+
if (ctx.args.components) {
|
|
448
|
+
return refuseDurableComponentSubcommand(
|
|
449
|
+
"chant run cancel --components",
|
|
450
|
+
"A component run lives and dies with the `chant run --components` process — interrupt that instead.",
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return runOpCancelOnRuntime(ctx, name);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* `chant run cancel <name>` on the resolved runtime (#2121). `--force` is
|
|
459
|
+
* already checked by the caller, so a runtime only ever sees a confirmed
|
|
460
|
+
* request; a runtime with nothing to cancel says so in its own words and the
|
|
461
|
+
* command exits non-zero.
|
|
462
|
+
*/
|
|
463
|
+
async function runOpCancelOnRuntime(ctx: CommandContext, name: string): Promise<number> {
|
|
464
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
465
|
+
if (!runtime) return 1;
|
|
466
|
+
|
|
500
467
|
try {
|
|
501
|
-
|
|
502
|
-
handle = client.workflow.getHandle(workflowId);
|
|
503
|
-
await handle.cancel();
|
|
468
|
+
await runtime.cancel(name, { force: true });
|
|
504
469
|
} catch (err) {
|
|
505
470
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
506
471
|
return 1;
|
|
507
472
|
}
|
|
508
473
|
|
|
509
|
-
console.error(formatSuccess(`Cancellation requested for ${
|
|
474
|
+
console.error(formatSuccess(`Cancellation requested for Op "${name}" on the "${runtime.name}" runtime`));
|
|
510
475
|
return 0;
|
|
511
476
|
}
|
|
512
477
|
|
|
513
478
|
// ── chant run <name> — main command ───────────────────────────────────────────
|
|
514
479
|
|
|
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
480
|
/**
|
|
545
481
|
* `chant run <name>` dispatcher.
|
|
546
482
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
483
|
+
* Every run goes through the runtime resolved by `--on` (#2121); without it,
|
|
484
|
+
* core's built-in local provider runs the Op in this process, where a `gate`
|
|
485
|
+
* is decided against the gate ledger and ends the run pending approval
|
|
486
|
+
* (#2119), exit code 3.
|
|
550
487
|
*
|
|
551
488
|
* `chant run --components <name|all>` (#585) is a separate target: discovered
|
|
552
489
|
* `Component` declarations dispatched through the interpret driver
|
|
@@ -555,22 +492,19 @@ function renderProgress(opName: string, history: WorkflowHistoryRaw): void {
|
|
|
555
492
|
* runComponentGraph(ctx)` branch (../handlers/graph.ts).
|
|
556
493
|
*
|
|
557
494
|
* 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.
|
|
495
|
+
* before that dispatch, and #2116 retired `--report` on the Op path too: it
|
|
496
|
+
* rendered a past durable run's workflow history, which no runtime keeps any
|
|
497
|
+
* more. Both refuse rather than falling through to a real dispatch — the
|
|
498
|
+
* component case was observed live reaching an actual cloud shell-out.
|
|
564
499
|
*/
|
|
565
500
|
/**
|
|
566
501
|
* Pre-flight for `chant run <op> --sandbox` on an Op containing a `policyGate`
|
|
567
502
|
* step (chant #2003). `--sandbox` is a global flag, and `../main.ts` arms the
|
|
568
503
|
* process-wide policy latch off it for every command — so the gate's
|
|
569
504
|
* `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.
|
|
505
|
+
* maintainer ("This is a chant bug"), which is not an answer a user can act
|
|
506
|
+
* on. Refuse here instead, before anything runs, naming the combination and
|
|
507
|
+
* what to do about it.
|
|
574
508
|
*
|
|
575
509
|
* Keyed on the flag rather than on whether the project declares
|
|
576
510
|
* `lint.policies`: the flag is what arms the latch, and a gate that builds the
|
|
@@ -597,18 +531,14 @@ export async function runOp(ctx: CommandContext): Promise<number> {
|
|
|
597
531
|
return 1;
|
|
598
532
|
}
|
|
599
533
|
if (ctx.args.components) return runOpComponents(ctx);
|
|
600
|
-
if (ctx.args.
|
|
534
|
+
if (ctx.args.report) {
|
|
601
535
|
console.error(formatError({
|
|
602
|
-
message: "
|
|
603
|
-
hint: "
|
|
536
|
+
message: "`chant run --report` rendered a past durable run, which #2116 removed",
|
|
537
|
+
hint: "Run `chant run log <op>` for the runtime's own run history.",
|
|
604
538
|
}));
|
|
605
539
|
return 1;
|
|
606
540
|
}
|
|
607
|
-
|
|
608
|
-
if (!requireTemporalMode(ctx, "chant run --report")) return 1;
|
|
609
|
-
return runOpTemporal(ctx);
|
|
610
|
-
}
|
|
611
|
-
return ctx.args.temporal ? runOpTemporal(ctx) : runOpLocal(ctx);
|
|
541
|
+
return runOpOnRuntime(ctx);
|
|
612
542
|
}
|
|
613
543
|
|
|
614
544
|
// ── Auto-release recording post-run (#597) ──────────────────────────────────
|
|
@@ -689,28 +619,18 @@ async function recordAutoReleasesForRun(
|
|
|
689
619
|
// ── chant run --components <name|all> ────────────────────────────────────────
|
|
690
620
|
|
|
691
621
|
/**
|
|
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.
|
|
622
|
+
* `chant run --components <name|all> [--env <env>]` (#585) — the interpret
|
|
623
|
+
* driver's CLI entrypoint, and since #2116 the only component run path there
|
|
624
|
+
* is. `args.path` is the component name (or `all`), matching `chant run
|
|
625
|
+
* <name>`'s Op-dispatch convention exactly (`args.path` is the Op name there
|
|
626
|
+
* too) rather than a project directory — components are always discovered from
|
|
627
|
+
* the current working directory, the same way Op discovery (`discoverOps()`)
|
|
628
|
+
* never takes a project-path argument either.
|
|
699
629
|
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
* pointing at `--temporal`.
|
|
705
|
-
*
|
|
706
|
-
* `--temporal` (#589) takes the durable path instead: compiles the named
|
|
707
|
-
* component's composition to a Temporal workflow/worker (mirroring
|
|
708
|
-
* `Temporal::Op` codegen — see `runComponentTemporal` below), and gates are
|
|
709
|
-
* now ACCEPTED — a gate is durable wait-for-signal there, not a local
|
|
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").
|
|
630
|
+
* The selector resolves through the runtime's `runComponents` and runs on the
|
|
631
|
+
* local in-process driver; a `gate` is decided against the gate ledger when
|
|
632
|
+
* the driver reaches it (#2119), and one nobody has approved ends the run with
|
|
633
|
+
* exit code 3 and the `chant approve` line, exactly as `chant run <op>` does.
|
|
714
634
|
*
|
|
715
635
|
* On a successful run, auto-emits one release-ledger record per component
|
|
716
636
|
* that published a digest (#597, `recordAutoReleasesForRun` above) — a
|
|
@@ -723,8 +643,8 @@ async function recordAutoReleasesForRun(
|
|
|
723
643
|
* chant #1108 — resolves this invocation's declared build-time parameters
|
|
724
644
|
* (`chant.config.ts`'s `buildParams`, against `--param`/`--params-file`/a
|
|
725
645
|
* declared `env` mapping) the exact same way `chant build` does
|
|
726
|
-
* (`resolveCliBuildParams`, shared with `buildCommand`), BEFORE
|
|
727
|
-
*
|
|
646
|
+
* (`resolveCliBuildParams`, shared with `buildCommand`), BEFORE anything
|
|
647
|
+
* discovers/imports a `*.component.ts` file.
|
|
728
648
|
* Before this, `params.*` (`@intentius/chant/params`) was always `{}` under
|
|
729
649
|
* this command, no matter what a component's `chant.config.ts` declared or a
|
|
730
650
|
* CI job's environment supplied — see chant #1108. `chant.config.ts` is
|
|
@@ -742,6 +662,9 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
742
662
|
return 1;
|
|
743
663
|
}
|
|
744
664
|
|
|
665
|
+
const gatedExit = resolveGatedExitCode(ctx);
|
|
666
|
+
if (gatedExit === undefined) return 1;
|
|
667
|
+
|
|
745
668
|
const projectPath = resolve(".");
|
|
746
669
|
const { config } = await loadChantConfig(projectPath).catch(() => ({ config: {} as ChantConfig }));
|
|
747
670
|
const paramsResolution = resolveCliBuildParams(config.buildParams, {
|
|
@@ -754,7 +677,15 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
754
677
|
return 1;
|
|
755
678
|
}
|
|
756
679
|
|
|
757
|
-
|
|
680
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
681
|
+
if (!runtime) return 1;
|
|
682
|
+
if (!runtime.runComponents) {
|
|
683
|
+
console.error(formatError({
|
|
684
|
+
message: `--components is not supported on the "${runtime.name}" runtime`,
|
|
685
|
+
hint: "Omit --on to run components on the built-in local runtime.",
|
|
686
|
+
}));
|
|
687
|
+
return 1;
|
|
688
|
+
}
|
|
758
689
|
|
|
759
690
|
const env = ctx.args.env ?? "local";
|
|
760
691
|
// Seed cross-component/cross-stack outputs from upstream jobs' dumped files
|
|
@@ -778,7 +709,7 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
778
709
|
// `undefined` and every `onProgress?.(...)` call in the driver is a no-op —
|
|
779
710
|
// behavior is byte-for-byte unchanged from before this flag existed.
|
|
780
711
|
const onProgress = ctx.args.progressJson ? ndjsonProgressSink() : undefined;
|
|
781
|
-
const result = await runComponents(projectPath, selector, {
|
|
712
|
+
const result = await runtime.runComponents(projectPath, selector, {
|
|
782
713
|
env: ctx.args.env,
|
|
783
714
|
componentOutputs: seededOutputs,
|
|
784
715
|
onProgress,
|
|
@@ -794,17 +725,7 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
794
725
|
writeFileSync(dumpPath, JSON.stringify(result.run.componentOutputs, null, 2));
|
|
795
726
|
}
|
|
796
727
|
|
|
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) {
|
|
728
|
+
if (!result.success && !result.run && !result.gated) {
|
|
808
729
|
console.error(formatError({ message: result.error ?? "Failed to run component(s)" }));
|
|
809
730
|
return 1;
|
|
810
731
|
}
|
|
@@ -813,6 +734,29 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
813
734
|
if (ctx.args.json) renderDriverJson(result.run); else renderDriverHuman(result.run);
|
|
814
735
|
}
|
|
815
736
|
|
|
737
|
+
// Gated (#2119): the same fact-and-stop the Op path takes, and the same
|
|
738
|
+
// exit code. Nothing to release — the component stopped short of finishing.
|
|
739
|
+
if (result.gated) {
|
|
740
|
+
const { gate } = result.gated;
|
|
741
|
+
console.error(formatWarning({
|
|
742
|
+
message: `component "${result.gated.component}" is gated on "${gate.gate}" — pending approval`,
|
|
743
|
+
}));
|
|
744
|
+
console.error(formatInfo(`approve : ${approveCommand(gate.op, gate.gate)}`));
|
|
745
|
+
if (gate.url) console.error(formatInfo(`approve at: ${gate.url}`));
|
|
746
|
+
console.error(formatInfo(`expires : ${gate.expiresAt}`));
|
|
747
|
+
reportGatedRun(
|
|
748
|
+
{
|
|
749
|
+
op: gate.op,
|
|
750
|
+
gate: gate.gate,
|
|
751
|
+
...(gate.description ? { description: gate.description } : {}),
|
|
752
|
+
expiresAt: gate.expiresAt,
|
|
753
|
+
...(gate.url ? { url: gate.url } : {}),
|
|
754
|
+
},
|
|
755
|
+
gatedExit,
|
|
756
|
+
);
|
|
757
|
+
return gatedExit;
|
|
758
|
+
}
|
|
759
|
+
|
|
816
760
|
if (result.success && result.run) {
|
|
817
761
|
const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord);
|
|
818
762
|
await recordAutoReleasesForRun(result.run.results, env, `local-${Date.now()}`, disabled);
|
|
@@ -821,259 +765,31 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
821
765
|
return result.success ? 0 : 1;
|
|
822
766
|
}
|
|
823
767
|
|
|
824
|
-
// ── chant run --components <name> --temporal (#589) ─────────────────────────
|
|
825
|
-
|
|
826
|
-
function componentWorkflowId(componentName: string): string {
|
|
827
|
-
return `chant-component-${componentName}`;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
768
|
/**
|
|
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.
|
|
769
|
+
* `chant run <name>` on the resolved runtime (#2121) — the built-in `local`
|
|
770
|
+
* provider by default, a lexicon's `opRuntime` under `--on <name>`.
|
|
841
771
|
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
* `config`/`buildParams` are resolved once by the caller (`runOpComponents`,
|
|
850
|
-
* chant #1108) BEFORE this function runs, so `resolveComponentTargets`
|
|
851
|
-
* below — which discovers/imports the target `*.component.ts` file — sees
|
|
852
|
-
* `params.*` (`@intentius/chant/params`) already populated, the same
|
|
853
|
-
* guarantee the local executor path gets.
|
|
772
|
+
* This function knows nothing about how a run happens. It discovers the Op,
|
|
773
|
+
* runs the CLI-level pre-flights that are about flags rather than execution
|
|
774
|
+
* (`--sandbox` over a `policyGate` step, #2003), hands the config to the
|
|
775
|
+
* runtime, and renders whatever comes back. On the local runtime that is the
|
|
776
|
+
* executor's own `OpRunResult`, so `--json` and the human render are exactly
|
|
777
|
+
* what they were before the seam existed; a runtime that reports only a state
|
|
778
|
+
* gets the terser render instead.
|
|
854
779
|
*/
|
|
855
|
-
async function
|
|
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.
|
|
1066
|
-
*/
|
|
1067
|
-
export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
780
|
+
export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
1068
781
|
const opName = ctx.args.path;
|
|
1069
782
|
if (!opName || opName === ".") {
|
|
1070
783
|
console.error(formatError({
|
|
1071
784
|
message: "Op name is required: chant run <name>",
|
|
1072
|
-
hint: "Run `chant run list
|
|
785
|
+
hint: "Run `chant run list` to see available Ops",
|
|
1073
786
|
}));
|
|
1074
787
|
return 1;
|
|
1075
788
|
}
|
|
1076
789
|
|
|
790
|
+
const gatedExit = resolveGatedExitCode(ctx);
|
|
791
|
+
if (gatedExit === undefined) return 1;
|
|
792
|
+
|
|
1077
793
|
const { ops, errors } = await discoverOps();
|
|
1078
794
|
for (const err of errors) console.error(formatWarning({ message: err }));
|
|
1079
795
|
|
|
@@ -1094,32 +810,12 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1094
810
|
// Pre-flight: --sandbox cannot cover a policyGate step (#2003).
|
|
1095
811
|
if (refusesPolicyGateUnderSandbox(ctx, config, opName)) return 1;
|
|
1096
812
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (gate) {
|
|
1100
|
-
console.error(formatError({
|
|
1101
|
-
message: new LocalGateUnsupportedError(gate.signalName).message,
|
|
1102
|
-
}));
|
|
1103
|
-
return 1;
|
|
1104
|
-
}
|
|
813
|
+
const runtime = await resolveOpRuntime(ctx);
|
|
814
|
+
if (!runtime) return 1;
|
|
1105
815
|
|
|
1106
|
-
//
|
|
1107
|
-
//
|
|
1108
|
-
|
|
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
|
-
}
|
|
1115
|
-
|
|
1116
|
-
let activities, profiles;
|
|
1117
|
-
try {
|
|
1118
|
-
[activities, profiles] = await Promise.all([loadActivities(lexicons), loadProfiles()]);
|
|
1119
|
-
} catch (err) {
|
|
1120
|
-
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1121
|
-
return 1;
|
|
1122
|
-
}
|
|
816
|
+
// `--progress-json` streams one NDJSON StepRecord per settled step, fed by
|
|
817
|
+
// whatever the runtime reports through `progress`.
|
|
818
|
+
const progress = ctx.args.progressJson ? ndjsonProgressSink<StepRecord>() : undefined;
|
|
1123
819
|
|
|
1124
820
|
// Ctrl-C aborts in-flight activities (kills their child processes) instead of
|
|
1125
821
|
// orphaning them. The handler is removed in `finally` so it never leaks.
|
|
@@ -1131,18 +827,57 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1131
827
|
process.once("SIGINT", onSigint);
|
|
1132
828
|
|
|
1133
829
|
try {
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
830
|
+
const handle = await runtime.start(config, {
|
|
831
|
+
env: ctx.args.env,
|
|
832
|
+
...(ctx.args.profile !== undefined ? { profile: ctx.args.profile } : {}),
|
|
833
|
+
progress,
|
|
834
|
+
signal: controller.signal,
|
|
835
|
+
});
|
|
836
|
+
const status = await handle.result();
|
|
837
|
+
|
|
838
|
+
if (status.result) {
|
|
839
|
+
// The executor's own render already prints a gate, its approve line
|
|
840
|
+
// and its expiry (#2119).
|
|
841
|
+
if (ctx.args.json) renderJson(status.result); else renderHuman(status.result);
|
|
842
|
+
} else {
|
|
843
|
+
renderRuntimeStatus("Op", opName, runtime.name, status);
|
|
844
|
+
if (status.state === "gated" && status.gate) {
|
|
845
|
+
console.error(formatWarning({
|
|
846
|
+
message: `Op "${opName}" is waiting on gate "${status.gate.name}"`,
|
|
847
|
+
hint: `Record the resolution with: ${approveCommand(opName, status.gate.name)}`,
|
|
848
|
+
}));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Exit 3 for a gated run (#2119) — a distinct code so CI can tell
|
|
853
|
+
// "waiting on a human" from a broken op, and retry the one but not the
|
|
854
|
+
// other. `--gated-exit <code>` remaps that one outcome and nothing else
|
|
855
|
+
// (#2243).
|
|
856
|
+
if (status.state === "gated") {
|
|
857
|
+
// The local runtime carries the whole pending fact on its result; a
|
|
858
|
+
// runtime that reports only a state carries the gate's name alone.
|
|
859
|
+
const pending = status.result?.gate;
|
|
860
|
+
const gate = pending?.gate ?? status.gate?.name;
|
|
861
|
+
if (gate) {
|
|
862
|
+
reportGatedRun(
|
|
863
|
+
{
|
|
864
|
+
op: opName,
|
|
865
|
+
gate,
|
|
866
|
+
...(pending?.description ? { description: pending.description } : {}),
|
|
867
|
+
...(pending?.expiresAt ? { expiresAt: pending.expiresAt } : {}),
|
|
868
|
+
...(pending?.url ? { url: pending.url } : {}),
|
|
869
|
+
},
|
|
870
|
+
gatedExit,
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return gatedExit;
|
|
874
|
+
}
|
|
875
|
+
return status.state === "completed" ? 0 : 1;
|
|
1137
876
|
} catch (err) {
|
|
1138
877
|
if (err instanceof OpRunFailure) {
|
|
1139
878
|
if (ctx.args.json) renderJson(err.result); else renderHuman(err.result);
|
|
1140
879
|
return 1;
|
|
1141
880
|
}
|
|
1142
|
-
if (err instanceof LocalGateUnsupportedError) {
|
|
1143
|
-
console.error(formatError({ message: err.message }));
|
|
1144
|
-
return 1;
|
|
1145
|
-
}
|
|
1146
881
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1147
882
|
return 1;
|
|
1148
883
|
} finally {
|
|
@@ -1150,213 +885,12 @@ export async function runOpLocal(ctx: CommandContext): Promise<number> {
|
|
|
1150
885
|
}
|
|
1151
886
|
}
|
|
1152
887
|
|
|
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
888
|
// ── fallback ────────────────────────────────────────────────────────────────���─
|
|
1355
889
|
|
|
1356
890
|
export function runOpUnknown(ctx: CommandContext): Promise<number> {
|
|
1357
891
|
console.error(formatError({
|
|
1358
892
|
message: `Unknown run subcommand: ${ctx.args.extraPositional ?? ctx.args.path}`,
|
|
1359
|
-
hint: "Available: chant run <name>, run list, run status, run
|
|
893
|
+
hint: "Available: chant run <name>, run list, run status, run approve, run cancel, run log",
|
|
1360
894
|
}));
|
|
1361
895
|
return Promise.resolve(1);
|
|
1362
896
|
}
|