@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,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `convergeTick` — the activity a `ConvergeOp`-generated Op's Converge
|
|
3
|
+
* phase runs (../../composites/converge-op.ts). One call is one tick:
|
|
4
|
+
* observe (shell to `chant lifecycle plan`/`chant components status`, the
|
|
5
|
+
* same CLI surface `reconcilePr`/`lifecycleDiff` already shell to — see
|
|
6
|
+
* ./reconcile.ts, ./lifecycle.ts) -> classify (the pure rule table
|
|
7
|
+
* evaluator, core's `evaluatePredicate`, then this
|
|
8
|
+
* activity's own runtime backstop re-classifying each dispatch target's verb
|
|
9
|
+
* class before it runs — see `verbClassAllowedToDispatch` below) -> dispatch
|
|
10
|
+
* within budget (`chant run <op>`, the existing local runner — a gated
|
|
11
|
+
* dispatched Op ends `gated` with its pending fact on the ledger, exit code 3,
|
|
12
|
+
* which this tick turns into a `"gated"` outcome) -> record (one line to the
|
|
13
|
+
* converge ledger, `../../lifecycle/converge-ledger`).
|
|
14
|
+
*
|
|
15
|
+
* Deliberately monolithic, matching `reconcilePr`'s shape (one activity that
|
|
16
|
+
* derives the change set, regenerates, and opens the PR) rather than
|
|
17
|
+
* `ApplyOp`'s Build/Plan/Approve/Apply phase split — a converge tick's
|
|
18
|
+
* "classify against a rule table, then dispatch" step can't be decomposed
|
|
19
|
+
* into separate declared steps, because *which* rules fire is a runtime fact
|
|
20
|
+
* discovered only once live symptoms are read, while an Op's phase structure
|
|
21
|
+
* is fully static at build time. Splitting `Observe` into its own phase (the
|
|
22
|
+
* composite still does, for the per-phase visibility WatchOp/ApplyOp/
|
|
23
|
+
* ReconcileOp all get) and threading its result into this activity's `args` via a
|
|
24
|
+
* step-output reference (#1290) rather than a search-attribute round-trip is
|
|
25
|
+
* the seam this module *does* use the new dataflow for.
|
|
26
|
+
*
|
|
27
|
+
* `unknown` never remediates: whatever a rule's own predicate says, a tick
|
|
28
|
+
* whose derived symptom is `status: "unknown"` forces every fired `run`
|
|
29
|
+
* action to `report` instead — the honesty requirement is enforced here, at
|
|
30
|
+
* the one place a bad rule table can't route around it, not only trusted to
|
|
31
|
+
* rule authors.
|
|
32
|
+
*/
|
|
33
|
+
import { exec } from "node:child_process";
|
|
34
|
+
import { promisify } from "node:util";
|
|
35
|
+
import {
|
|
36
|
+
evaluatePredicate,
|
|
37
|
+
type ConvergeRule,
|
|
38
|
+
type RuleAction,
|
|
39
|
+
DEFAULT_FLAP_THRESHOLD,
|
|
40
|
+
} from "../converge-rule";
|
|
41
|
+
import { classifyOpVerbClass, type OpVerbClass } from "../op-verb-class";
|
|
42
|
+
import { discoverOps } from "../discover";
|
|
43
|
+
import { deriveSymptoms, type ConvergeSymptom } from "../../lifecycle/symptoms";
|
|
44
|
+
import {
|
|
45
|
+
appendConvergeRecord,
|
|
46
|
+
readConvergeLedger,
|
|
47
|
+
consecutiveRuleFires,
|
|
48
|
+
componentVerdicts,
|
|
49
|
+
sanitizeLedgerText,
|
|
50
|
+
type ConvergeRuleOutcome,
|
|
51
|
+
type ConvergeTickRecord,
|
|
52
|
+
} from "../../lifecycle/converge-ledger";
|
|
53
|
+
import { resolveApprovalUrl } from "../../lifecycle/gate-ledger";
|
|
54
|
+
import { fetchLifecycle, pushLifecycle } from "../../lifecycle/git";
|
|
55
|
+
import type { ChangeSet } from "../../lifecycle/change-set";
|
|
56
|
+
import type { ComponentStatusRow } from "../../lifecycle/status";
|
|
57
|
+
|
|
58
|
+
const execAsync = promisify(exec);
|
|
59
|
+
|
|
60
|
+
/** A `ConvergeRule<ConvergeSymptom>`, restated with `S = ConvergeSymptom` fixed — the JSON shape `ConvergeOp` bakes into the Op's `convergeTick` step args. */
|
|
61
|
+
export type SerializedConvergeRule = ConvergeRule<ConvergeSymptom>;
|
|
62
|
+
|
|
63
|
+
export interface ConvergeTickArgs {
|
|
64
|
+
/** Op name (`ConvergeOp`'s own `config.name`) — carried into the ledger record. */
|
|
65
|
+
opName: string;
|
|
66
|
+
env: string;
|
|
67
|
+
/**
|
|
68
|
+
* Authority level for this environment (the issue's dial × verb-class
|
|
69
|
+
* matrix): `"observe"` never dispatches a mutating/destructive op
|
|
70
|
+
* (report-only); `"reconcile"` free-runs a read-only op but never a
|
|
71
|
+
* mutating one — the issue's table answer for `reconcile` × mutating is
|
|
72
|
+
* "open PR", which v1 doesn't implement (epic #1487's onDrift-channel open
|
|
73
|
+
* question), so `reconcile` refuses that dispatch (`OPS014`, build time)
|
|
74
|
+
* rather than silently escalating it to "run directly"; `"apply"` is the
|
|
75
|
+
* only dial that free-runs a mutating op. A destructive op is refused
|
|
76
|
+
* under every dial, `"apply"` included, in v1 — see `OPS014`'s doc on why
|
|
77
|
+
* "destructive + apply + gated" can never actually dispatch.
|
|
78
|
+
*/
|
|
79
|
+
dial: "observe" | "reconcile" | "apply";
|
|
80
|
+
/** Max number of `run` dispatches this tick may perform; remaining matched rules are recorded `skipped-budget`. */
|
|
81
|
+
budget: number;
|
|
82
|
+
/** The rule table, in authored order. */
|
|
83
|
+
rules: SerializedConvergeRule[];
|
|
84
|
+
/**
|
|
85
|
+
* `Observe` phase's quick drift signal, threaded in via a step-output
|
|
86
|
+
* reference rather than a search-attribute round-trip (#1290) — a
|
|
87
|
+
* diagnostic cross-check folded into the tick record, not a control input
|
|
88
|
+
* (this activity re-derives its own symptom independently).
|
|
89
|
+
*/
|
|
90
|
+
preflightDrift?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ConvergeTickResult {
|
|
94
|
+
/** The ledger record's tick id (#2027) — what a caller correlates this tick's outcomes, gate facts and remediations against. */
|
|
95
|
+
id: string;
|
|
96
|
+
drifted: boolean;
|
|
97
|
+
remediated: number;
|
|
98
|
+
reported: number;
|
|
99
|
+
skippedBudget: number;
|
|
100
|
+
skippedFlap: number;
|
|
101
|
+
unobserved: number;
|
|
102
|
+
adopted: number;
|
|
103
|
+
/** Rules whose dispatch hit a gate this tick (#1485) — see `ConvergeRuleOutcome.action`'s doc. */
|
|
104
|
+
gated: number;
|
|
105
|
+
/** The one human-readable summary line this tick produced. */
|
|
106
|
+
log: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function shellQuote(s: string): string {
|
|
110
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Cap a subprocess failure's raw output to one sanitized line — a ledger
|
|
115
|
+
* record (`converge-ledger.ts`'s `appendConvergeRecord`) is one line of JSON,
|
|
116
|
+
* and a multi-line/oversized `stderr` folded straight into `outcome.reason`
|
|
117
|
+
* would break that. Not a fix for the ledger-durability bug (#1936,
|
|
118
|
+
* `writeBlobToPath`'s corruption under concurrent writes — fixed at the root
|
|
119
|
+
* in a separate PR); this only keeps one dispatch failure's own message from
|
|
120
|
+
* corrupting the line it's written into.
|
|
121
|
+
*
|
|
122
|
+
* The rule now lives next to the record it protects (`sanitizeLedgerText`,
|
|
123
|
+
* #2027), since a component verdict's `detail` needs the same cap; this stays
|
|
124
|
+
* as the dispatch path's name for it.
|
|
125
|
+
*/
|
|
126
|
+
export const sanitizeOneLine = sanitizeLedgerText;
|
|
127
|
+
|
|
128
|
+
/** Run `chant lifecycle plan <env> --live --json` and parse its `ChangeSet`. */
|
|
129
|
+
async function observeChangeSet(env: string, signal?: AbortSignal): Promise<ChangeSet> {
|
|
130
|
+
const { stdout } = await execAsync(`chant lifecycle plan ${shellQuote(env)} --live --json`, { signal });
|
|
131
|
+
return JSON.parse(stdout) as ChangeSet;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Run `chant components status <env> --live --json` and parse its rows. */
|
|
135
|
+
async function observeStatusRows(env: string, signal?: AbortSignal): Promise<ComponentStatusRow[]> {
|
|
136
|
+
const { stdout } = await execAsync(`chant components status ${shellQuote(env)} --live --json`, { signal });
|
|
137
|
+
const parsed = JSON.parse(stdout) as ComponentStatusRow[] | { rows: ComponentStatusRow[] };
|
|
138
|
+
return Array.isArray(parsed) ? parsed : parsed.rows;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* `chant run <op>`'s exit code for a run that stopped at an unapproved gate
|
|
143
|
+
* (#2119 — `../../../../../packages/core/src/cli/handlers/run.ts`'s
|
|
144
|
+
* `GATED_EXIT_CODE`). The typed signal that replaced the old string match
|
|
145
|
+
* against `LocalGateUnsupportedError`'s message: since #2119 the executor
|
|
146
|
+
* records the pending fact itself and ends with `status: "gated"`, so a tick
|
|
147
|
+
* no longer has to reconstruct a gate from an error string, and no longer
|
|
148
|
+
* writes the pending fact on the executor's behalf.
|
|
149
|
+
*/
|
|
150
|
+
const GATED_EXIT_CODE = 3;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The gate a gated `chant run` stopped at, read off its JSON result (#2119) —
|
|
154
|
+
* pure, unit-tested directly rather than only through `dispatchOp`'s
|
|
155
|
+
* subprocess plumbing. `undefined` when the output says nothing about a gate.
|
|
156
|
+
*/
|
|
157
|
+
export function classifyDispatchFailure(raw: string): { gateName: string } | undefined {
|
|
158
|
+
for (const line of raw.split("\n")) {
|
|
159
|
+
const trimmed = line.trim();
|
|
160
|
+
if (!trimmed.startsWith("{")) continue;
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(trimmed) as { status?: string; gate?: { gate?: string } };
|
|
163
|
+
if (parsed.status === "gated" && typeof parsed.gate?.gate === "string") {
|
|
164
|
+
return { gateName: parsed.gate.gate };
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// Not this line's JSON — keep looking.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const match = /is gated on "([^"]+)"/.exec(raw);
|
|
171
|
+
return match ? { gateName: match[1] } : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Dispatch one matched `run` action via the existing local runner (`chant run
|
|
176
|
+
* <op> --json`). A gated target Op is gate-as-fact (#1485, closed by #2119):
|
|
177
|
+
* the target's own executor reads the gate ledger, records the pending fact,
|
|
178
|
+
* and exits {@link GATED_EXIT_CODE} — so this tick reads the gate off that
|
|
179
|
+
* result rather than parsing a refusal message, and records the `"gated"`
|
|
180
|
+
* outcome that names it. The pending fact itself is the executor's to write;
|
|
181
|
+
* duplicating it here is what "call the same executor code" rules out.
|
|
182
|
+
*/
|
|
183
|
+
async function dispatchOp(
|
|
184
|
+
opName: string,
|
|
185
|
+
signal?: AbortSignal,
|
|
186
|
+
): Promise<{ ok: true } | { ok: false; error: string; gateName?: string }> {
|
|
187
|
+
try {
|
|
188
|
+
await execAsync(`chant run ${shellQuote(opName)} --json`, { signal });
|
|
189
|
+
return { ok: true };
|
|
190
|
+
} catch (err) {
|
|
191
|
+
const e = err as { code?: number; stdout?: string; stderr?: string; message?: string };
|
|
192
|
+
const raw = e.stderr ?? e.message ?? String(err);
|
|
193
|
+
if (e.code === GATED_EXIT_CODE) {
|
|
194
|
+
const gate = classifyDispatchFailure(`${e.stdout ?? ""}\n${raw}`);
|
|
195
|
+
if (gate) return { ok: false, error: sanitizeOneLine(raw), gateName: gate.gateName };
|
|
196
|
+
}
|
|
197
|
+
return { ok: false, error: sanitizeOneLine(raw) };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Verb class allowed to free-run for a given dial — the coarse first-pass
|
|
203
|
+
* gate: `"observe"` never dispatches anything (report only), `"reconcile"`
|
|
204
|
+
* and `"apply"` both allow *some* dispatch. Which verb classes each of those
|
|
205
|
+
* two actually permits is `OPS014`'s job at build time
|
|
206
|
+
* (`../../lint/post-synth/tmp014-converge-rule-refusals.ts`) and
|
|
207
|
+
* {@link verbClassAllowedToDispatch}'s job as this tick's own runtime
|
|
208
|
+
* backstop, below.
|
|
209
|
+
*/
|
|
210
|
+
function dialAllowsDispatch(dial: ConvergeTickArgs["dial"]): boolean {
|
|
211
|
+
return dial === "reconcile" || dial === "apply";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Runtime backstop for issue #1484's Autonomy table (pre-merge review of
|
|
216
|
+
* #1954): `OPS014` already refuses, at build time, a rule table shaped to
|
|
217
|
+
* reach this point with a mutating dispatch outside `"apply"` or any
|
|
218
|
+
* destructive dispatch at all. This is the defense-in-depth check for a rule
|
|
219
|
+
* table that reached `convergeTick` without going through that build — the
|
|
220
|
+
* same "not authored through the checked path" concern
|
|
221
|
+
* `isWellFormedPredicate` already guards for a hand-assembled predicate (see
|
|
222
|
+
* `../../../../packages/core/src/op/converge-rule.ts`'s doc). Fail-closed:
|
|
223
|
+
* an unclassifiable target (`verbClass === undefined`, e.g. the target op
|
|
224
|
+
* couldn't be discovered/read) is never treated as safe to free-run — same
|
|
225
|
+
* fail-closed stance `classifyOpVerbClass` itself takes for an unrecognized
|
|
226
|
+
* activity `fn`.
|
|
227
|
+
*/
|
|
228
|
+
export function verbClassAllowedToDispatch(dial: ConvergeTickArgs["dial"], verbClass: OpVerbClass | undefined): boolean {
|
|
229
|
+
const effective = verbClass ?? "mutating";
|
|
230
|
+
if (effective === "read-only") return true;
|
|
231
|
+
if (effective === "mutating") return dial === "apply";
|
|
232
|
+
return false; // destructive: refused at runtime, unconditionally — see OPS014's doc on why the gate can never actually run.
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Apply {@link verbClassAllowedToDispatch} to one planned "ran" outcome,
|
|
237
|
+
* downgrading it to "reported" (never silently dropped, never silently run)
|
|
238
|
+
* when the dial/verb-class pairing isn't one the tick may actually free-run.
|
|
239
|
+
* Pure — the impure part (discovering and classifying the target op) is
|
|
240
|
+
* {@link classifyDispatchTarget}, called by `convergeTick` before this.
|
|
241
|
+
*/
|
|
242
|
+
export function enforceVerbClassAtDispatch(
|
|
243
|
+
outcome: ConvergeRuleOutcome,
|
|
244
|
+
dial: ConvergeTickArgs["dial"],
|
|
245
|
+
verbClass: OpVerbClass | undefined,
|
|
246
|
+
): ConvergeRuleOutcome {
|
|
247
|
+
if (outcome.action !== "ran") return outcome;
|
|
248
|
+
if (verbClassAllowedToDispatch(dial, verbClass)) return outcome;
|
|
249
|
+
return {
|
|
250
|
+
...outcome,
|
|
251
|
+
action: "reported",
|
|
252
|
+
reason:
|
|
253
|
+
`runtime backstop: dial "${dial}" does not permit dispatching "${outcome.op}" ` +
|
|
254
|
+
`(${verbClass ?? "unclassifiable — treated as mutating, fail-closed"}) — OPS014 should already refuse ` +
|
|
255
|
+
`this at build; reporting instead of risking a silent authority escalation`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Discover and classify a dispatch target's own verb class. Never throws: an op that can't be discovered/read classifies as `undefined` (fail-closed via {@link verbClassAllowedToDispatch}), not silently as `"read-only"`. */
|
|
260
|
+
async function classifyDispatchTarget(opName: string): Promise<OpVerbClass | undefined> {
|
|
261
|
+
try {
|
|
262
|
+
const { ops } = await discoverOps();
|
|
263
|
+
const target = ops.get(opName);
|
|
264
|
+
return target ? classifyOpVerbClass(target.config) : undefined;
|
|
265
|
+
} catch {
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The pure planning core: given a symptom, a rule table, prior-tick fire
|
|
272
|
+
* history, the dial, and the remaining budget, decide what each rule does
|
|
273
|
+
* this tick. Exported and unit-tested directly — the activity function
|
|
274
|
+
* around it is the thin I/O shell (gather inputs, execute the plan, append
|
|
275
|
+
* the ledger record), matching `reconcilePr`'s split between its pure
|
|
276
|
+
* `reconcileSummary`/`entriesFromPlan` and its I/O body.
|
|
277
|
+
*/
|
|
278
|
+
export interface ConvergeTickPlan {
|
|
279
|
+
firedRuleIds: string[];
|
|
280
|
+
outcomes: ConvergeRuleOutcome[];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function planConvergeTick(
|
|
284
|
+
symptom: ConvergeSymptom,
|
|
285
|
+
rules: SerializedConvergeRule[],
|
|
286
|
+
priorRecords: ConvergeTickRecord[],
|
|
287
|
+
dial: ConvergeTickArgs["dial"],
|
|
288
|
+
budget: number,
|
|
289
|
+
): ConvergeTickPlan {
|
|
290
|
+
const firedRuleIds: string[] = [];
|
|
291
|
+
const outcomes: ConvergeRuleOutcome[] = [];
|
|
292
|
+
let dispatched = 0;
|
|
293
|
+
|
|
294
|
+
for (const rule of rules) {
|
|
295
|
+
if (!evaluatePredicate(rule.when, symptom)) continue;
|
|
296
|
+
firedRuleIds.push(rule.id);
|
|
297
|
+
|
|
298
|
+
const threshold = rule.flapThreshold ?? DEFAULT_FLAP_THRESHOLD;
|
|
299
|
+
const consecutive = consecutiveRuleFires(priorRecords, rule.id);
|
|
300
|
+
if (consecutive >= threshold) {
|
|
301
|
+
outcomes.push({
|
|
302
|
+
ruleId: rule.id,
|
|
303
|
+
action: "skipped-flap",
|
|
304
|
+
reason: `fired ${consecutive + 1} consecutive ticks without clearing (threshold ${threshold}) — escalated to report`,
|
|
305
|
+
});
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const action: RuleAction = rule.then;
|
|
310
|
+
|
|
311
|
+
// `unknown` never remediates, regardless of what the rule says.
|
|
312
|
+
if (symptom.status === "unknown" && action.kind === "run") {
|
|
313
|
+
outcomes.push({
|
|
314
|
+
ruleId: rule.id,
|
|
315
|
+
action: "reported",
|
|
316
|
+
reason: `symptom is unknown (${symptom.unobservedReasons.join(", ") || "unobserved"}) — unknown never remediates`,
|
|
317
|
+
});
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (action.kind === "report") {
|
|
322
|
+
outcomes.push({ ruleId: rule.id, action: "reported", reason: action.reason });
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// action.kind === "run"
|
|
327
|
+
if (!dialAllowsDispatch(dial)) {
|
|
328
|
+
outcomes.push({ ruleId: rule.id, action: "reported", reason: `dial "${dial}" does not permit dispatch — report only`, op: action.op });
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (dispatched >= budget) {
|
|
332
|
+
outcomes.push({ ruleId: rule.id, action: "skipped-budget", op: action.op });
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
dispatched++;
|
|
336
|
+
outcomes.push({ ruleId: rule.id, action: "ran", op: action.op });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return { firedRuleIds, outcomes };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function summarizeOutcomes(outcomes: ConvergeRuleOutcome[]) {
|
|
343
|
+
return {
|
|
344
|
+
remediated: outcomes.filter((o) => o.action === "ran").length,
|
|
345
|
+
reported: outcomes.filter((o) => o.action === "reported").length,
|
|
346
|
+
skippedBudget: outcomes.filter((o) => o.action === "skipped-budget").length,
|
|
347
|
+
skippedFlap: outcomes.filter((o) => o.action === "skipped-flap").length,
|
|
348
|
+
gated: outcomes.filter((o) => o.action === "gated").length,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function renderLog(env: string, s: ConvergeSymptom, counts: ReturnType<typeof summarizeOutcomes>): string {
|
|
353
|
+
return (
|
|
354
|
+
`converge(${env}): drifted=${s.updateCount + s.deleteCount} remediated=${counts.remediated} ` +
|
|
355
|
+
`reported=${counts.reported} skipped-budget=${counts.skippedBudget} skipped-flap=${counts.skippedFlap} ` +
|
|
356
|
+
`gated=${counts.gated} unobserved=${s.unobservedCount} adopted=${s.adoptCount}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal): Promise<ConvergeTickResult> {
|
|
361
|
+
await fetchLifecycle().catch(() => undefined);
|
|
362
|
+
|
|
363
|
+
const [cs, statusRows, ledger] = await Promise.all([
|
|
364
|
+
observeChangeSet(args.env, signal),
|
|
365
|
+
observeStatusRows(args.env, signal),
|
|
366
|
+
readConvergeLedger(args.env),
|
|
367
|
+
]);
|
|
368
|
+
|
|
369
|
+
const symptom = deriveSymptoms(args.env, cs, statusRows);
|
|
370
|
+
const plan = planConvergeTick(symptom, args.rules, ledger.records, args.dial, args.budget);
|
|
371
|
+
|
|
372
|
+
// Execute: only "ran" outcomes cause a subprocess dispatch. Before that
|
|
373
|
+
// dispatch, the runtime backstop (Finding A, #1954 pre-merge review)
|
|
374
|
+
// re-classifies the target and downgrades to "reported" if this dial/verb
|
|
375
|
+
// class pairing was never supposed to reach dispatch — OPS014 (build time)
|
|
376
|
+
// is the primary defense; this is what catches a rule table that reached
|
|
377
|
+
// `convergeTick` without going through it.
|
|
378
|
+
for (const outcome of plan.outcomes) {
|
|
379
|
+
if (outcome.action !== "ran" || !outcome.op) continue;
|
|
380
|
+
|
|
381
|
+
const verbClass = await classifyDispatchTarget(outcome.op);
|
|
382
|
+
const backstopped = enforceVerbClassAtDispatch(outcome, args.dial, verbClass);
|
|
383
|
+
outcome.action = backstopped.action;
|
|
384
|
+
outcome.reason = backstopped.reason;
|
|
385
|
+
if (outcome.action !== "ran") continue;
|
|
386
|
+
|
|
387
|
+
const result = await dispatchOp(outcome.op, signal);
|
|
388
|
+
if (!result.ok && result.gateName) {
|
|
389
|
+
// Gate-as-fact (#1485, closed by #2119): a terminal, durable,
|
|
390
|
+
// non-blocking fact — not an ordinary dispatch failure the operator
|
|
391
|
+
// keeps retrying. The target's own executor already wrote the pending
|
|
392
|
+
// record to `_gates/<op>.jsonl`; this outcome is the tick's account of
|
|
393
|
+
// the same event. See ../../../../../packages/core/src/lifecycle/
|
|
394
|
+
// gate-ledger.ts for how it resolves (`chant approve`, or a merged PR).
|
|
395
|
+
outcome.action = "gated";
|
|
396
|
+
outcome.gateName = result.gateName;
|
|
397
|
+
// #2028: the pending fact carries its address when this tick is running
|
|
398
|
+
// somewhere that has one (a PR/MR job). Absent otherwise — never guessed.
|
|
399
|
+
const url = resolveApprovalUrl();
|
|
400
|
+
if (url) outcome.url = url;
|
|
401
|
+
outcome.reason =
|
|
402
|
+
`dispatch of "${outcome.op}" hit gate "${result.gateName}" — recorded as a pending fact, not retried; ` +
|
|
403
|
+
`resolve with \`chant approve ${outcome.op} ${result.gateName}\`${url ? ` or by merging ${url}` : " or a merged PR"}`;
|
|
404
|
+
} else if (!result.ok) {
|
|
405
|
+
outcome.action = "reported";
|
|
406
|
+
outcome.reason = `dispatch of "${outcome.op}" failed: ${result.error}`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const counts = summarizeOutcomes(plan.outcomes);
|
|
411
|
+
const log = renderLog(args.env, symptom, counts);
|
|
412
|
+
const timestamp = new Date().toISOString();
|
|
413
|
+
|
|
414
|
+
const { record } = await appendConvergeRecord({
|
|
415
|
+
op: args.opName,
|
|
416
|
+
env: args.env,
|
|
417
|
+
timestamp,
|
|
418
|
+
firedRuleIds: plan.firedRuleIds,
|
|
419
|
+
outcomes: plan.outcomes,
|
|
420
|
+
// #2027: the per-component verdicts this tick already observed, kept
|
|
421
|
+
// instead of discarded once `summary`'s counts were derived from them.
|
|
422
|
+
components: componentVerdicts(statusRows),
|
|
423
|
+
summary: {
|
|
424
|
+
drifted: symptom.updateCount + symptom.deleteCount,
|
|
425
|
+
remediated: counts.remediated,
|
|
426
|
+
reported: counts.reported,
|
|
427
|
+
skippedBudget: counts.skippedBudget,
|
|
428
|
+
skippedFlap: counts.skippedFlap,
|
|
429
|
+
gated: counts.gated,
|
|
430
|
+
unobserved: symptom.unobservedCount,
|
|
431
|
+
adopted: symptom.adoptCount,
|
|
432
|
+
},
|
|
433
|
+
log,
|
|
434
|
+
});
|
|
435
|
+
await pushLifecycle().catch(() => undefined);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
id: record.id,
|
|
439
|
+
drifted: symptom.status === "drifted",
|
|
440
|
+
remediated: record.summary.remediated,
|
|
441
|
+
reported: record.summary.reported,
|
|
442
|
+
skippedBudget: record.summary.skippedBudget,
|
|
443
|
+
skippedFlap: record.summary.skippedFlap,
|
|
444
|
+
gated: record.summary.gated ?? 0,
|
|
445
|
+
unobserved: record.summary.unobserved,
|
|
446
|
+
adopted: record.summary.adopted,
|
|
447
|
+
log: record.log,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
|
+
import { envTeardown } from "./env-teardown";
|
|
3
|
+
import type { ObservationLexicon, TeardownEnumeration, TeardownExecution } from "../../lexicon";
|
|
4
|
+
import type { ChantConfig } from "../../config";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The activity is exercised through the REAL core engine
|
|
8
|
+
* (`executeTeardown` from @intentius/chant/lifecycle/teardown) — only the
|
|
9
|
+
* config and the lexicon plugin are injected. What's asserted is therefore
|
|
10
|
+
* the whole path an Op step takes: guards, plan, execution, retry, report.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const projectConfig = (overrides: Partial<ChantConfig> = {}): ChantConfig =>
|
|
14
|
+
({
|
|
15
|
+
environments: ["dev", "prod"],
|
|
16
|
+
ownership: { stack: "shop" },
|
|
17
|
+
...overrides,
|
|
18
|
+
}) as ChantConfig;
|
|
19
|
+
|
|
20
|
+
const mockPlugin = (overrides: Record<string, unknown>): ObservationLexicon =>
|
|
21
|
+
({ name: "mock", ...overrides }) as unknown as ObservationLexicon;
|
|
22
|
+
|
|
23
|
+
const enumeration: TeardownEnumeration = {
|
|
24
|
+
candidates: [
|
|
25
|
+
{ name: "web", type: "K8s::Apps::Deployment", physicalId: "web-1", marker: { stack: "shop", env: "dev" } },
|
|
26
|
+
{ name: "cache", type: "K8s::Core::Service", marker: { stack: "shop", env: "dev" } },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("envTeardown — the happy path through the real core engine", () => {
|
|
31
|
+
test("plans by marker, deletes through the plugin, and reports counts", async () => {
|
|
32
|
+
const teardownOwned = vi.fn(async () => enumeration);
|
|
33
|
+
const executeTeardown = vi.fn(
|
|
34
|
+
async (args: { candidates: Array<{ name: string }> }): Promise<TeardownExecution> => ({
|
|
35
|
+
outcomes: args.candidates.map((c) => ({ name: c.name, outcome: "deleted" as const })),
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
const plugin = mockPlugin({ teardownOwned, executeTeardown });
|
|
39
|
+
|
|
40
|
+
const result = await envTeardown({ env: "dev" }, undefined, {
|
|
41
|
+
config: projectConfig(),
|
|
42
|
+
plugins: [plugin],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(teardownOwned).toHaveBeenCalledWith({
|
|
46
|
+
environment: "dev",
|
|
47
|
+
marker: { stack: "shop", env: "dev" },
|
|
48
|
+
});
|
|
49
|
+
expect(executeTeardown).toHaveBeenCalledTimes(1);
|
|
50
|
+
expect(executeTeardown.mock.calls[0]![0]).toMatchObject({
|
|
51
|
+
environment: "dev",
|
|
52
|
+
marker: { stack: "shop", env: "dev" },
|
|
53
|
+
});
|
|
54
|
+
expect(result.environment).toBe("dev");
|
|
55
|
+
expect(result.stack).toBe("shop");
|
|
56
|
+
expect(result.deleted).toBe(2);
|
|
57
|
+
expect(result.notPrunable).toBe(0);
|
|
58
|
+
expect(result.skipped).toBe(0);
|
|
59
|
+
expect(result.holes).toBe(0);
|
|
60
|
+
expect(result.report.outcomes.map((o) => o.outcome)).toEqual(["deleted", "deleted"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a candidate whose marker is foreign never reaches execution — core drops it", async () => {
|
|
64
|
+
const teardownOwned = vi.fn(async (): Promise<TeardownEnumeration> => ({
|
|
65
|
+
candidates: [
|
|
66
|
+
{ name: "mine", type: "T", marker: { stack: "shop", env: "dev" } },
|
|
67
|
+
{ name: "other-env", type: "T", marker: { stack: "shop", env: "prod" } },
|
|
68
|
+
],
|
|
69
|
+
}));
|
|
70
|
+
const executeTeardown = vi.fn(
|
|
71
|
+
async (args: { candidates: Array<{ name: string }> }): Promise<TeardownExecution> => ({
|
|
72
|
+
outcomes: args.candidates.map((c) => ({ name: c.name, outcome: "deleted" as const })),
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const result = await envTeardown({ env: "dev" }, undefined, {
|
|
77
|
+
config: projectConfig(),
|
|
78
|
+
plugins: [mockPlugin({ teardownOwned, executeTeardown })],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(executeTeardown.mock.calls[0]![0].candidates.map((c) => c.name)).toEqual(["mine"]);
|
|
82
|
+
expect(result.deleted).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("envTeardown — guards (the CLI's guards, unchanged)", () => {
|
|
87
|
+
test("refuses a prod-like environment without confirmProd: true, before any live read", async () => {
|
|
88
|
+
const teardownOwned = vi.fn(async () => enumeration);
|
|
89
|
+
await expect(
|
|
90
|
+
envTeardown({ env: "prod" }, undefined, {
|
|
91
|
+
config: projectConfig(),
|
|
92
|
+
plugins: [mockPlugin({ teardownOwned })],
|
|
93
|
+
}),
|
|
94
|
+
).rejects.toThrow(/looks like a production environment.*confirmProd: true/);
|
|
95
|
+
expect(teardownOwned).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("confirmProd: true releases the prod guard", async () => {
|
|
99
|
+
const teardownOwned = vi.fn(async (): Promise<TeardownEnumeration> => ({
|
|
100
|
+
candidates: [{ name: "web", type: "T", marker: { stack: "shop", env: "prod" } }],
|
|
101
|
+
}));
|
|
102
|
+
const executeTeardown = vi.fn(async (): Promise<TeardownExecution> => ({
|
|
103
|
+
outcomes: [{ name: "web", outcome: "deleted" }],
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
const result = await envTeardown({ env: "prod", confirmProd: true }, undefined, {
|
|
107
|
+
config: projectConfig(),
|
|
108
|
+
plugins: [mockPlugin({ teardownOwned, executeTeardown })],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.deleted).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a truthy-but-not-true confirmProd does not count", async () => {
|
|
115
|
+
await expect(
|
|
116
|
+
envTeardown({ env: "prod", confirmProd: "yes" as unknown as boolean }, undefined, {
|
|
117
|
+
config: projectConfig(),
|
|
118
|
+
plugins: [],
|
|
119
|
+
}),
|
|
120
|
+
).rejects.toThrow(/confirmProd: true/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("refuses an environment the project does not declare", async () => {
|
|
124
|
+
const teardownOwned = vi.fn(async () => enumeration);
|
|
125
|
+
await expect(
|
|
126
|
+
envTeardown({ env: "qa" }, undefined, {
|
|
127
|
+
config: projectConfig(),
|
|
128
|
+
plugins: [mockPlugin({ teardownOwned })],
|
|
129
|
+
}),
|
|
130
|
+
).rejects.toThrow(/Unknown environment "qa"/);
|
|
131
|
+
expect(teardownOwned).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("refuses a project with no ownership.stack — nothing to select on", async () => {
|
|
135
|
+
await expect(
|
|
136
|
+
envTeardown({ env: "dev" }, undefined, {
|
|
137
|
+
config: projectConfig({ ownership: undefined }),
|
|
138
|
+
plugins: [],
|
|
139
|
+
}),
|
|
140
|
+
).rejects.toThrow(/ownership\.stack/);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("envTeardown — failures fail the activity", () => {
|
|
145
|
+
test("a candidate still failed after core's retry pass throws, naming it", async () => {
|
|
146
|
+
const teardownOwned = vi.fn(async (): Promise<TeardownEnumeration> => ({
|
|
147
|
+
candidates: [
|
|
148
|
+
{ name: "web", type: "T", marker: { stack: "shop", env: "dev" } },
|
|
149
|
+
{ name: "cache", type: "T", marker: { stack: "shop", env: "dev" } },
|
|
150
|
+
],
|
|
151
|
+
}));
|
|
152
|
+
const executeTeardown = vi.fn(
|
|
153
|
+
async (args: { candidates: Array<{ name: string }> }): Promise<TeardownExecution> => ({
|
|
154
|
+
outcomes: args.candidates.map((c) =>
|
|
155
|
+
c.name === "web"
|
|
156
|
+
? { name: c.name, outcome: "failed" as const, detail: "409 conflict" }
|
|
157
|
+
: { name: c.name, outcome: "deleted" as const },
|
|
158
|
+
),
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await expect(
|
|
163
|
+
envTeardown({ env: "dev" }, undefined, {
|
|
164
|
+
config: projectConfig(),
|
|
165
|
+
plugins: [mockPlugin({ teardownOwned, executeTeardown })],
|
|
166
|
+
}),
|
|
167
|
+
).rejects.toThrow(/1 of 2 candidate\(s\) still failed.*mock\/web.*409 conflict/);
|
|
168
|
+
// First pass over both, then core's one bounded retry pass over the failure.
|
|
169
|
+
expect(executeTeardown).toHaveBeenCalledTimes(2);
|
|
170
|
+
expect(executeTeardown.mock.calls[1]![0].candidates.map((c) => c.name)).toEqual(["web"]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("a lexicon that enumerates but cannot execute reports skipped, not clean", async () => {
|
|
174
|
+
const teardownOwned = vi.fn(async (): Promise<TeardownEnumeration> => ({
|
|
175
|
+
candidates: [{ name: "web", type: "T", marker: { stack: "shop", env: "dev" } }],
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
const result = await envTeardown({ env: "dev" }, undefined, {
|
|
179
|
+
config: projectConfig(),
|
|
180
|
+
plugins: [mockPlugin({ teardownOwned })],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result.deleted).toBe(0);
|
|
184
|
+
expect(result.skipped).toBe(1);
|
|
185
|
+
expect(result.report.unimplemented).toEqual(["mock"]);
|
|
186
|
+
});
|
|
187
|
+
});
|