@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/op/cron.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron for an Op's own schedule (#2120) — validation at `Op()` construction
|
|
3
|
+
* and minute-granularity matching for `chant operator`'s per-op cadence.
|
|
4
|
+
*
|
|
5
|
+
* `OpConfig.schedule.cron` is runtime-neutral data: the github lexicon turns
|
|
6
|
+
* it into `on.schedule`, a hosting lexicon hands it to its own scheduler,
|
|
7
|
+
* `chant operator` reads it as that Op's tick cadence, and the local one-shot
|
|
8
|
+
* executor ignores it entirely. Nothing here talks to a scheduler.
|
|
9
|
+
*
|
|
10
|
+
* The validator a lexicon's own post-synth cron check imports rather than
|
|
11
|
+
* keeping its own copy:
|
|
12
|
+
* deliberately permissive, a pre-submission guard rather than a full parser,
|
|
13
|
+
* since the final word belongs to whichever scheduler runs the cron.
|
|
14
|
+
*
|
|
15
|
+
* {@link cronMatches} is stricter about what it can *evaluate* than
|
|
16
|
+
* {@link isValidCronExpression} is about what it accepts, so a field carrying
|
|
17
|
+
* a Quartz extension the evaluator does not implement (`L`, `W`, `#`, `?`)
|
|
18
|
+
* matches everything rather than nothing — an operator that ticks too often
|
|
19
|
+
* is a wasted converge, one that never ticks is a silent outage.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Very permissive cron field pattern — catches obvious syntax errors. */
|
|
23
|
+
const CRON_FIELD = /^[0-9*,/\-?LW#]+$/;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* True when `expr` looks like 5- or 6-field cron. Field-shape only: it does
|
|
27
|
+
* not check that a number is in range for its position, and it accepts the
|
|
28
|
+
* Quartz extensions (`L`, `W`, `#`, `?`) some schedulers take.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidCronExpression(expr: string): boolean {
|
|
31
|
+
const fields = expr.trim().split(/\s+/);
|
|
32
|
+
if (fields.length < 5 || fields.length > 6) return false;
|
|
33
|
+
return fields.every((f) => CRON_FIELD.test(f));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The message `Op()` throws with and a lexicon's post-synth cron check
|
|
38
|
+
* reports, so one wording covers both the build-time refusal and the
|
|
39
|
+
* post-synth diagnostic.
|
|
40
|
+
*/
|
|
41
|
+
export function cronSyntaxMessage(expr: string): string {
|
|
42
|
+
return `cron expression "${expr}" does not look like valid 5- or 6-field cron syntax`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A parsed cron field: either "matches anything" or the exact set it matches. */
|
|
46
|
+
type FieldSpec = { any: true } | { any: false; values: Set<number> };
|
|
47
|
+
|
|
48
|
+
const ANY: FieldSpec = { any: true };
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse one field into the set of values it matches. Anything the evaluator
|
|
52
|
+
* cannot make sense of — a Quartz extension, an inverted range, a zero step —
|
|
53
|
+
* degrades to {@link ANY} rather than throwing: this runs on an expression
|
|
54
|
+
* `Op()` already accepted, and refusing at tick time would strand the Op.
|
|
55
|
+
*/
|
|
56
|
+
function parseField(field: string, min: number, max: number): FieldSpec {
|
|
57
|
+
if (field === "*" || field === "?" || /[LW#]/.test(field)) return ANY;
|
|
58
|
+
|
|
59
|
+
const values = new Set<number>();
|
|
60
|
+
for (const part of field.split(",")) {
|
|
61
|
+
const [rangePart, stepPart] = part.split("/");
|
|
62
|
+
const step = stepPart === undefined ? 1 : Number(stepPart);
|
|
63
|
+
if (!Number.isInteger(step) || step < 1) return ANY;
|
|
64
|
+
|
|
65
|
+
let lo: number;
|
|
66
|
+
let hi: number;
|
|
67
|
+
if (rangePart === "*" || rangePart === "") {
|
|
68
|
+
lo = min;
|
|
69
|
+
hi = max;
|
|
70
|
+
} else if (rangePart.includes("-")) {
|
|
71
|
+
const [a, b] = rangePart.split("-");
|
|
72
|
+
lo = Number(a);
|
|
73
|
+
hi = Number(b);
|
|
74
|
+
} else {
|
|
75
|
+
lo = Number(rangePart);
|
|
76
|
+
// `5/10` means "from 5, every 10" — a bare `5` means only 5.
|
|
77
|
+
hi = stepPart === undefined ? lo : max;
|
|
78
|
+
}
|
|
79
|
+
if (!Number.isInteger(lo) || !Number.isInteger(hi) || lo > hi) return ANY;
|
|
80
|
+
|
|
81
|
+
for (let v = lo; v <= hi; v += step) values.add(v);
|
|
82
|
+
}
|
|
83
|
+
return values.size === 0 ? ANY : { any: false, values };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fieldMatches(spec: FieldSpec, value: number): boolean {
|
|
87
|
+
return spec.any || spec.values.has(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* True when `date` falls on a minute `expr` fires at, in the host's local
|
|
92
|
+
* time zone (the only clock the operator daemon has).
|
|
93
|
+
*
|
|
94
|
+
* A 6-field expression is read as Quartz's `second minute hour dom month dow`
|
|
95
|
+
* and the seconds field is dropped — nothing in chant ticks sub-minute.
|
|
96
|
+
* Day-of-month and day-of-week follow the usual cron rule: when both are
|
|
97
|
+
* restricted, either one matching is enough.
|
|
98
|
+
*/
|
|
99
|
+
export function cronMatches(expr: string, date: Date): boolean {
|
|
100
|
+
const fields = expr.trim().split(/\s+/);
|
|
101
|
+
const five = fields.length === 6 ? fields.slice(1) : fields;
|
|
102
|
+
if (five.length !== 5) return false;
|
|
103
|
+
|
|
104
|
+
const [minuteF, hourF, domF, monthF, dowF] = five;
|
|
105
|
+
if (!fieldMatches(parseField(minuteF, 0, 59), date.getMinutes())) return false;
|
|
106
|
+
if (!fieldMatches(parseField(hourF, 0, 23), date.getHours())) return false;
|
|
107
|
+
if (!fieldMatches(parseField(monthF, 1, 12), date.getMonth() + 1)) return false;
|
|
108
|
+
|
|
109
|
+
const dom = parseField(domF, 1, 31);
|
|
110
|
+
const dow = parseField(dowF, 0, 7);
|
|
111
|
+
const weekday = date.getDay();
|
|
112
|
+
const domOk = fieldMatches(dom, date.getDate());
|
|
113
|
+
const dowOk = fieldMatches(dow, weekday) || (weekday === 0 && fieldMatches(dow, 7));
|
|
114
|
+
|
|
115
|
+
if (dom.any && dow.any) return true;
|
|
116
|
+
if (dom.any) return dowOk;
|
|
117
|
+
if (dow.any) return domOk;
|
|
118
|
+
return domOk || dowOk;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The longest window {@link cronDueBetween} scans minute by minute. Past it
|
|
123
|
+
* the answer is "yes" without scanning: an operator that has not looked at an
|
|
124
|
+
* op for two days has certainly missed a fire of any cron worth putting on an
|
|
125
|
+
* Op, and the alternative — 100k iterations on a clock jump — buys nothing.
|
|
126
|
+
*/
|
|
127
|
+
const MAX_SCAN_MINUTES = 2 * 24 * 60;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* True when `expr` fires at some minute in `(after, upTo]` — what a round
|
|
131
|
+
* asks for an Op it last ticked at `after`. Level-triggered on purpose: a
|
|
132
|
+
* round that arrives late still ticks the Op it owes rather than waiting for
|
|
133
|
+
* the next exact match, and several missed fires collapse into the one tick a
|
|
134
|
+
* convergent verb needs.
|
|
135
|
+
*/
|
|
136
|
+
export function cronDueBetween(expr: string, after: Date, upTo: Date): boolean {
|
|
137
|
+
if (upTo.getTime() <= after.getTime()) return false;
|
|
138
|
+
|
|
139
|
+
const startMs = Math.floor(after.getTime() / 60_000) * 60_000 + 60_000;
|
|
140
|
+
if (startMs > upTo.getTime()) return false;
|
|
141
|
+
|
|
142
|
+
const spanMinutes = Math.floor((upTo.getTime() - startMs) / 60_000) + 1;
|
|
143
|
+
if (spanMinutes > MAX_SCAN_MINUTES) return true;
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < spanMinutes; i++) {
|
|
146
|
+
if (cronMatches(expr, new Date(startMs + i * 60_000))) return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
@@ -16,7 +16,8 @@ import { discoverOps } from "./discover";
|
|
|
16
16
|
* symlink and the walk compares resolved paths).
|
|
17
17
|
*/
|
|
18
18
|
let fakeGitRoot = "";
|
|
19
|
-
vi.mock("../runtime-adapter", () => ({
|
|
19
|
+
vi.mock("../runtime-adapter", async (importOriginal) => ({
|
|
20
|
+
...(await importOriginal<typeof import("../runtime-adapter")>()),
|
|
20
21
|
getRuntime: () => ({
|
|
21
22
|
spawn: async (cmd: string[]) =>
|
|
22
23
|
cmd[0] === "git" && cmd[1] === "rev-parse"
|
|
@@ -66,3 +67,106 @@ describe("discoverOps — the scan root is the project, not the checkout (#2058)
|
|
|
66
67
|
expect([...ops.keys()].sort()).toEqual(["mine", "root", "theirs"]);
|
|
67
68
|
});
|
|
68
69
|
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* #2171. An Op may be exported by name, not only as the file's default.
|
|
73
|
+
*
|
|
74
|
+
* Discovery used to read `mod.default` alone, which forced every runnable Op
|
|
75
|
+
* into the one export shape the fold path refuses, so an Op under a project's
|
|
76
|
+
* `sourceDir` cost that project its fold coverage. The default export is still
|
|
77
|
+
* accepted, unchanged; these cases are the widening around it.
|
|
78
|
+
*
|
|
79
|
+
* Same fake-git-root harness as the suite above: the Op files here are written
|
|
80
|
+
* as plain object literals with a `props` field, which is the shape discovery
|
|
81
|
+
* actually validates, so the fixtures stay free of the Op builders.
|
|
82
|
+
*/
|
|
83
|
+
describe("discoverOps: an Op may be exported by name (#2171)", () => {
|
|
84
|
+
const OP_VALUE = (name: string): string =>
|
|
85
|
+
`{ props: { name: ${JSON.stringify(name)}, overview: "t", phases: [{ name: "Run", steps: [] }] } }`;
|
|
86
|
+
|
|
87
|
+
let checkout: string;
|
|
88
|
+
const write = (file: string, body: string): void => {
|
|
89
|
+
writeFileSync(join(checkout, "ops", file), body);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
checkout = realpathSync(mkdtempSync(join(tmpdir(), "chant-op-named-")));
|
|
94
|
+
fakeGitRoot = checkout;
|
|
95
|
+
mkdirSync(join(checkout, "ops"), { recursive: true });
|
|
96
|
+
writeFileSync(join(checkout, "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => rmSync(checkout, { recursive: true, force: true }));
|
|
99
|
+
|
|
100
|
+
test("a named export is discovered", async () => {
|
|
101
|
+
write("named.op.ts", `export const deploy = ${OP_VALUE("deploy")};\n`);
|
|
102
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
103
|
+
expect(errors).toEqual([]);
|
|
104
|
+
expect([...ops.keys()]).toEqual(["deploy"]);
|
|
105
|
+
expect(ops.get("deploy")!.exportName).toBe("deploy");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("the default export still works, and reports itself as the default", async () => {
|
|
109
|
+
write("legacy.op.ts", `export default ${OP_VALUE("legacy")};\n`);
|
|
110
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
111
|
+
expect(errors).toEqual([]);
|
|
112
|
+
expect([...ops.keys()]).toEqual(["legacy"]);
|
|
113
|
+
expect(ops.get("legacy")!.exportName).toBe("default");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("one file may declare several Ops, and each is registered on its own", async () => {
|
|
117
|
+
write("many.op.ts", `export const a = ${OP_VALUE("alpha")};\nexport const b = ${OP_VALUE("beta")};\n`);
|
|
118
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
119
|
+
expect(errors).toEqual([]);
|
|
120
|
+
expect([...ops.keys()].sort()).toEqual(["alpha", "beta"]);
|
|
121
|
+
expect(ops.get("alpha")!.filePath).toBe(ops.get("beta")!.filePath);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("a default and a named Op in one file are both registered", async () => {
|
|
125
|
+
write("both.op.ts", `export default ${OP_VALUE("first")};\nexport const other = ${OP_VALUE("second")};\n`);
|
|
126
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
127
|
+
expect(errors).toEqual([]);
|
|
128
|
+
expect([...ops.keys()].sort()).toEqual(["first", "second"]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the same Op exported twice is one Op, not a self-collision", async () => {
|
|
132
|
+
write("aliased.op.ts", `const op = ${OP_VALUE("aliased")};\nexport default op;\nexport { op };\n`);
|
|
133
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
134
|
+
expect(errors).toEqual([]);
|
|
135
|
+
expect([...ops.keys()]).toEqual(["aliased"]);
|
|
136
|
+
expect(ops.get("aliased")!.exportName).toBe("default");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("exports that are not Ops are skipped in silence", async () => {
|
|
140
|
+
write("mixed.op.ts", `export const meta = { team: "infra" };\nexport const notQuite = { props: { name: "x" } };\nexport const real = ${OP_VALUE("real")};\n`);
|
|
141
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
142
|
+
expect(errors).toEqual([]);
|
|
143
|
+
expect([...ops.keys()]).toEqual(["real"]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("a file exporting no Op at all is the error, and names both accepted shapes", async () => {
|
|
147
|
+
write("empty.op.ts", `export const meta = { team: "infra" };\n`);
|
|
148
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
149
|
+
expect(ops.size).toBe(0);
|
|
150
|
+
expect(errors).toHaveLength(1);
|
|
151
|
+
expect(errors[0]).toContain("exports no Op");
|
|
152
|
+
expect(errors[0]).toContain("export default Op({...})");
|
|
153
|
+
expect(errors[0]).toContain("export const deploy = Op({...})");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("two files declaring the same Op name still collide", async () => {
|
|
157
|
+
write("one.op.ts", `export const a = ${OP_VALUE("same")};\n`);
|
|
158
|
+
write("two.op.ts", `export const b = ${OP_VALUE("same")};\n`);
|
|
159
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
160
|
+
expect(ops.size).toBe(1);
|
|
161
|
+
expect(errors).toHaveLength(1);
|
|
162
|
+
expect(errors[0]).toMatch(/^Duplicate Op name "same" in .+ and .+$/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("two Ops sharing a name inside one file collide, and the message says so", async () => {
|
|
166
|
+
write("clash.op.ts", `export const a = ${OP_VALUE("twice")};\nexport const b = ${OP_VALUE("twice")};\n`);
|
|
167
|
+
const { ops, errors } = await discoverOps({ cwd: checkout });
|
|
168
|
+
expect(ops.size).toBe(1);
|
|
169
|
+
expect(errors).toHaveLength(1);
|
|
170
|
+
expect(errors[0]).toContain(`Duplicate Op name "twice" declared twice in`);
|
|
171
|
+
});
|
|
172
|
+
});
|
package/src/op/discover.test.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import { discoverOps } from "./discover";
|
|
3
3
|
|
|
4
|
-
// Mock getRuntime to return git root pointing at the repo root
|
|
5
|
-
|
|
4
|
+
// Mock getRuntime to return git root pointing at the repo root. Partial, via
|
|
5
|
+
// `importOriginal`: the modules a discovered Op file pulls in reach the rest of
|
|
6
|
+
// this module (`moduleDir`, for the lint presets), and a wholesale replacement
|
|
7
|
+
// would make them fail to load rather than fail an assertion.
|
|
8
|
+
vi.mock("../runtime-adapter", async (importOriginal) => ({
|
|
9
|
+
...(await importOriginal<typeof import("../runtime-adapter")>()),
|
|
6
10
|
getRuntime: () => ({
|
|
7
11
|
spawn: async (cmd: string[]) => {
|
|
8
12
|
if (cmd[0] === "git" && cmd[1] === "rev-parse") {
|
package/src/op/discover.ts
CHANGED
|
@@ -7,6 +7,12 @@ import type { OpConfig } from "./types";
|
|
|
7
7
|
export interface DiscoveredOp {
|
|
8
8
|
config: OpConfig;
|
|
9
9
|
filePath: string;
|
|
10
|
+
/**
|
|
11
|
+
* The export this Op arrived on: `"default"`, or the named export's own
|
|
12
|
+
* name (#2171). Diagnostic only, and the reason a file may carry more than
|
|
13
|
+
* one Op without either becoming ambiguous.
|
|
14
|
+
*/
|
|
15
|
+
exportName: string;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export interface OpDiscoveryResult {
|
|
@@ -70,11 +76,71 @@ async function collectOpFiles(dir: string): Promise<string[]> {
|
|
|
70
76
|
return files;
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
/** The `OpConfig` behind an exported value, or `undefined` when the value is not an Op. An Op entity carries its config on `.props` (`./resource.ts`); `name` and `phases` are what every consumer of a discovered Op reads. */
|
|
80
|
+
function opConfigOf(value: unknown): OpConfig | undefined {
|
|
81
|
+
if (!value || typeof value !== "object") return undefined;
|
|
82
|
+
const config = (value as { props?: unknown }).props as OpConfig | undefined;
|
|
83
|
+
if (!config || typeof config.name !== "string" || !Array.isArray(config.phases)) return undefined;
|
|
84
|
+
return config;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Every Op a module exports, default first (#2171).
|
|
89
|
+
*
|
|
90
|
+
* Discovery used to read `mod.default` and nothing else, which made
|
|
91
|
+
* `export default op` the only shape a runnable Op could take. That is also
|
|
92
|
+
* the one export shape the fold path refuses (`../discovery/fold-import.ts`
|
|
93
|
+
* scans for it by name and falls the whole file back to run), so an Op living
|
|
94
|
+
* under a project's `sourceDir` cost that project its fold coverage, and took
|
|
95
|
+
* every file importing it down too. Accepting a named export removes the
|
|
96
|
+
* conflict without moving the fold's rule: the default export still works, so
|
|
97
|
+
* every Op file written against the documented shape keeps running unchanged.
|
|
98
|
+
*
|
|
99
|
+
* Non-Op exports are skipped in silence, not reported. An Op file is ordinary
|
|
100
|
+
* TypeScript and may export a helper, a config literal or a type alongside its
|
|
101
|
+
* Op; only a file exporting no Op at all is worth an error.
|
|
102
|
+
*
|
|
103
|
+
* A file may carry more than one Op, and each is registered on its own. Ops
|
|
104
|
+
* are keyed by their declared `config.name`, never by the export name or the
|
|
105
|
+
* file, so two Ops in one file are no more ambiguous to `chant run` than two
|
|
106
|
+
* Ops in two files, and the duplicate-name check in {@link discoverOps}
|
|
107
|
+
* covers both the same way. The same object exported twice (`export default
|
|
108
|
+
* op` alongside `export { op }`) is one Op, deduplicated by identity, not a
|
|
109
|
+
* self-collision.
|
|
110
|
+
*
|
|
111
|
+
* Export order is the module namespace's own: `default` first by construction
|
|
112
|
+
* here, then the named exports, which the ECMAScript specification requires a
|
|
113
|
+
* namespace object to enumerate in sorted order. So the discovered set is
|
|
114
|
+
* deterministic across runs and platforms.
|
|
115
|
+
*/
|
|
116
|
+
function opsExportedBy(mod: Record<string, unknown>): Array<{ exportName: string; config: OpConfig }> {
|
|
117
|
+
const found: Array<{ exportName: string; config: OpConfig }> = [];
|
|
118
|
+
const seen = new Set<unknown>();
|
|
119
|
+
|
|
120
|
+
const consider = (exportName: string, value: unknown): void => {
|
|
121
|
+
if (seen.has(value)) return;
|
|
122
|
+
const config = opConfigOf(value);
|
|
123
|
+
if (!config) return;
|
|
124
|
+
seen.add(value);
|
|
125
|
+
found.push({ exportName, config });
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
consider("default", mod.default);
|
|
129
|
+
for (const exportName of Object.keys(mod)) {
|
|
130
|
+
if (exportName === "default") continue;
|
|
131
|
+
consider(exportName, mod[exportName]);
|
|
132
|
+
}
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
135
|
+
|
|
73
136
|
/**
|
|
74
137
|
* Discover all Op definitions from `*.op.ts` files under the nearest chant
|
|
75
138
|
* project root (the directory holding `chant.config.ts`/`.json`, walking up
|
|
76
139
|
* from `cwd`), or under the git root when no config exists — see
|
|
77
140
|
* {@link findDiscoveryRoot} (#2058).
|
|
141
|
+
*
|
|
142
|
+
* An Op may be the file's default export or a named one, and a file may hold
|
|
143
|
+
* several. See {@link opsExportedBy} (#2171).
|
|
78
144
|
*/
|
|
79
145
|
export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryResult> {
|
|
80
146
|
const errors: string[] = [];
|
|
@@ -87,28 +153,30 @@ export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryR
|
|
|
87
153
|
|
|
88
154
|
for (const filePath of files) {
|
|
89
155
|
try {
|
|
90
|
-
const mod = await import(filePath)
|
|
91
|
-
const
|
|
156
|
+
const mod = (await import(filePath)) as Record<string, unknown>;
|
|
157
|
+
const exported = opsExportedBy(mod);
|
|
92
158
|
|
|
93
|
-
if (
|
|
94
|
-
errors.push(
|
|
159
|
+
if (exported.length === 0) {
|
|
160
|
+
errors.push(
|
|
161
|
+
`${filePath}: exports no Op. Expected \`export default Op({...})\` or a named export such as \`export const deploy = Op({...})\``,
|
|
162
|
+
);
|
|
95
163
|
continue;
|
|
96
164
|
}
|
|
97
165
|
|
|
98
|
-
const
|
|
166
|
+
for (const { exportName, config } of exported) {
|
|
167
|
+
const priorFile = nameToFile.get(config.name);
|
|
168
|
+
if (priorFile !== undefined) {
|
|
169
|
+
errors.push(
|
|
170
|
+
priorFile === filePath
|
|
171
|
+
? `Duplicate Op name "${config.name}" declared twice in ${filePath}`
|
|
172
|
+
: `Duplicate Op name "${config.name}" in ${filePath} and ${priorFile}`,
|
|
173
|
+
);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
99
176
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
continue;
|
|
177
|
+
nameToFile.set(config.name, filePath);
|
|
178
|
+
ops.set(config.name, { config, filePath, exportName });
|
|
103
179
|
}
|
|
104
|
-
|
|
105
|
-
if (nameToFile.has(config.name)) {
|
|
106
|
-
errors.push(`Duplicate Op name "${config.name}" in ${filePath} and ${nameToFile.get(config.name)}`);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
nameToFile.set(config.name, filePath);
|
|
111
|
-
ops.set(config.name, { config, filePath });
|
|
112
180
|
} catch (err) {
|
|
113
181
|
errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
114
182
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The duration-string parser the Op model uses everywhere a timeout is
|
|
3
|
+
* authored ("5m", "30s", "1h30m", "100ms"). Its own leaf module so both
|
|
4
|
+
* `./local-executor.ts` (activity start-to-close timeouts) and `./gate.ts`
|
|
5
|
+
* (a gate's pending-fact expiry) can reach it without importing each other.
|
|
6
|
+
* Re-exported from `./local-executor.ts`, which is where it lived and where
|
|
7
|
+
* every existing caller still imports it from.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Parse a duration string ("5m", "30s", "1h30m", "100ms") to milliseconds. Throws when nothing in `s` parses as a quantity+unit. */
|
|
11
|
+
export function parseDuration(s: string): number {
|
|
12
|
+
const units: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
|
|
13
|
+
let total = 0;
|
|
14
|
+
let matched = false;
|
|
15
|
+
for (const m of s.matchAll(/(\d+)(ms|s|m|h|d)/g)) {
|
|
16
|
+
total += Number(m[1]) * units[m[2]];
|
|
17
|
+
matched = true;
|
|
18
|
+
}
|
|
19
|
+
if (!matched) throw new Error(`unparseable duration: "${s}"`);
|
|
20
|
+
return total;
|
|
21
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - `receiptStaleness` (WatchOp's phase) is read-only: it reports absent and
|
|
12
12
|
* differing receipts as findings and never writes.
|
|
13
13
|
*/
|
|
14
|
-
import { describe, test, expect } from "vitest";
|
|
14
|
+
import { describe, test, expect, vi } from "vitest";
|
|
15
15
|
import { EffectReceipt, receiptExpectation, EXISTENCE_EXPECTATION } from "../effect-receipt";
|
|
16
16
|
import { INTRINSIC_MARKER, type Intrinsic } from "../intrinsic";
|
|
17
17
|
import { effect, phase } from "./builders";
|
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
} from "./receipt-store";
|
|
24
24
|
import type { ActivityFn, ActivityProfile } from "./activity-registry";
|
|
25
25
|
import type { OpConfig, ActivityStep } from "./types";
|
|
26
|
-
import { runOpLocally,
|
|
26
|
+
import { runOpLocally, OpRunFailure } from "./local-executor";
|
|
27
|
+
import { memoryGateLedgerPort } from "./gate";
|
|
27
28
|
|
|
28
29
|
function fakeIntrinsic(json: unknown): Intrinsic {
|
|
29
30
|
return {
|
|
@@ -57,7 +58,7 @@ describe("effect() builder", () => {
|
|
|
57
58
|
inputs: { file: "seed.sql", version: 3 },
|
|
58
59
|
});
|
|
59
60
|
expect(s.expectation).toBe(receiptExpectation(seeded));
|
|
60
|
-
expect(s.steps.map((n) => (n.kind === "activity" ? n.fn : n.
|
|
61
|
+
expect(s.steps.map((n) => (n.kind === "activity" ? n.fn : n.gate))).toEqual(["shellCmd"]);
|
|
61
62
|
});
|
|
62
63
|
|
|
63
64
|
test("existence receipts always get the constant expectation, reference inputs or not", () => {
|
|
@@ -82,7 +83,7 @@ describe("effect() builder", () => {
|
|
|
82
83
|
|
|
83
84
|
test("preserves authored order and nested gates", () => {
|
|
84
85
|
const s = effect(seeded, [
|
|
85
|
-
{ kind: "gate",
|
|
86
|
+
{ kind: "gate", gate: "approve-seed" },
|
|
86
87
|
step("shellCmd", { cmd: "seed" }),
|
|
87
88
|
]);
|
|
88
89
|
expect(s.steps.map((n) => n.kind)).toEqual(["gate", "activity"]);
|
|
@@ -187,7 +188,7 @@ describe("receiptActivities (the injectable store seam)", () => {
|
|
|
187
188
|
// ── Local executor: read-compare-run-write ────────────────────────────────────
|
|
188
189
|
|
|
189
190
|
const PROFILES: Record<string, ActivityProfile> = {
|
|
190
|
-
fastIdempotent: {
|
|
191
|
+
fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
|
|
191
192
|
};
|
|
192
193
|
|
|
193
194
|
function activityMap(
|
|
@@ -216,7 +217,7 @@ describe("runOpLocally — effect steps", () => {
|
|
|
216
217
|
const ran: string[] = [];
|
|
217
218
|
const activities = activityMap(store, { runSeed: async () => void ran.push("runSeed") });
|
|
218
219
|
const result = await runOpLocally(seedOp(), activities, PROFILES);
|
|
219
|
-
expect(result.
|
|
220
|
+
expect(result.status).toBe("ok");
|
|
220
221
|
expect(ran).toEqual([]);
|
|
221
222
|
expect(writes).toEqual([]);
|
|
222
223
|
// The read records the effect as already applied; the nested step is skipped.
|
|
@@ -235,7 +236,7 @@ describe("runOpLocally — effect steps", () => {
|
|
|
235
236
|
{ runSeed: async () => void order.push("runSeed") },
|
|
236
237
|
);
|
|
237
238
|
const result = await runOpLocally(seedOp(), activities, PROFILES);
|
|
238
|
-
expect(result.
|
|
239
|
+
expect(result.status).toBe("ok");
|
|
239
240
|
expect(order).toEqual(["runSeed", "write"]);
|
|
240
241
|
expect(writes).toEqual([{ name: "seeded", expectation: receiptExpectation(seeded) }]);
|
|
241
242
|
expect(result.records.map((r) => [r.fn, r.status])).toEqual([
|
|
@@ -262,7 +263,7 @@ describe("runOpLocally — effect steps", () => {
|
|
|
262
263
|
// then writes.
|
|
263
264
|
healthy = true;
|
|
264
265
|
const rerun = await runOpLocally(seedOp(), activities, PROFILES);
|
|
265
|
-
expect(rerun.
|
|
266
|
+
expect(rerun.status).toBe("ok");
|
|
266
267
|
expect(writes).toEqual([{ name: "seeded", expectation: receiptExpectation(seeded) }]);
|
|
267
268
|
});
|
|
268
269
|
|
|
@@ -282,19 +283,33 @@ describe("runOpLocally — effect steps", () => {
|
|
|
282
283
|
]);
|
|
283
284
|
});
|
|
284
285
|
|
|
285
|
-
test("a gate nested in an effect step
|
|
286
|
+
test("a gate nested in an effect step stops before the receipt is written (#2119)", async () => {
|
|
286
287
|
const config: OpConfig = {
|
|
287
288
|
name: "gated",
|
|
288
289
|
overview: "",
|
|
289
290
|
phases: [
|
|
290
|
-
phase("Seed", [effect(seeded, [{ kind: "gate",
|
|
291
|
+
phase("Seed", [effect(seeded, [{ kind: "gate", gate: "approve-seed" }, step("runSeed")])]),
|
|
291
292
|
],
|
|
292
293
|
};
|
|
293
|
-
expect(findGate(config)?.signalName).toBe("approve-seed");
|
|
294
294
|
const { store } = memStore();
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
const ran = vi.fn();
|
|
296
|
+
const result = await runOpLocally(
|
|
297
|
+
config,
|
|
298
|
+
activityMap(store, { runSeed: async () => { ran(); } }),
|
|
299
|
+
PROFILES,
|
|
300
|
+
undefined,
|
|
301
|
+
{ gates: memoryGateLedgerPort(), now: "2026-09-05T12:00:00.000Z" },
|
|
302
|
+
);
|
|
303
|
+
expect(result.status).toBe("gated");
|
|
304
|
+
expect(result.gate?.gate).toBe("approve-seed");
|
|
305
|
+
expect(ran).not.toHaveBeenCalled();
|
|
306
|
+
// The receipt is untouched, so the next run re-proposes the effect.
|
|
307
|
+
expect(result.records.map((r) => [r.fn, r.status])).toEqual([
|
|
308
|
+
["receiptRead", "ok"],
|
|
309
|
+
["gate:approve-seed", "skipped"],
|
|
310
|
+
["runSeed", "skipped"],
|
|
311
|
+
["receiptWrite", "skipped"],
|
|
312
|
+
]);
|
|
298
313
|
});
|
|
299
314
|
|
|
300
315
|
test("an effect step in a parallel phase is refused — read-compare-run-write is ordered", async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { exec } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import {
|
|
3
|
+
import { sleep } from "./activity-runtime";
|
|
4
4
|
|
|
5
5
|
const execAsync = promisify(exec);
|
|
6
6
|
|
|
@@ -158,7 +158,6 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
158
158
|
let ok = false;
|
|
159
159
|
while (Date.now() < deadline) {
|
|
160
160
|
if (signal?.aborted) throw new Error(`emulator "${name}" wait aborted`);
|
|
161
|
-
safeHeartbeat({ step: "emulatorUp", container: name });
|
|
162
161
|
try {
|
|
163
162
|
const res = await fetch(url, { signal });
|
|
164
163
|
if (res.ok && ready(await res.text())) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `signalName` -> `gate` rename (#2202) and the one-version bridge that
|
|
3
|
+
* keeps the old key readable through 0.59.0.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { gateName, usesDeprecatedGateKey, opUsesDeprecatedGateKey } from "./gate-name";
|
|
7
|
+
import type { OpConfig } from "./types";
|
|
8
|
+
|
|
9
|
+
describe("gateName", () => {
|
|
10
|
+
it("reads the new key", () => {
|
|
11
|
+
expect(gateName({ gate: "approve-prod" })).toBe("approve-prod");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("falls back to the deprecated key", () => {
|
|
15
|
+
expect(gateName({ signalName: "approve-prod" })).toBe("approve-prod");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("prefers the new key when a step somehow carries both", () => {
|
|
19
|
+
expect(gateName({ gate: "new", signalName: "old" })).toBe("new");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("usesDeprecatedGateKey", () => {
|
|
24
|
+
it("is true only when the new key is absent", () => {
|
|
25
|
+
expect(usesDeprecatedGateKey({ signalName: "old" })).toBe(true);
|
|
26
|
+
expect(usesDeprecatedGateKey({ gate: "new" })).toBe(false);
|
|
27
|
+
expect(usesDeprecatedGateKey({ gate: "new", signalName: "old" })).toBe(false);
|
|
28
|
+
expect(usesDeprecatedGateKey({})).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("opUsesDeprecatedGateKey", () => {
|
|
33
|
+
const op = (phases: OpConfig["phases"], onFailure?: OpConfig["phases"]): OpConfig =>
|
|
34
|
+
({ name: "x", overview: "x", phases, ...(onFailure ? { onFailure } : {}) }) as OpConfig;
|
|
35
|
+
|
|
36
|
+
it("finds a gate on the old key in a plain phase", () => {
|
|
37
|
+
expect(opUsesDeprecatedGateKey(op([{ name: "Approve", steps: [{ kind: "gate", signalName: "go" }] }]))).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("finds one nested inside an effect step", () => {
|
|
41
|
+
const config = op([
|
|
42
|
+
{
|
|
43
|
+
name: "Seed",
|
|
44
|
+
steps: [
|
|
45
|
+
{
|
|
46
|
+
kind: "effect",
|
|
47
|
+
receipt: { name: "seeded", effect: "db-seed", flavor: "hash", inputs: { file: "seed.sql" } },
|
|
48
|
+
steps: [{ kind: "gate", signalName: "go" }],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
expect(opUsesDeprecatedGateKey(config)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("finds one in an onFailure phase", () => {
|
|
57
|
+
expect(
|
|
58
|
+
opUsesDeprecatedGateKey(op([{ name: "Apply", steps: [] }], [{ name: "Undo", steps: [{ kind: "gate", signalName: "go" }] }])),
|
|
59
|
+
).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("is false for an Op on the new key", () => {
|
|
63
|
+
expect(opUsesDeprecatedGateKey(op([{ name: "Approve", steps: [{ kind: "gate", gate: "go" }] }]))).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|