@intentius/chant 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/catalog.d.ts +51 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +20 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +14 -8
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/lineage.d.ts +10 -0
- package/dist/audit/lineage.d.ts.map +1 -0
- package/dist/audit/prior-art.d.ts +385 -0
- package/dist/audit/prior-art.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +16 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +8 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +3 -0
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +15 -0
- package/dist/audit/secrets.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +46 -0
- package/dist/audit/terraform-state.d.ts.map +1 -0
- package/dist/build.d.ts +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/claimed-fields.d.ts +161 -0
- package/dist/claimed-fields.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +2 -2
- package/dist/cli/command-group.d.ts +14 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +17 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +2 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +82 -16
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -42
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts +28 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts +16 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resources/context.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +26 -9
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
- package/dist/components/auto-release.d.ts +10 -20
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +4 -4
- package/dist/components/cli-support.d.ts +20 -21
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +3 -3
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver-output.d.ts.map +1 -1
- package/dist/components/driver.d.ts +66 -36
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/manifest-persistence.d.ts +1 -9
- package/dist/components/manifest-persistence.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +6 -6
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +9 -15
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts +2 -2
- package/dist/deep-observation.d.ts +17 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/files.d.ts +4 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +33 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/config-wire.d.ts +2 -2
- package/dist/fold/foldable-helpers.d.ts +5 -1
- package/dist/fold/foldable-helpers.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +236 -0
- package/dist/fold-provenance.d.ts.map +1 -0
- package/dist/graph-ops.d.ts +1 -1
- package/dist/held-elsewhere.d.ts +85 -0
- package/dist/held-elsewhere.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts +3 -3
- package/dist/lexicon-config.d.ts +4 -4
- package/dist/lexicon-plugin-helpers.d.ts +1 -1
- package/dist/lexicon.d.ts +104 -14
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +13 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +133 -6
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +71 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +2 -2
- package/dist/lifecycle/run-ledger.d.ts +53 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +3 -3
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/config.d.ts +81 -2
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +55 -2
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +1 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +16 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
- package/dist/lint/rules/op/support.d.ts +12 -0
- package/dist/lint/rules/op/support.d.ts.map +1 -0
- package/dist/lint/suppressions.d.ts +108 -0
- package/dist/lint/suppressions.d.ts.map +1 -0
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/op/activities/activity-contracts.d.ts +54 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -0
- package/dist/op/activities/apply.d.ts +262 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +13 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +110 -0
- package/dist/op/activities/converge.d.ts.map +1 -0
- package/dist/op/activities/env-teardown.d.ts +76 -0
- package/dist/op/activities/env-teardown.d.ts.map +1 -0
- package/dist/op/activities/guard-validate.d.ts +64 -0
- package/dist/op/activities/guard-validate.d.ts.map +1 -0
- package/dist/op/activities/http-check.d.ts +34 -0
- package/dist/op/activities/http-check.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +31 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +89 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +7 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +28 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts +76 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +25 -18
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts +28 -0
- package/dist/op/activity-failure.d.ts.map +1 -0
- package/dist/op/activity-profiles.d.ts +118 -0
- package/dist/op/activity-profiles.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +22 -38
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +3 -14
- package/dist/op/activity-runtime.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -29
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +113 -0
- package/dist/op/composites/apply-op.d.ts.map +1 -0
- package/dist/op/composites/converge-op.d.ts +120 -0
- package/dist/op/composites/converge-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +26 -0
- package/dist/op/composites/index.d.ts.map +1 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
- package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
- package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/op/composites/reconcile-op.d.ts +57 -0
- package/dist/op/composites/reconcile-op.d.ts.map +1 -0
- package/dist/op/composites/watch-op.d.ts +60 -0
- package/dist/op/composites/watch-op.d.ts.map +1 -0
- package/dist/op/composites/workflow-audit-op.d.ts +57 -0
- package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/op/converge-rule.d.ts +12 -10
- package/dist/op/converge-rule.d.ts.map +1 -1
- package/dist/op/cron.d.ts +51 -0
- package/dist/op/cron.d.ts.map +1 -0
- package/dist/op/discover.d.ts +9 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/duration.d.ts +11 -0
- package/dist/op/duration.d.ts.map +1 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate.d.ts +90 -0
- package/dist/op/gate.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +9 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/index.d.ts +23 -7
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +114 -28
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +11 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +184 -0
- package/dist/op/op-ir.d.ts.map +1 -0
- package/dist/op/op-verb-class.d.ts +10 -2
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +30 -5
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/receipt-store.d.ts +1 -1
- package/dist/op/receipt-store.d.ts.map +1 -1
- package/dist/op/resource.d.ts +15 -1
- package/dist/op/resource.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +200 -0
- package/dist/op/runtime.d.ts.map +1 -0
- package/dist/op/runtimes/local.d.ts +29 -0
- package/dist/op/runtimes/local.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +15 -13
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/types.d.ts +79 -27
- package/dist/op/types.d.ts.map +1 -1
- package/dist/ownership.d.ts +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/audit/catalog.test.ts +79 -3
- package/src/audit/catalog.ts +111 -13
- package/src/audit/core.test.ts +26 -0
- package/src/audit/core.ts +61 -17
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +91 -15
- package/src/audit/lineage.ts +100 -0
- package/src/audit/prior-art.ts +153 -0
- package/src/audit/proof.test.ts +67 -0
- package/src/audit/proof.ts +70 -0
- package/src/audit/report-html.ts +9 -4
- package/src/audit/report-model.ts +16 -2
- package/src/audit/report.test.ts +33 -0
- package/src/audit/report.ts +5 -3
- package/src/audit/rules-doc.test.ts +34 -2
- package/src/audit/rules-doc.ts +82 -9
- package/src/audit/secrets.ts +15 -3
- package/src/audit/terraform-state.test.ts +144 -0
- package/src/audit/terraform-state.ts +131 -0
- package/src/build.ts +31 -1
- package/src/claimed-fields.test.ts +145 -0
- package/src/claimed-fields.ts +208 -0
- package/src/cli/build-params-cli.ts +2 -2
- package/src/cli/command-group.ts +14 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
- package/src/cli/commands/audit.test.ts +189 -11
- package/src/cli/commands/audit.ts +73 -27
- package/src/cli/commands/build.test.ts +292 -9
- package/src/cli/commands/build.ts +156 -24
- package/src/cli/commands/check-lexicon-docs.ts +1 -1
- package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
- package/src/cli/commands/check-lexicon-examples.ts +42 -10
- package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
- package/src/cli/commands/init-lexicon.test.ts +97 -2
- package/src/cli/commands/init-lexicon.ts +4 -2
- package/src/cli/commands/init.test.ts +35 -1
- package/src/cli/commands/init.ts +14 -3
- package/src/cli/commands/lint.test.ts +48 -0
- package/src/cli/commands/lint.ts +123 -0
- package/src/cli/commands/onboard.test.ts +75 -0
- package/src/cli/commands/onboard.ts +54 -23
- package/src/cli/handlers/build.test.ts +81 -1
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/graph.test.ts +3 -3
- package/src/cli/handlers/lifecycle.test.ts +195 -12
- package/src/cli/handlers/lifecycle.ts +123 -6
- package/src/cli/handlers/misc.ts +1 -1
- package/src/cli/handlers/operator.test.ts +183 -11
- package/src/cli/handlers/operator.ts +260 -55
- package/src/cli/handlers/run.test.ts +431 -1287
- package/src/cli/handlers/run.ts +365 -915
- package/src/cli/main.test.ts +23 -15
- package/src/cli/main.ts +88 -33
- package/src/cli/mcp/op-tools.ts +147 -88
- package/src/cli/mcp/resource-handlers.ts +10 -37
- package/src/cli/mcp/resources/context.ts +11 -12
- package/src/cli/mcp/server.test.ts +25 -27
- package/src/cli/mcp/server.ts +2 -2
- package/src/cli/registry.ts +26 -9
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.ts +1 -1
- package/src/components/README.md +3 -2
- package/src/components/auto-release.test.ts +4 -20
- package/src/components/auto-release.ts +17 -42
- package/src/components/capability.ts +4 -4
- package/src/components/cli-support.test.ts +10 -5
- package/src/components/cli-support.ts +52 -44
- package/src/components/component.schema.json +2 -2
- package/src/components/component.ts +3 -3
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +92 -22
- package/src/components/driver.ts +193 -83
- package/src/components/index.ts +1 -6
- package/src/components/manifest-persistence.test.ts +2 -20
- package/src/components/manifest-persistence.ts +4 -24
- package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
- package/src/components/run-progress.ts +6 -6
- package/src/components/verbs/run-agent.ts +11 -17
- package/src/components/verbs/verify.ts +2 -2
- package/src/deep-observation.test.ts +75 -0
- package/src/deep-observation.ts +48 -0
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.ts +105 -2
- package/src/discovery/param-deps.test.ts +112 -1
- package/src/discovery/param-deps.ts +193 -0
- package/src/discovery/sandbox/bundle.ts +15 -1
- package/src/discovery/sandbox/config-wire.test.ts +2 -2
- package/src/discovery/sandbox/config-wire.ts +2 -2
- package/src/fold/fold.test.ts +56 -0
- package/src/fold/foldable-helpers.ts +81 -1
- package/src/fold-provenance-exclusion.test.ts +186 -0
- package/src/fold-provenance.test.ts +495 -0
- package/src/fold-provenance.ts +441 -0
- package/src/graph-ops.test.ts +2 -2
- package/src/graph-ops.ts +1 -1
- package/src/held-elsewhere.test.ts +102 -0
- package/src/held-elsewhere.ts +114 -0
- package/src/index.ts +3 -0
- package/src/kubectl-context.ts +3 -3
- package/src/lexicon-config.test.ts +5 -5
- package/src/lexicon-config.ts +4 -4
- package/src/lexicon-plugin-helpers.ts +1 -1
- package/src/lexicon.ts +120 -14
- package/src/lifecycle/change-set.test.ts +49 -0
- package/src/lifecycle/change-set.ts +67 -0
- package/src/lifecycle/converge-ledger.ts +1 -1
- package/src/lifecycle/deep-diff.test.ts +267 -14
- package/src/lifecycle/deep-diff.ts +217 -14
- package/src/lifecycle/deep-observe.test.ts +3 -1
- package/src/lifecycle/deep-observe.ts +16 -6
- package/src/lifecycle/gate-ledger.test.ts +74 -2
- package/src/lifecycle/gate-ledger.ts +143 -36
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/release-ledger.ts +2 -2
- package/src/lifecycle/run-ledger.test.ts +268 -0
- package/src/lifecycle/run-ledger.ts +201 -0
- package/src/lifecycle/symptoms.ts +3 -3
- package/src/lint/config.test.ts +82 -1
- package/src/lint/config.ts +112 -3
- package/src/lint/policy.test.ts +1 -1
- package/src/lint/post-synth.test.ts +31 -0
- package/src/lint/post-synth.ts +54 -1
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
- package/src/lint/rules/comp/comp.test.ts +1 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
- package/src/lint/rules/comp/index.ts +3 -3
- package/src/lint/rules/op/index.ts +23 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
- package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
- package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
- package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
- package/src/lint/rules/op/support.ts +11 -0
- package/src/lint/suppressions.test.ts +163 -0
- package/src/lint/suppressions.ts +204 -0
- package/src/live-endpoint.ts +1 -1
- package/src/op/activities/activity-contracts.ts +66 -0
- package/src/op/activities/apply.test.ts +567 -0
- package/src/op/activities/apply.ts +662 -0
- package/src/op/activities/build.ts +26 -0
- package/src/op/activities/converge.test.ts +330 -0
- package/src/op/activities/converge.ts +449 -0
- package/src/op/activities/env-teardown.test.ts +187 -0
- package/src/op/activities/env-teardown.ts +171 -0
- package/src/op/activities/guard-validate.test.ts +139 -0
- package/src/op/activities/guard-validate.ts +223 -0
- package/src/op/activities/http-check.test.ts +48 -0
- package/src/op/activities/http-check.ts +55 -0
- package/src/op/activities/index.ts +119 -0
- package/src/op/activities/lexicon-upgrade.test.ts +492 -0
- package/src/op/activities/lexicon-upgrade.ts +673 -0
- package/src/op/activities/lifecycle.ts +85 -0
- package/src/op/activities/pipeline-audit.test.ts +67 -0
- package/src/op/activities/pipeline-audit.ts +201 -0
- package/src/op/activities/policy.test.ts +19 -0
- package/src/op/activities/policy.ts +37 -0
- package/src/op/activities/reconcile.test.ts +81 -0
- package/src/op/activities/reconcile.ts +187 -0
- package/src/op/activities/shell.ts +26 -0
- package/src/op/activities/teardown.ts +22 -0
- package/src/op/activities/util.ts +6 -0
- package/src/op/activities/wait.ts +69 -0
- package/src/op/activities/workflow-audit.test.ts +90 -0
- package/src/op/activities/workflow-audit.ts +203 -0
- package/src/op/activity-contract-registry.test.ts +98 -0
- package/src/op/activity-contract-registry.ts +114 -0
- package/src/op/activity-contract.test.ts +1 -1
- package/src/op/activity-contract.ts +37 -33
- package/src/op/activity-failure.ts +34 -0
- package/src/op/activity-profiles.test.ts +56 -0
- package/src/op/activity-profiles.ts +116 -0
- package/src/op/activity-registry.test.ts +37 -28
- package/src/op/activity-registry.ts +27 -56
- package/src/op/activity-runtime.ts +3 -47
- package/src/op/builders-exports.test.ts +0 -49
- package/src/op/builders-typed.test.ts +164 -0
- package/src/op/builders.ts +123 -39
- package/src/op/composites/apply-op.ts +208 -0
- package/src/op/composites/composites.test.ts +366 -0
- package/src/op/composites/converge-op.test.ts +168 -0
- package/src/op/composites/converge-op.ts +214 -0
- package/src/op/composites/index.ts +26 -0
- package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
- package/src/op/composites/lexicon-upgrade-op.ts +134 -0
- package/src/op/composites/pipeline-audit-op.test.ts +25 -0
- package/src/op/composites/pipeline-audit-op.ts +78 -0
- package/src/op/composites/reconcile-op.test.ts +51 -0
- package/src/op/composites/reconcile-op.ts +107 -0
- package/src/op/composites/watch-op.ts +108 -0
- package/src/op/composites/workflow-audit-op.test.ts +25 -0
- package/src/op/composites/workflow-audit-op.ts +88 -0
- package/src/op/converge-rule.ts +12 -10
- package/src/op/cron.test.ts +128 -0
- package/src/op/cron.ts +149 -0
- package/src/op/discover-root.test.ts +105 -1
- package/src/op/discover.test.ts +6 -2
- package/src/op/discover.ts +84 -16
- package/src/op/duration.ts +21 -0
- package/src/op/effect-step.test.ts +26 -11
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate.ts +155 -0
- package/src/op/generate-pipeline.test.ts +65 -3
- package/src/op/generate-pipeline.ts +19 -2
- package/src/op/index.ts +39 -6
- package/src/op/local-executor.test.ts +163 -34
- package/src/op/local-executor.ts +370 -102
- package/src/op/local-output.test.ts +95 -11
- package/src/op/local-output.ts +33 -4
- package/src/op/op-ir.test.ts +281 -0
- package/src/op/op-ir.ts +417 -0
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +26 -5
- package/src/op/operator.test.ts +175 -6
- package/src/op/operator.ts +63 -16
- package/src/op/receipt-store.ts +1 -1
- package/src/op/resource.ts +20 -2
- package/src/op/runtime.ts +203 -0
- package/src/op/runtimes/local.test.ts +181 -0
- package/src/op/runtimes/local.ts +212 -0
- package/src/op/step-output-ref.ts +15 -13
- package/src/op/types.ts +84 -24
- package/src/ownership.ts +1 -1
- package/src/provenance.test.ts +6 -0
- package/src/provenance.ts +21 -1
- package/src/serializer-walker.test.ts +42 -0
- package/src/serializer-walker.ts +21 -2
- package/dist/cli/handlers/op-progress.d.ts +0 -90
- package/dist/cli/handlers/op-progress.d.ts.map +0 -1
- package/dist/cli/handlers/run-client.d.ts +0 -127
- package/dist/cli/handlers/run-client.d.ts.map +0 -1
- package/dist/cli/handlers/run-report.d.ts +0 -5
- package/dist/cli/handlers/run-report.d.ts.map +0 -1
- package/dist/components/temporal-codegen-loader.d.ts +0 -27
- package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
- package/src/cli/handlers/op-progress.test.ts +0 -268
- package/src/cli/handlers/op-progress.ts +0 -248
- package/src/cli/handlers/run-client.test.ts +0 -82
- package/src/cli/handlers/run-client.ts +0 -219
- package/src/cli/handlers/run-report.test.ts +0 -62
- package/src/cli/handlers/run-report.ts +0 -122
- package/src/components/temporal-codegen-loader.ts +0 -41
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPS013: a step-output reference (`stepOutput()`/`activity().out`, chant
|
|
3
|
+
* #1290) must name a step that precedes it, in scope (main `phases`, not
|
|
4
|
+
* `onFailure` or nested inside an `EffectStep`), with a registered
|
|
5
|
+
* `ActivityContract` whose `returns` schema the referenced path resolves
|
|
6
|
+
* against. Moved from a hosting lexicon's own TMP013 to core in #2122 (epic
|
|
7
|
+
* #2114 sub-issue 6), so it fires on every project that declares an Op — a
|
|
8
|
+
* project on the local runtime with no lexicons configured included.
|
|
9
|
+
*
|
|
10
|
+
* Same shape as OPS012 (`./ops012-activity-contract.ts`): the generic walk
|
|
11
|
+
* lives in `@intentius/chant/op`'s `validateStepOutputRefs`, over the same
|
|
12
|
+
* contract map this file builds from core's own base-activity contracts
|
|
13
|
+
* (`../../../op/activities/activity-contracts.ts`, #2117). A producer
|
|
14
|
+
* step whose `fn` has no registered contract is flagged (unlike OPS012's
|
|
15
|
+
* args/outcomeAttribute checks, which skip an unregistered `fn` — a
|
|
16
|
+
* reference has nothing to validate against without one, so it can't be
|
|
17
|
+
* silently allowed the way an unchecked arg can). The purely structural half
|
|
18
|
+
* of this check (an unknown step id, or a reference into a later phase) is
|
|
19
|
+
* independent of the contract map's contents entirely — no lexicon needs to
|
|
20
|
+
* be configured, and no activity needs a registered contract at all, for it
|
|
21
|
+
* to fire.
|
|
22
|
+
*
|
|
23
|
+
* Cross-lexicon (chant #2101): an Op step may call an activity any
|
|
24
|
+
* configured lexicon contributes (`terraformPlan`, `k3sInstall`,
|
|
25
|
+
* `flyApply`), and since this check became core-owned it fires on every
|
|
26
|
+
* project that declares an Op. `ctx.activityContracts` carries the contracts
|
|
27
|
+
* every configured lexicon declared, resolved by `loadActivityContracts`
|
|
28
|
+
* (`../../../op/activity-contract-registry.ts`) the same way
|
|
29
|
+
* `loadActivities` resolves the implementations, and is merged over the
|
|
30
|
+
* static table below. A context that carries none — a hand-built one in a
|
|
31
|
+
* test, a caller that has not loaded any — falls back to that table alone,
|
|
32
|
+
* exactly as before.
|
|
33
|
+
*
|
|
34
|
+
* That merge is what makes this rule usable at all on an Op whose steps are
|
|
35
|
+
* a lexicon's: before it, `terraformPlan` feeding `terraformApply` failed
|
|
36
|
+
* `chant build` for the absence of a contract the terraform lexicon does in
|
|
37
|
+
* fact declare, which is why both terraform examples carrying an Op kept it
|
|
38
|
+
* outside `src/` where the example-build harness could not see it.
|
|
39
|
+
*
|
|
40
|
+
* This check is what makes it safe for a lexicon's own serializer to compile
|
|
41
|
+
* every reference it finds unconditionally: `chant build` blocks file output
|
|
42
|
+
* while an error-severity post-synth finding stands, so generated code
|
|
43
|
+
* referencing an unresolved step never reaches disk (a lexicon's own Op
|
|
44
|
+
* serializer also runs the scope-only subset of this check directly —
|
|
45
|
+
* `validateStepOutputRefScope` — as defense-in-depth, chant #1950 pre-merge
|
|
46
|
+
* review finding 2).
|
|
47
|
+
*
|
|
48
|
+
* Cross-contract type compatibility (chant #1950 pre-merge review, finding
|
|
49
|
+
* 3): this rule also compares a producer's declared return type at `path`
|
|
50
|
+
* against the consumer's declared arg type at the same position
|
|
51
|
+
* (`string`/`number`/`boolean`/`object`/`array` only) and flags a mismatch —
|
|
52
|
+
* a string-returning path feeding a number-typed arg, for example.
|
|
53
|
+
* Deliberately shallow: a union, enum, literal, `z.any()`/`z.unknown()`, a
|
|
54
|
+
* transform, or a reference sitting inside an array in `args` all bail
|
|
55
|
+
* silently rather than being reasoned about. See `@intentius/chant/op`'s
|
|
56
|
+
* `step-output-ref.ts` module doc for the full writeup of what this catches
|
|
57
|
+
* and what it defers (to #1288 Stage 2).
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "../../post-synth";
|
|
61
|
+
import { validateStepOutputRefs, mergeActivityContracts, type ActivityContract } from "../../../op";
|
|
62
|
+
import type { OpConfig } from "../../../op";
|
|
63
|
+
import * as contracts from "../../../op/activities/activity-contracts";
|
|
64
|
+
import { isOpEntity } from "./support";
|
|
65
|
+
|
|
66
|
+
const CONTRACTS: Map<string, ActivityContract> = new Map(
|
|
67
|
+
Object.values(contracts).map((c) => [c.name, c]),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const ops013: PostSynthCheck = {
|
|
71
|
+
id: "OPS013",
|
|
72
|
+
description: "A step-output reference must name an in-scope, preceding, contract-validated producer step",
|
|
73
|
+
|
|
74
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
75
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
76
|
+
const activeContracts = mergeActivityContracts(CONTRACTS, ctx.activityContracts);
|
|
77
|
+
|
|
78
|
+
for (const [entityKey, entity] of ctx.entities) {
|
|
79
|
+
if (!isOpEntity(entity)) continue;
|
|
80
|
+
const rec = entity as unknown as Record<string, unknown>;
|
|
81
|
+
|
|
82
|
+
const props = ((entity as { props?: Record<string, unknown> }).props ?? {}) as unknown as OpConfig;
|
|
83
|
+
if (typeof props.name !== "string" || !Array.isArray(props.phases)) continue;
|
|
84
|
+
|
|
85
|
+
for (const issue of validateStepOutputRefs(props, activeContracts)) {
|
|
86
|
+
diagnostics.push({
|
|
87
|
+
checkId: "OPS013",
|
|
88
|
+
severity: "error",
|
|
89
|
+
message: `Op "${issue.opName}", phase "${issue.phase}", step "${issue.fn}": ${issue.message}`,
|
|
90
|
+
entity: entityKey,
|
|
91
|
+
lexicon: typeof rec.lexicon === "string" ? rec.lexicon : undefined,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return diagnostics;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPS014 tests — ported from a hosting lexicon's own TMP014
|
|
3
|
+
* (#1484, moved to core by #2122).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from "vitest";
|
|
7
|
+
import type { PostSynthContext } from "../../post-synth";
|
|
8
|
+
import { DECLARABLE_MARKER } from "../../../declarable";
|
|
9
|
+
import { when, eq, gt, allOf, run, report } from "../../../op";
|
|
10
|
+
import type { ConvergeRule } from "../../../op";
|
|
11
|
+
import type { ConvergeSymptom } from "../../../lifecycle/symptoms";
|
|
12
|
+
import { ops014 } from "./ops014-converge-rule-refusals";
|
|
13
|
+
|
|
14
|
+
function makeEntity(entityType: string, props: Record<string, unknown>) {
|
|
15
|
+
return {
|
|
16
|
+
[DECLARABLE_MARKER]: true,
|
|
17
|
+
entityType,
|
|
18
|
+
lexicon: "chant",
|
|
19
|
+
kind: "resource",
|
|
20
|
+
props,
|
|
21
|
+
attributes: {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeCtxFromEntities(entities: Map<string, unknown>): PostSynthContext {
|
|
26
|
+
return {
|
|
27
|
+
outputs: new Map([["chant", ""]]),
|
|
28
|
+
entities: entities as Map<string, never>,
|
|
29
|
+
buildResult: {
|
|
30
|
+
outputs: new Map([["chant", ""]]),
|
|
31
|
+
entities: entities as Map<string, never>,
|
|
32
|
+
warnings: [],
|
|
33
|
+
errors: [],
|
|
34
|
+
sourceFileCount: 1,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function opEntity(name: string, steps: unknown[], entityType = "Chant::Op") {
|
|
40
|
+
return makeEntity(entityType, { name, overview: "test", phases: [{ name: "Phase", steps }] });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function convergeOpEntity(
|
|
44
|
+
name: string,
|
|
45
|
+
rules: ConvergeRule<ConvergeSymptom>[],
|
|
46
|
+
opts?: { dial?: "observe" | "reconcile" | "apply"; entityType?: string },
|
|
47
|
+
) {
|
|
48
|
+
return makeEntity(opts?.entityType ?? "Chant::Op", {
|
|
49
|
+
name,
|
|
50
|
+
overview: "test",
|
|
51
|
+
labels: { Converge: "true", Env: "staging", Dial: opts?.dial ?? "observe" },
|
|
52
|
+
phases: [
|
|
53
|
+
{ name: "Observe", steps: [{ kind: "activity", fn: "lifecycleDiff", args: { env: "staging" }, id: "diff" }] },
|
|
54
|
+
{ name: "Converge", steps: [{ kind: "activity", fn: "convergeTick", args: { rules } }] },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readOnlyOpEntity(name: string) {
|
|
60
|
+
return opEntity(name, [{ kind: "activity", fn: "lifecycleDiff", args: { env: "staging" } }]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mutatingOpEntity(name: string) {
|
|
64
|
+
return opEntity(name, [
|
|
65
|
+
{ kind: "activity", fn: "nativeApply", args: { target: "kubectl", env: "staging", output: "dist", deleteMode: "never" } },
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function destructiveOpEntity(name: string, opts?: { gated?: boolean }) {
|
|
70
|
+
const steps: unknown[] = [
|
|
71
|
+
{ kind: "activity", fn: "nativeApply", args: { target: "kubectl", env: "staging", output: "dist", deleteMode: "gated" } },
|
|
72
|
+
];
|
|
73
|
+
const phases = opts?.gated
|
|
74
|
+
? [
|
|
75
|
+
{ name: "Approve", steps: [{ kind: "gate", signalName: "approve-x" }] },
|
|
76
|
+
{ name: "Apply", steps },
|
|
77
|
+
]
|
|
78
|
+
: [{ name: "Apply", steps }];
|
|
79
|
+
return makeEntity("Chant::Op", { name, overview: "test", phases });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("OPS014: converge-rule-refusals", () => {
|
|
83
|
+
test("passes a well-formed rule table dispatching a read-only op under observe", () => {
|
|
84
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("watch"), {
|
|
85
|
+
id: "drift-watch",
|
|
86
|
+
why: "Re-check drift with a read-only observation.",
|
|
87
|
+
});
|
|
88
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
89
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "observe" })],
|
|
90
|
+
["watch", readOnlyOpEntity("watch")],
|
|
91
|
+
]));
|
|
92
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("passes a report-only rule table with no dispatch at all", () => {
|
|
96
|
+
const rule = when<ConvergeSymptom>(gt("adoptCount", 0), report("unowned resources present"), {
|
|
97
|
+
id: "adopt-report",
|
|
98
|
+
why: "Unowned resources are reported, never auto-claimed.",
|
|
99
|
+
});
|
|
100
|
+
const ctx = makeCtxFromEntities(new Map([["converge", convergeOpEntity("converge", [rule])]]));
|
|
101
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("errors when a rule has a blank why", () => {
|
|
105
|
+
const badRule = {
|
|
106
|
+
id: "no-why",
|
|
107
|
+
when: { kind: "field-comparison", field: "status", op: "eq", value: "drifted" },
|
|
108
|
+
then: { kind: "report", reason: "x" },
|
|
109
|
+
why: "",
|
|
110
|
+
} as unknown as ConvergeRule<ConvergeSymptom>;
|
|
111
|
+
const ctx = makeCtxFromEntities(new Map([["converge", convergeOpEntity("converge", [badRule])]]));
|
|
112
|
+
const diags = ops014.check(ctx);
|
|
113
|
+
expect(diags.some((d) => d.checkId === "OPS014" && d.message.includes("must carry its why"))).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("errors when a rule's predicate is malformed (outside the evaluable subset)", () => {
|
|
117
|
+
const badRule = {
|
|
118
|
+
id: "bad-predicate",
|
|
119
|
+
when: { kind: "not-a-real-predicate-kind" },
|
|
120
|
+
then: { kind: "report", reason: "x" },
|
|
121
|
+
why: "some reason",
|
|
122
|
+
} as unknown as ConvergeRule<ConvergeSymptom>;
|
|
123
|
+
const ctx = makeCtxFromEntities(new Map([["converge", convergeOpEntity("converge", [badRule])]]));
|
|
124
|
+
const diags = ops014.check(ctx);
|
|
125
|
+
expect(diags.some((d) => d.message.includes("outside the evaluable subset"))).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("errors when run() names an op that doesn't exist", () => {
|
|
129
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("does-not-exist"), { id: "drift-apply", why: "Re-apply on drift." });
|
|
130
|
+
const ctx = makeCtxFromEntities(new Map([["converge", convergeOpEntity("converge", [rule], { dial: "apply" })]]));
|
|
131
|
+
const diags = ops014.check(ctx);
|
|
132
|
+
expect(diags.some((d) => d.message.includes('unknown op "does-not-exist"'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("errors when a mutating op is dispatched under an observe dial", () => {
|
|
136
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("apply-staging"), { id: "drift-apply", why: "Re-apply on drift." });
|
|
137
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
138
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "observe" })],
|
|
139
|
+
["apply-staging", mutatingOpEntity("apply-staging")],
|
|
140
|
+
]));
|
|
141
|
+
const diags = ops014.check(ctx);
|
|
142
|
+
expect(diags.some((d) => d.message.includes("mutating") && d.message.includes('dial "observe"'))).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Finding A (#1954 pre-merge review): issue #1484's own Autonomy table
|
|
146
|
+
// gives `reconcile` × mutating "open PR", not "run directly" — v1 doesn't
|
|
147
|
+
// build the PR-opening channel, so this is refused rather than silently
|
|
148
|
+
// escalated to match `apply`'s authority.
|
|
149
|
+
test("errors when a mutating op is dispatched under a reconcile dial — reconcile's table answer is \"open PR\", not implemented in v1", () => {
|
|
150
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("apply-staging"), { id: "drift-apply", why: "Re-apply on drift." });
|
|
151
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
152
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "reconcile" })],
|
|
153
|
+
["apply-staging", mutatingOpEntity("apply-staging")],
|
|
154
|
+
]));
|
|
155
|
+
const diags = ops014.check(ctx);
|
|
156
|
+
expect(diags.some((d) => d.message.includes("mutating") && d.message.includes('dial "reconcile"') && d.message.includes("open PR"))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("passes a mutating dispatch under an apply dial", () => {
|
|
160
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("apply-staging"), { id: "drift-apply", why: "Re-apply on drift." });
|
|
161
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
162
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
163
|
+
["apply-staging", mutatingOpEntity("apply-staging")],
|
|
164
|
+
]));
|
|
165
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Finding B (#1954 pre-merge review): a destructive dispatch target is
|
|
169
|
+
// refused under every dial, `apply` included — a converge tick is
|
|
170
|
+
// unattended, so a destructive target needs a human's approval before
|
|
171
|
+
// it's attempted, not a gate the tick consults after already committing to
|
|
172
|
+
// running it.
|
|
173
|
+
test("errors when a destructive op is dispatched under a reconcile dial", () => {
|
|
174
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("prune-staging"), { id: "drift-prune", why: "Prune drifted resources." });
|
|
175
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
176
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "reconcile" })],
|
|
177
|
+
["prune-staging", destructiveOpEntity("prune-staging", { gated: true })],
|
|
178
|
+
]));
|
|
179
|
+
const diags = ops014.check(ctx);
|
|
180
|
+
expect(diags.some((d) => d.message.includes("destructive") && d.message.includes("refused in v1"))).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("errors when a destructive op is dispatched under apply and has no gate", () => {
|
|
184
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("prune-staging"), { id: "drift-prune", why: "Prune drifted resources." });
|
|
185
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
186
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
187
|
+
["prune-staging", destructiveOpEntity("prune-staging", { gated: false })],
|
|
188
|
+
]));
|
|
189
|
+
const diags = ops014.check(ctx);
|
|
190
|
+
expect(diags.some((d) => d.message.includes("destructive") && d.message.includes("refused in v1"))).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("errors when a destructive op is dispatched under apply even when the target is gated — approval must come before dispatch, not after", () => {
|
|
194
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("prune-staging"), { id: "drift-prune", why: "Prune drifted resources." });
|
|
195
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
196
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
197
|
+
["prune-staging", destructiveOpEntity("prune-staging", { gated: true })],
|
|
198
|
+
]));
|
|
199
|
+
const diags = ops014.check(ctx);
|
|
200
|
+
expect(diags.some((d) => d.message.includes("destructive") && d.message.includes("refused in v1"))).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Finding C (#1954 pre-merge review): adopt safety — "an unowned resource
|
|
204
|
+
// is reported, never auto-claimed" — is enforced, not just documented.
|
|
205
|
+
test("errors when a rule reads adoptCount and dispatches a mutating op", () => {
|
|
206
|
+
const rule = when<ConvergeSymptom>(gt("adoptCount", 0), run("apply-staging"), { id: "adopt-apply", why: "test" });
|
|
207
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
208
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
209
|
+
["apply-staging", mutatingOpEntity("apply-staging")],
|
|
210
|
+
]));
|
|
211
|
+
const diags = ops014.check(ctx);
|
|
212
|
+
expect(diags.some((d) => d.message.includes('reads "adoptCount"') && d.message.includes("mutating"))).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("errors when adoptCount is read inside a nested allOf/anyOf predicate that dispatches a mutating op", () => {
|
|
216
|
+
const rule = when<ConvergeSymptom>(
|
|
217
|
+
allOf(eq("status", "drifted"), gt("adoptCount", 0)),
|
|
218
|
+
run("apply-staging"),
|
|
219
|
+
{ id: "nested-adopt-apply", why: "test" },
|
|
220
|
+
);
|
|
221
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
222
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
223
|
+
["apply-staging", mutatingOpEntity("apply-staging")],
|
|
224
|
+
]));
|
|
225
|
+
const diags = ops014.check(ctx);
|
|
226
|
+
expect(diags.some((d) => d.message.includes('reads "adoptCount"'))).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("passes a rule that reads adoptCount and dispatches a read-only op", () => {
|
|
230
|
+
const rule = when<ConvergeSymptom>(gt("adoptCount", 0), run("watch"), { id: "adopt-watch", why: "test" });
|
|
231
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
232
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply" })],
|
|
233
|
+
["watch", readOnlyOpEntity("watch")],
|
|
234
|
+
]));
|
|
235
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("passes a rule that reads adoptCount and only reports (no dispatch at all)", () => {
|
|
239
|
+
const rule = when<ConvergeSymptom>(gt("adoptCount", 0), report("unowned resources present"), { id: "adopt-report", why: "test" });
|
|
240
|
+
const ctx = makeCtxFromEntities(new Map([["converge", convergeOpEntity("converge", [rule], { dial: "apply" })]]));
|
|
241
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("ignores an Op with no Converge label", () => {
|
|
245
|
+
const ctx = makeCtxFromEntities(new Map([["op", readOnlyOpEntity("op")]]));
|
|
246
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("ignores non-Op entities", () => {
|
|
250
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
251
|
+
["bucket", makeEntity("AWS::S3::Bucket", { name: "default" })],
|
|
252
|
+
]));
|
|
253
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("ignores an entity whose entityType isn't Chant::Op", () => {
|
|
257
|
+
const rule = when<ConvergeSymptom>(eq("status", "drifted"), run("does-not-exist"), { id: "drift-apply", why: "Re-apply on drift." });
|
|
258
|
+
const ctx = makeCtxFromEntities(new Map([
|
|
259
|
+
["converge", convergeOpEntity("converge", [rule], { dial: "apply", entityType: "Legacy::Op" })],
|
|
260
|
+
]));
|
|
261
|
+
expect(ops014.check(ctx)).toHaveLength(0);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPS014: converge-rule-refusals (#1484). Moved from a hosting lexicon's own
|
|
3
|
+
* TMP014 to core in #2122 (epic #2114 sub-issue 6), so it fires on every
|
|
4
|
+
* project that declares a `ConvergeOp`-shaped Op, not only one with that
|
|
5
|
+
* lexicon configured.
|
|
6
|
+
*
|
|
7
|
+
* Cross-Op build-time refusals for a `ConvergeOp`'s rule table — the checks
|
|
8
|
+
* `../../../op/composites/converge-op.ts`'s factory can't make on its own,
|
|
9
|
+
* since they need the *whole* discovered graph of `Chant::Op` entities
|
|
10
|
+
* (`PostSynthContext.entities`), not just the ConvergeOp instance under
|
|
11
|
+
* construction: a sibling `*.op.ts` file's Op may not exist yet at the
|
|
12
|
+
* moment a composite factory runs, the same reason OPS012/OPS013 exist as
|
|
13
|
+
* post-synth checks rather than builder-time throws.
|
|
14
|
+
*
|
|
15
|
+
* A `ConvergeOp`-produced Op is identified by its own
|
|
16
|
+
* `labels.Converge === "true"` marker (mirroring `ApplyOp`'s
|
|
17
|
+
* `{ Apply: "true" }` / `WatchOp`'s `{ Watch: "true" }`); its rule table is
|
|
18
|
+
* read back off the `convergeTick` activity step's `args.rules` (there is no
|
|
19
|
+
* dedicated `OpConfig` field for it — see `../../../op/composites/
|
|
20
|
+
* converge-op.ts`'s doc on why the rule table travels as a step's args
|
|
21
|
+
* instead of a bespoke config field).
|
|
22
|
+
*
|
|
23
|
+
* Flags (pre-merge review of #1954 tightened the last three to match issue
|
|
24
|
+
* #1484's own Autonomy table — see `converge-op.ts`'s module doc):
|
|
25
|
+
* - a rule with no (or a blank) `why` — every rule must carry its
|
|
26
|
+
* rationale (Accessible Ops factor III),
|
|
27
|
+
* - a rule whose predicate isn't in the evaluable subset
|
|
28
|
+
* (`isWellFormedPredicate`, re-validated here as the runtime backstop
|
|
29
|
+
* over a rule table not authored through `when()`),
|
|
30
|
+
* - a `run()` action naming an Op no declared Op entity has as its `name`,
|
|
31
|
+
* - a `run()` action dispatching a `mutating` Op under any dial other than
|
|
32
|
+
* `"apply"` — the issue's Autonomy table gives `reconcile` "open PR", not
|
|
33
|
+
* "run directly"; opening a PR is out of v1 scope (epic #1487's
|
|
34
|
+
* `onDrift`-channel open question), so `reconcile` refuses the mutating
|
|
35
|
+
* dispatch rather than silently escalating it to "run directly",
|
|
36
|
+
* - a `run()` action dispatching a `destructive` Op under any dial,
|
|
37
|
+
* unconditionally — a converge tick runs unattended, on a schedule, with
|
|
38
|
+
* no one watching it fire. A destructive dispatch needs a human's
|
|
39
|
+
* approval *before* it is attempted, not a gate the tick consults after
|
|
40
|
+
* it has already committed to running the op — and `run()` has no way to
|
|
41
|
+
* ask first. (This used to be argued from the local executor's inability
|
|
42
|
+
* to honor a gate at all; as of #2119 a gated op ends the run `gated`
|
|
43
|
+
* rather than being refused, so that argument no longer holds — the
|
|
44
|
+
* refusal stands on the unattended-approval ground instead.) Durable,
|
|
45
|
+
* pre-approved destructive dispatch from a tick is a future design's to
|
|
46
|
+
* own; v1 refuses the target rather than shipping a path that dispatches
|
|
47
|
+
* a destructive op with no human in the loop,
|
|
48
|
+
* - a rule whose predicate reads `adoptCount` while its action is `run()`
|
|
49
|
+
* against a `mutating` Op — adopt safety (never auto-claiming an unowned
|
|
50
|
+
* resource) is enforced here, not merely documented: a rule that reads
|
|
51
|
+
* the adopt signal has no business dispatching a mutation.
|
|
52
|
+
*/
|
|
53
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "../../post-synth";
|
|
54
|
+
import { classifyOpVerbClass, isWellFormedPredicate, predicateReferencesField } from "../../../op";
|
|
55
|
+
import type { ConvergeRule, OpConfig } from "../../../op";
|
|
56
|
+
import { CONVERGE_SYMPTOM_FIELDS, type ConvergeSymptom } from "../../../lifecycle/symptoms";
|
|
57
|
+
import { isOpEntity } from "./support";
|
|
58
|
+
|
|
59
|
+
function looksLikeOpProps(props: Record<string, unknown>): boolean {
|
|
60
|
+
return typeof props.name === "string" && Array.isArray(props.phases);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read a ConvergeOp's rule table back off its `convergeTick` step's `args.rules`. `undefined` if this doesn't look like a ConvergeOp-shaped Op (defensive — never crashes the build over an unexpected shape). */
|
|
64
|
+
function findConvergeRules(props: OpConfig): ConvergeRule<ConvergeSymptom>[] | undefined {
|
|
65
|
+
for (const phase of props.phases) {
|
|
66
|
+
for (const step of phase.steps) {
|
|
67
|
+
if (step.kind === "activity" && step.fn === "convergeTick") {
|
|
68
|
+
const rules = step.args?.rules;
|
|
69
|
+
return Array.isArray(rules) ? (rules as ConvergeRule<ConvergeSymptom>[]) : undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const ops014: PostSynthCheck = {
|
|
77
|
+
id: "OPS014",
|
|
78
|
+
description:
|
|
79
|
+
"A ConvergeOp rule table: run() must name a declared Op, a mutating dispatch requires dial apply (reconcile opens a PR in a future version, not implemented in v1), a destructive dispatch is refused outright in v1 (a converge tick is unattended and a destructive dispatch needs approval before it's attempted, not a gate consulted after the tick already committed to it), an adoptCount-reading rule may not dispatch a mutation, and every rule must carry its why",
|
|
80
|
+
|
|
81
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
82
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
83
|
+
|
|
84
|
+
const opsByName = new Map<string, OpConfig>();
|
|
85
|
+
for (const entity of ctx.entities.values()) {
|
|
86
|
+
if (!isOpEntity(entity)) continue;
|
|
87
|
+
const rawProps = ((entity as { props?: Record<string, unknown> }).props ?? {}) as Record<string, unknown>;
|
|
88
|
+
if (!looksLikeOpProps(rawProps)) continue;
|
|
89
|
+
const opProps = rawProps as unknown as OpConfig;
|
|
90
|
+
opsByName.set(opProps.name, opProps);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const [entityKey, entity] of ctx.entities) {
|
|
94
|
+
if (!isOpEntity(entity)) continue;
|
|
95
|
+
const rec = entity as unknown as Record<string, unknown>;
|
|
96
|
+
|
|
97
|
+
const rawProps = ((entity as { props?: Record<string, unknown> }).props ?? {}) as Record<string, unknown>;
|
|
98
|
+
if (!looksLikeOpProps(rawProps)) continue;
|
|
99
|
+
const props = rawProps as unknown as OpConfig;
|
|
100
|
+
if (props.labels?.Converge !== "true") continue;
|
|
101
|
+
|
|
102
|
+
const dial = props.labels?.Dial ?? "observe";
|
|
103
|
+
const rules = findConvergeRules(props);
|
|
104
|
+
if (!rules) continue;
|
|
105
|
+
|
|
106
|
+
for (const rule of rules) {
|
|
107
|
+
const label = typeof rule?.id === "string" ? rule.id : "(unnamed rule)";
|
|
108
|
+
const push = (message: string): void => {
|
|
109
|
+
diagnostics.push({
|
|
110
|
+
checkId: "OPS014",
|
|
111
|
+
severity: "error",
|
|
112
|
+
message: `ConvergeOp "${props.name}", rule "${label}": ${message}`,
|
|
113
|
+
entity: entityKey,
|
|
114
|
+
lexicon: typeof rec.lexicon === "string" ? rec.lexicon : undefined,
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (typeof rule?.why !== "string" || rule.why.trim().length === 0) {
|
|
119
|
+
push("every rule must carry its why — refused at build");
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!isWellFormedPredicate(rule.when, CONVERGE_SYMPTOM_FIELDS)) {
|
|
123
|
+
push("predicate is outside the evaluable subset (build it from eq/neq/gt/gte/lt/lte/truthy/falsy/allOf/anyOf over a known symptom field)");
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!rule.then || rule.then.kind !== "run") continue;
|
|
127
|
+
|
|
128
|
+
const targetName = rule.then.op;
|
|
129
|
+
const target = opsByName.get(targetName);
|
|
130
|
+
if (!target) {
|
|
131
|
+
push(`references unknown op "${targetName}" — no declared Op has that name`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const verbClass = classifyOpVerbClass(target);
|
|
136
|
+
|
|
137
|
+
// Mutating: the issue's Autonomy table gives `reconcile` "open PR",
|
|
138
|
+
// not "run directly" — building that channel is out of v1 scope
|
|
139
|
+
// (epic #1487's onDrift-channel open question), so only `apply`
|
|
140
|
+
// (the dial the issue's table marks "run, gated per op") may free-run
|
|
141
|
+
// a mutating dispatch. Refusing this at build is the honest
|
|
142
|
+
// alternative to silently escalating `reconcile`'s authority to
|
|
143
|
+
// match `apply`.
|
|
144
|
+
if (verbClass === "mutating" && dial !== "apply") {
|
|
145
|
+
const escalationNote =
|
|
146
|
+
dial === "reconcile"
|
|
147
|
+
? ` — the issue's table answer for "reconcile" is "open PR", which v1 does not implement (see epic #1487's onDrift-channel open question); this Op would otherwise silently run a mutation "reconcile" was never granted`
|
|
148
|
+
: "";
|
|
149
|
+
push(`dispatches "${targetName}" (mutating), but dial "${dial}" only permits a mutating dispatch under "apply"${escalationNote} — report instead, or raise the dial to "apply"`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Destructive: refused outright, regardless of dial or gate. A
|
|
153
|
+
// converge tick runs unattended on a schedule — nothing is watching
|
|
154
|
+
// it fire — so a destructive dispatch needs a human's approval
|
|
155
|
+
// *before* it is attempted, not a gate the tick consults only after
|
|
156
|
+
// it has already committed to running the op. `run()` has no way to
|
|
157
|
+
// ask first, so the target is refused rather than dispatched.
|
|
158
|
+
if (verbClass === "destructive") {
|
|
159
|
+
push(
|
|
160
|
+
`dispatches "${targetName}" (destructive) — destructive run() targets are refused in v1 under any dial: a converge tick is unattended, and a destructive dispatch needs a human's approval before it is attempted, not a gate consulted after the tick has already committed to running it. Destructive remediation via ConvergeOp arrives once a tick can ask for that approval before it dispatches — until then, remediate manually via a gated ApplyOp/ReconcileOp run.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Adopt safety: enforced, not just documented. A rule that reads
|
|
165
|
+
// `adoptCount` — the "unowned resource" signal — has no honest
|
|
166
|
+
// reason to dispatch a mutation; the only sanctioned action for that
|
|
167
|
+
// signal is `report()`. (A destructive target already gets refused
|
|
168
|
+
// above regardless of what its predicate reads.)
|
|
169
|
+
if (verbClass === "mutating" && predicateReferencesField(rule.when, "adoptCount")) {
|
|
170
|
+
push(
|
|
171
|
+
`predicate reads "adoptCount" but dispatches "${targetName}" (mutating) — an unowned resource is reported, never auto-claimed by a dispatched mutation; use report() for a rule that reads the adopt signal`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return diagnostics;
|
|
178
|
+
},
|
|
179
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared support for the OPS* Op-model post-synth checks (#2122, epic #2114
|
|
3
|
+
* sub-issue 6) — OPS012 (`./ops012-activity-contract.ts`), OPS013
|
|
4
|
+
* (`./ops013-step-output-ref.ts`) and OPS014
|
|
5
|
+
* (`./ops014-converge-rule-refusals.ts`), ported here from a hosting
|
|
6
|
+
* lexicon's own TMP012/TMP013/TMP014.
|
|
7
|
+
*
|
|
8
|
+
* Op-entity recognition is core's own `isOpEntity` (`../../../op/resource.ts`,
|
|
9
|
+
* #2118) — re-exported here so every OPS* check imports it from one place.
|
|
10
|
+
*/
|
|
11
|
+
export { isOpEntity } from "../../../op/resource";
|