@intentius/chant 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/catalog.d.ts +51 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +36 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +26 -9
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/lineage.d.ts +10 -0
- package/dist/audit/lineage.d.ts.map +1 -0
- package/dist/audit/prior-art.d.ts +385 -0
- package/dist/audit/prior-art.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +16 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +8 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +3 -0
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +15 -0
- package/dist/audit/secrets.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +46 -0
- package/dist/audit/terraform-state.d.ts.map +1 -0
- package/dist/build.d.ts +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/claimed-fields.d.ts +161 -0
- package/dist/claimed-fields.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +2 -2
- package/dist/cli/command-group.d.ts +14 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +17 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts +13 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +2 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +82 -16
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -42
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts +16 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resources/context.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +22 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +40 -9
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
- package/dist/components/auto-release.d.ts +10 -20
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +4 -4
- package/dist/components/cli-support.d.ts +24 -22
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +21 -6
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver-output.d.ts.map +1 -1
- package/dist/components/driver.d.ts +73 -37
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/manifest-persistence.d.ts +1 -9
- package/dist/components/manifest-persistence.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +6 -6
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +10 -22
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts +2 -2
- package/dist/deep-observation.d.ts +17 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/detectLexicon.d.ts +13 -0
- package/dist/detectLexicon.d.ts.map +1 -1
- package/dist/discovery/files.d.ts +4 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +33 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/config-wire.d.ts +2 -2
- package/dist/fold/foldable-helpers.d.ts +5 -1
- package/dist/fold/foldable-helpers.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +236 -0
- package/dist/fold-provenance.d.ts.map +1 -0
- package/dist/graph-ops.d.ts +1 -1
- package/dist/held-elsewhere.d.ts +85 -0
- package/dist/held-elsewhere.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts +3 -3
- package/dist/lexicon-config.d.ts +4 -4
- package/dist/lexicon-plugin-helpers.d.ts +1 -1
- package/dist/lexicon.d.ts +273 -16
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +13 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +133 -6
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +79 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +2 -2
- package/dist/lifecycle/run-ledger.d.ts +53 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +3 -3
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/config.d.ts +81 -2
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +55 -2
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +1 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +16 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
- package/dist/lint/rules/op/support.d.ts +12 -0
- package/dist/lint/rules/op/support.d.ts.map +1 -0
- package/dist/lint/suppressions.d.ts +108 -0
- package/dist/lint/suppressions.d.ts.map +1 -0
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/op/activities/activity-contracts.d.ts +54 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -0
- package/dist/op/activities/apply.d.ts +262 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +13 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +110 -0
- package/dist/op/activities/converge.d.ts.map +1 -0
- package/dist/op/activities/env-teardown.d.ts +76 -0
- package/dist/op/activities/env-teardown.d.ts.map +1 -0
- package/dist/op/activities/guard-validate.d.ts +64 -0
- package/dist/op/activities/guard-validate.d.ts.map +1 -0
- package/dist/op/activities/http-check.d.ts +34 -0
- package/dist/op/activities/http-check.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +31 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +149 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +7 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +28 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts +76 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +25 -18
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts +28 -0
- package/dist/op/activity-failure.d.ts.map +1 -0
- package/dist/op/activity-profiles.d.ts +118 -0
- package/dist/op/activity-profiles.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +22 -38
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +3 -14
- package/dist/op/activity-runtime.d.ts.map +1 -1
- package/dist/op/builders.d.ts +85 -30
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/change-signal.d.ts +91 -0
- package/dist/op/change-signal.d.ts.map +1 -0
- package/dist/op/composites/apply-op.d.ts +118 -0
- package/dist/op/composites/apply-op.d.ts.map +1 -0
- package/dist/op/composites/converge-op.d.ts +120 -0
- package/dist/op/composites/converge-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +26 -0
- package/dist/op/composites/index.d.ts.map +1 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
- package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
- package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/op/composites/reconcile-op.d.ts +57 -0
- package/dist/op/composites/reconcile-op.d.ts.map +1 -0
- package/dist/op/composites/watch-op.d.ts +60 -0
- package/dist/op/composites/watch-op.d.ts.map +1 -0
- package/dist/op/composites/workflow-audit-op.d.ts +57 -0
- package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/op/converge-rule.d.ts +12 -10
- package/dist/op/converge-rule.d.ts.map +1 -1
- package/dist/op/cron.d.ts +51 -0
- package/dist/op/cron.d.ts.map +1 -0
- package/dist/op/discover.d.ts +9 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/duration.d.ts +11 -0
- package/dist/op/duration.d.ts.map +1 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +40 -0
- package/dist/op/gate-name.d.ts.map +1 -0
- package/dist/op/gate-summary.d.ts +43 -0
- package/dist/op/gate-summary.d.ts.map +1 -0
- package/dist/op/gate.d.ts +90 -0
- package/dist/op/gate.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +9 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/index.d.ts +29 -9
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +114 -28
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +11 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +185 -0
- package/dist/op/op-ir.d.ts.map +1 -0
- package/dist/op/op-verb-class.d.ts +10 -2
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +93 -5
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/receipt-store.d.ts +1 -1
- package/dist/op/receipt-store.d.ts.map +1 -1
- package/dist/op/resource.d.ts +15 -1
- package/dist/op/resource.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +200 -0
- package/dist/op/runtime.d.ts.map +1 -0
- package/dist/op/runtimes/local.d.ts +29 -0
- package/dist/op/runtimes/local.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +15 -13
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/types.d.ts +96 -29
- package/dist/op/types.d.ts.map +1 -1
- package/dist/ownership.d.ts +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/dist/terraform/bridge.d.ts +26 -7
- package/dist/terraform/bridge.d.ts.map +1 -1
- package/dist/terraform/carve-provider.d.ts +28 -2
- package/dist/terraform/carve-provider.d.ts.map +1 -1
- package/dist/terraform/data-source-shape.d.ts +77 -0
- package/dist/terraform/data-source-shape.d.ts.map +1 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/providers/kubernetes.d.ts +5 -0
- package/dist/terraform/providers/kubernetes.d.ts.map +1 -1
- package/dist/terraform/tier-map.d.ts +16 -4
- package/dist/terraform/tier-map.d.ts.map +1 -1
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/audit/catalog.test.ts +79 -3
- package/src/audit/catalog.ts +111 -13
- package/src/audit/core.test.ts +26 -0
- package/src/audit/core.ts +84 -18
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +202 -18
- package/src/audit/lineage.ts +100 -0
- package/src/audit/prior-art.ts +153 -0
- package/src/audit/proof.test.ts +67 -0
- package/src/audit/proof.ts +70 -0
- package/src/audit/report-html.ts +9 -4
- package/src/audit/report-model.ts +16 -2
- package/src/audit/report.test.ts +33 -0
- package/src/audit/report.ts +5 -3
- package/src/audit/rules-doc.test.ts +34 -2
- package/src/audit/rules-doc.ts +82 -9
- package/src/audit/secrets.ts +15 -3
- package/src/audit/terraform-state.test.ts +144 -0
- package/src/audit/terraform-state.ts +131 -0
- package/src/build.ts +31 -1
- package/src/claimed-fields.test.ts +145 -0
- package/src/claimed-fields.ts +208 -0
- package/src/cli/build-params-cli.ts +2 -2
- package/src/cli/command-group.ts +14 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
- package/src/cli/commands/audit.test.ts +191 -11
- package/src/cli/commands/audit.ts +78 -28
- package/src/cli/commands/build.test.ts +330 -9
- package/src/cli/commands/build.ts +170 -24
- package/src/cli/commands/carve-bridge.test.ts +237 -14
- package/src/cli/commands/carve-bridge.ts +21 -17
- package/src/cli/commands/carve-emit-k8s.test.ts +17 -10
- package/src/cli/commands/check-lexicon-docs.ts +1 -1
- package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
- package/src/cli/commands/check-lexicon-examples.ts +42 -10
- package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
- package/src/cli/commands/init-lexicon.test.ts +97 -2
- package/src/cli/commands/init-lexicon.ts +4 -2
- package/src/cli/commands/init.test.ts +35 -1
- package/src/cli/commands/init.ts +14 -3
- package/src/cli/commands/lint.test.ts +194 -1
- package/src/cli/commands/lint.ts +205 -7
- package/src/cli/commands/onboard.test.ts +75 -0
- package/src/cli/commands/onboard.ts +54 -23
- package/src/cli/handlers/build.test.ts +81 -1
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/graph.test.ts +3 -3
- package/src/cli/handlers/lifecycle.test.ts +195 -12
- package/src/cli/handlers/lifecycle.ts +123 -6
- package/src/cli/handlers/misc.ts +1 -1
- package/src/cli/handlers/operator.test.ts +183 -11
- package/src/cli/handlers/operator.ts +341 -55
- package/src/cli/handlers/run.test.ts +568 -1295
- package/src/cli/handlers/run.ts +446 -912
- package/src/cli/main.test.ts +22 -16
- package/src/cli/main.ts +66 -33
- package/src/cli/mcp/op-tools.ts +147 -88
- package/src/cli/mcp/resource-handlers.ts +10 -37
- package/src/cli/mcp/resources/context.ts +11 -12
- package/src/cli/mcp/server.test.ts +25 -27
- package/src/cli/mcp/server.ts +2 -2
- package/src/cli/plugins.test.ts +68 -2
- package/src/cli/plugins.ts +39 -0
- package/src/cli/registry.ts +40 -9
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.ts +1 -1
- package/src/components/README.md +4 -3
- package/src/components/__fixtures__/neo4j-fanout.json +1 -1
- package/src/components/auto-release.test.ts +4 -20
- package/src/components/auto-release.ts +17 -42
- package/src/components/capability.ts +4 -4
- package/src/components/cli-support.test.ts +28 -12
- package/src/components/cli-support.ts +60 -47
- package/src/components/component-schema.test.ts +18 -2
- package/src/components/component.schema.json +18 -5
- package/src/components/component.test.ts +2 -2
- package/src/components/component.ts +27 -7
- package/src/components/config-defaults.test.ts +2 -2
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +109 -20
- package/src/components/driver.ts +201 -84
- package/src/components/index.ts +1 -6
- package/src/components/manifest-persistence.test.ts +2 -20
- package/src/components/manifest-persistence.ts +4 -24
- package/src/components/pilots/neo4j-fanout.pilot.ts +3 -3
- package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
- package/src/components/run-progress.ts +6 -6
- package/src/components/verbs/run-agent.test.ts +19 -0
- package/src/components/verbs/run-agent.ts +12 -24
- package/src/components/verbs/verify.ts +2 -2
- package/src/deep-observation.test.ts +75 -0
- package/src/deep-observation.ts +48 -0
- package/src/detectLexicon.ts +18 -1
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.test.ts +1 -1
- package/src/discovery/fold-import.ts +105 -2
- package/src/discovery/param-deps.test.ts +112 -1
- package/src/discovery/param-deps.ts +193 -0
- package/src/discovery/sandbox/bundle.ts +15 -1
- package/src/discovery/sandbox/config-wire.test.ts +2 -2
- package/src/discovery/sandbox/config-wire.ts +2 -2
- package/src/fold/fold.test.ts +56 -0
- package/src/fold/foldable-helpers.ts +82 -2
- package/src/fold-provenance-exclusion.test.ts +186 -0
- package/src/fold-provenance.test.ts +495 -0
- package/src/fold-provenance.ts +441 -0
- package/src/graph-ops.test.ts +3 -3
- package/src/graph-ops.ts +1 -1
- package/src/held-elsewhere.test.ts +102 -0
- package/src/held-elsewhere.ts +114 -0
- package/src/index.ts +3 -0
- package/src/kubectl-context.ts +3 -3
- package/src/lexicon-config.test.ts +5 -5
- package/src/lexicon-config.ts +4 -4
- package/src/lexicon-plugin-helpers.ts +1 -1
- package/src/lexicon.ts +296 -16
- package/src/lifecycle/change-set.test.ts +49 -0
- package/src/lifecycle/change-set.ts +67 -0
- package/src/lifecycle/converge-ledger.ts +1 -1
- package/src/lifecycle/deep-diff.test.ts +267 -14
- package/src/lifecycle/deep-diff.ts +217 -14
- package/src/lifecycle/deep-observe.test.ts +3 -1
- package/src/lifecycle/deep-observe.ts +16 -6
- package/src/lifecycle/gate-ledger.test.ts +74 -2
- package/src/lifecycle/gate-ledger.ts +154 -36
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/release-ledger.ts +2 -2
- package/src/lifecycle/run-ledger.test.ts +268 -0
- package/src/lifecycle/run-ledger.ts +201 -0
- package/src/lifecycle/symptoms.ts +3 -3
- package/src/lint/config.test.ts +82 -1
- package/src/lint/config.ts +112 -3
- package/src/lint/pipeline-change-gate.test.ts +2 -2
- package/src/lint/policy.test.ts +1 -1
- package/src/lint/post-synth.test.ts +31 -0
- package/src/lint/post-synth.ts +54 -1
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
- package/src/lint/rules/comp/comp.test.ts +27 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +76 -0
- package/src/lint/rules/comp/index.ts +3 -3
- package/src/lint/rules/op/index.ts +23 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
- package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
- package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
- package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
- package/src/lint/rules/op/support.ts +11 -0
- package/src/lint/suppressions.test.ts +163 -0
- package/src/lint/suppressions.ts +204 -0
- package/src/live-endpoint.ts +1 -1
- package/src/op/activities/activity-contracts.ts +66 -0
- package/src/op/activities/apply.test.ts +567 -0
- package/src/op/activities/apply.ts +662 -0
- package/src/op/activities/build.ts +26 -0
- package/src/op/activities/converge.test.ts +330 -0
- package/src/op/activities/converge.ts +449 -0
- package/src/op/activities/env-teardown.test.ts +187 -0
- package/src/op/activities/env-teardown.ts +171 -0
- package/src/op/activities/guard-validate.test.ts +139 -0
- package/src/op/activities/guard-validate.ts +223 -0
- package/src/op/activities/http-check.test.ts +48 -0
- package/src/op/activities/http-check.ts +55 -0
- package/src/op/activities/index.ts +119 -0
- package/src/op/activities/lexicon-upgrade.test.ts +492 -0
- package/src/op/activities/lexicon-upgrade.ts +673 -0
- package/src/op/activities/lifecycle.ts +85 -0
- package/src/op/activities/pipeline-audit.test.ts +67 -0
- package/src/op/activities/pipeline-audit.ts +201 -0
- package/src/op/activities/policy.test.ts +19 -0
- package/src/op/activities/policy.ts +37 -0
- package/src/op/activities/reconcile.test.ts +161 -0
- package/src/op/activities/reconcile.ts +354 -0
- package/src/op/activities/shell.ts +26 -0
- package/src/op/activities/teardown.ts +22 -0
- package/src/op/activities/util.ts +6 -0
- package/src/op/activities/wait.ts +69 -0
- package/src/op/activities/workflow-audit.test.ts +90 -0
- package/src/op/activities/workflow-audit.ts +203 -0
- package/src/op/activity-contract-registry.test.ts +98 -0
- package/src/op/activity-contract-registry.ts +114 -0
- package/src/op/activity-contract.test.ts +1 -1
- package/src/op/activity-contract.ts +37 -33
- package/src/op/activity-failure.ts +34 -0
- package/src/op/activity-profiles.test.ts +56 -0
- package/src/op/activity-profiles.ts +116 -0
- package/src/op/activity-registry.test.ts +37 -28
- package/src/op/activity-registry.ts +27 -56
- package/src/op/activity-runtime.ts +3 -47
- package/src/op/builders-exports.test.ts +0 -49
- package/src/op/builders-typed.test.ts +164 -0
- package/src/op/builders.ts +125 -41
- package/src/op/change-signal.test.ts +117 -0
- package/src/op/change-signal.ts +169 -0
- package/src/op/composites/apply-op.ts +218 -0
- package/src/op/composites/composites.test.ts +379 -0
- package/src/op/composites/converge-op.test.ts +168 -0
- package/src/op/composites/converge-op.ts +214 -0
- package/src/op/composites/index.ts +26 -0
- package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
- package/src/op/composites/lexicon-upgrade-op.ts +134 -0
- package/src/op/composites/pipeline-audit-op.test.ts +25 -0
- package/src/op/composites/pipeline-audit-op.ts +78 -0
- package/src/op/composites/reconcile-op.test.ts +51 -0
- package/src/op/composites/reconcile-op.ts +110 -0
- package/src/op/composites/watch-op.ts +108 -0
- package/src/op/composites/workflow-audit-op.test.ts +25 -0
- package/src/op/composites/workflow-audit-op.ts +88 -0
- package/src/op/converge-rule.ts +12 -10
- package/src/op/cron.test.ts +128 -0
- package/src/op/cron.ts +149 -0
- package/src/op/discover-root.test.ts +105 -1
- package/src/op/discover.test.ts +6 -2
- package/src/op/discover.ts +84 -16
- package/src/op/duration.ts +21 -0
- package/src/op/effect-step.test.ts +29 -14
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate-name.test.ts +65 -0
- package/src/op/gate-name.ts +60 -0
- package/src/op/gate-summary.ts +84 -0
- package/src/op/gate.ts +155 -0
- package/src/op/generate-pipeline.test.ts +65 -3
- package/src/op/generate-pipeline.ts +19 -2
- package/src/op/index.ts +48 -8
- package/src/op/local-executor.test.ts +178 -34
- package/src/op/local-executor.ts +371 -102
- package/src/op/local-output.test.ts +95 -11
- package/src/op/local-output.ts +33 -4
- package/src/op/op-ir.test.ts +292 -0
- package/src/op/op-ir.ts +419 -0
- package/src/op/op-verb-class.test.ts +2 -2
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +28 -7
- package/src/op/operator.test.ts +543 -6
- package/src/op/operator.ts +203 -20
- package/src/op/receipt-store.ts +1 -1
- package/src/op/resource.ts +20 -2
- package/src/op/runtime.ts +203 -0
- package/src/op/runtimes/local.test.ts +181 -0
- package/src/op/runtimes/local.ts +212 -0
- package/src/op/step-output-ref.ts +15 -13
- package/src/op/types.ts +106 -26
- package/src/ownership.ts +1 -1
- package/src/provenance.test.ts +6 -0
- package/src/provenance.ts +21 -1
- package/src/serializer-walker.test.ts +42 -0
- package/src/serializer-walker.ts +21 -2
- package/src/terraform/aws-resources.test.ts +13 -4
- package/src/terraform/bridge.test.ts +22 -9
- package/src/terraform/bridge.ts +89 -28
- package/src/terraform/carve-provider.ts +38 -2
- package/src/terraform/data-source-shape.ts +95 -0
- package/src/terraform/graph.ts +35 -7
- package/src/terraform/providers/kubernetes.ts +48 -2
- package/src/terraform/tier-map.ts +21 -6
- package/src/terraform/types.ts +8 -0
- package/dist/cli/handlers/op-progress.d.ts +0 -90
- package/dist/cli/handlers/op-progress.d.ts.map +0 -1
- package/dist/cli/handlers/run-client.d.ts +0 -127
- package/dist/cli/handlers/run-client.d.ts.map +0 -1
- package/dist/cli/handlers/run-report.d.ts +0 -5
- package/dist/cli/handlers/run-report.d.ts.map +0 -1
- package/dist/components/temporal-codegen-loader.d.ts +0 -27
- package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
- package/src/cli/handlers/op-progress.test.ts +0 -268
- package/src/cli/handlers/op-progress.ts +0 -248
- package/src/cli/handlers/run-client.test.ts +0 -82
- package/src/cli/handlers/run-client.ts +0 -219
- package/src/cli/handlers/run-report.test.ts +0 -62
- package/src/cli/handlers/run-report.ts +0 -122
- package/src/components/temporal-codegen-loader.ts +0 -41
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { runOpLocally } from "../local-executor";
|
|
3
|
+
import type { ActivityFn, ActivityProfile } from "../activity-registry";
|
|
4
|
+
import type { OpConfig } from "../types";
|
|
5
|
+
import { eq, gt, run, report, when } from "../converge-rule";
|
|
6
|
+
import type { ConvergeSymptom } from "../../lifecycle/symptoms";
|
|
7
|
+
import { ConvergeOp } from "./converge-op";
|
|
8
|
+
|
|
9
|
+
function props(op: unknown): OpConfig {
|
|
10
|
+
return (op as { props: OpConfig }).props;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function phaseNamed(op: unknown, name: string) {
|
|
14
|
+
const phase = props(op).phases.find((p) => p.name === name);
|
|
15
|
+
if (!phase) throw new Error(`no "${name}" phase`);
|
|
16
|
+
return phase;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const driftRule = when<ConvergeSymptom>(eq("status", "drifted"), run("fountain-apply"), {
|
|
20
|
+
id: "drift-apply",
|
|
21
|
+
why: "Re-apply converges drift back to declared source.",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("ConvergeOp composite (#1484)", () => {
|
|
25
|
+
test("builds an Observe -> Converge phase pair", () => {
|
|
26
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
|
|
27
|
+
const config = props(op);
|
|
28
|
+
expect(config.phases.map((p) => p.name)).toEqual(["Observe", "Converge"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("Observe phase snapshots then diffs, with a Drift outcome attribute", () => {
|
|
32
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
|
|
33
|
+
const observe = phaseNamed(op, "Observe");
|
|
34
|
+
expect(observe.steps).toHaveLength(2);
|
|
35
|
+
expect((observe.steps[0] as { fn: string }).fn).toBe("lifecycleSnapshot");
|
|
36
|
+
const diff = observe.steps[1] as { fn: string; args: Record<string, unknown>; outcomeAttribute: unknown };
|
|
37
|
+
expect(diff.fn).toBe("lifecycleDiff");
|
|
38
|
+
expect(diff.args.env).toBe("staging");
|
|
39
|
+
expect(diff.outcomeAttribute).toEqual({ name: "Drift", from: "drifted" });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("Converge phase calls convergeTick with the rule table, dial, and budget, threading the Observe diff via a step-output reference", () => {
|
|
43
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", budget: 5, rules: [driftRule] });
|
|
44
|
+
const converge = phaseNamed(op, "Converge");
|
|
45
|
+
expect(converge.steps).toHaveLength(1);
|
|
46
|
+
const tick = converge.steps[0] as { fn: string; args: Record<string, unknown>; profile: string; outcomeAttribute: unknown };
|
|
47
|
+
expect(tick.fn).toBe("convergeTick");
|
|
48
|
+
expect(tick.profile).toBe("longInfra");
|
|
49
|
+
expect(tick.args.opName).toBe("staging-converge");
|
|
50
|
+
expect(tick.args.env).toBe("staging");
|
|
51
|
+
expect(tick.args.dial).toBe("apply");
|
|
52
|
+
expect(tick.args.budget).toBe(5);
|
|
53
|
+
expect(tick.args.rules).toEqual([driftRule]);
|
|
54
|
+
expect(tick.args.preflightDrift).toMatchObject({ kind: "step-output-ref", step: "diff", path: "drifted" });
|
|
55
|
+
expect(tick.outcomeAttribute).toEqual({ name: "Remediated", from: "remediated" });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("defaults: dial observe, budget 3, live true", () => {
|
|
59
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
|
|
60
|
+
const config = props(op);
|
|
61
|
+
expect(config.labels).toEqual({ Converge: "true", Env: "staging", Dial: "observe" });
|
|
62
|
+
const tick = phaseNamed(op, "Converge").steps[0] as { args: Record<string, unknown> };
|
|
63
|
+
expect(tick.args.dial).toBe("observe");
|
|
64
|
+
expect(tick.args.budget).toBe(3);
|
|
65
|
+
const diff = phaseNamed(op, "Observe").steps[1] as { args: Record<string, unknown> };
|
|
66
|
+
expect(diff.args.live).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("no schedule -> no cadence on the op; a schedule -> op.schedule.cron (#2120)", () => {
|
|
70
|
+
const noSchedule = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
|
|
71
|
+
expect(props(noSchedule.op).schedule).toBeUndefined();
|
|
72
|
+
|
|
73
|
+
const scheduled = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "*/10 * * * *" });
|
|
74
|
+
expect(props(scheduled.op).schedule?.cron).toBe("*/10 * * * *");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Finding D (#1954 pre-merge review): issue #1484's acceptance criterion is
|
|
78
|
+
// "skip-and-report when a prior remediation is in flight ... never queue".
|
|
79
|
+
// `"skip"` is the overlap policy that drops a fire whose predecessor is
|
|
80
|
+
// still running, rather than buffering/queuing it.
|
|
81
|
+
test("a scheduled ConvergeOp sets an explicit \"skip\" overlap — never queue an in-flight tick", () => {
|
|
82
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "*/10 * * * *" });
|
|
83
|
+
expect(props(op).schedule?.overlap).toBe("skip");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("an invalid cron is refused at construction, with cronSyntaxMessage's wording (#2120)", () => {
|
|
87
|
+
expect(() =>
|
|
88
|
+
ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "every ten minutes" }),
|
|
89
|
+
).toThrow(/does not look like valid 5- or 6-field cron syntax/);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("ConvergeOp composite — build-time refusals (#1484)", () => {
|
|
94
|
+
test("refuses an empty rule table", () => {
|
|
95
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [] })).toThrow(/at least one rule/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("refuses duplicate rule ids", () => {
|
|
99
|
+
const dup = when<ConvergeSymptom>(eq("status", "reconciled"), report("noop"), { id: "drift-apply", why: "y" });
|
|
100
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule, dup] })).toThrow(/duplicate rule id/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("refuses a negative or non-integer budget", () => {
|
|
104
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule], budget: -1 })).toThrow(/budget must be/);
|
|
105
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule], budget: 1.5 })).toThrow(/budget must be/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("refuses a rule assembled by hand with a blank why", () => {
|
|
109
|
+
const noWhy = { id: "r1", when: { kind: "field-comparison", field: "status", op: "eq", value: "drifted" }, then: { kind: "report", reason: "x" }, why: "" } as unknown as typeof driftRule;
|
|
110
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [noWhy] })).toThrow(/carry its why/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("refuses a rule assembled by hand outside the evaluable subset", () => {
|
|
114
|
+
const bad = { id: "r1", when: { kind: "javascript-closure" }, then: { kind: "report", reason: "x" }, why: "y" } as unknown as typeof driftRule;
|
|
115
|
+
expect(() => ConvergeOp({ name: "x", env: "staging", rules: [bad] })).toThrow(/evaluable subset/);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("ConvergeOp — end-to-end local run (#1484)", () => {
|
|
120
|
+
const PROFILES: Record<string, ActivityProfile> = {
|
|
121
|
+
fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
|
|
122
|
+
longInfra: { timeout: "20m", retry: { maximumAttempts: 1 } },
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
test("chant run <converge-op>: observes, then ticks, resolving the step-output reference into the tick's args", async () => {
|
|
126
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", budget: 2, rules: [driftRule] });
|
|
127
|
+
const config = props(op);
|
|
128
|
+
|
|
129
|
+
let receivedArgs: Record<string, unknown> | undefined;
|
|
130
|
+
const activities = new Map<string, ActivityFn>([
|
|
131
|
+
["lifecycleSnapshot", (async () => undefined) as ActivityFn],
|
|
132
|
+
["lifecycleDiff", (async () => ({ output: "DRIFTED (1)", exitCode: 0, drifted: true })) as ActivityFn],
|
|
133
|
+
[
|
|
134
|
+
"convergeTick",
|
|
135
|
+
(async (args) => {
|
|
136
|
+
receivedArgs = args;
|
|
137
|
+
return { drifted: true, remediated: 1, reported: 0, skippedBudget: 0, skippedFlap: 0, unobserved: 0, adopted: 0, log: "converge(staging): remediated=1" };
|
|
138
|
+
}) as ActivityFn,
|
|
139
|
+
],
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const result = await runOpLocally(config, activities, PROFILES);
|
|
143
|
+
|
|
144
|
+
expect(result.status).toBe("ok");
|
|
145
|
+
// The step-output reference resolved to the real value the Observe phase produced.
|
|
146
|
+
expect(receivedArgs?.preflightDrift).toBe(true);
|
|
147
|
+
expect(receivedArgs?.dial).toBe("apply");
|
|
148
|
+
expect(receivedArgs?.budget).toBe(2);
|
|
149
|
+
expect(receivedArgs?.opName).toBe("staging-converge");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a ConvergeOp with a gated dispatch target still runs locally itself — the tick's own phases carry no gate", async () => {
|
|
153
|
+
// ConvergeOp's own generated workflow never contains a gate step (dispatch
|
|
154
|
+
// to a gated Op happens inside convergeTick's own subprocess dispatch, not
|
|
155
|
+
// as a step in this Op's phases) — so the local runner never refuses it.
|
|
156
|
+
const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", rules: [driftRule] });
|
|
157
|
+
const config = props(op);
|
|
158
|
+
expect(config.phases.every((p) => p.steps.every((s) => s.kind !== "gate"))).toBe(true);
|
|
159
|
+
|
|
160
|
+
const activities = new Map<string, ActivityFn>([
|
|
161
|
+
["lifecycleSnapshot", (async () => undefined) as ActivityFn],
|
|
162
|
+
["lifecycleDiff", (async () => ({ output: "", exitCode: 0, drifted: false })) as ActivityFn],
|
|
163
|
+
["convergeTick", (async () => ({ drifted: false, remediated: 0, reported: 0, skippedBudget: 0, skippedFlap: 0, unobserved: 0, adopted: 0, log: "" })) as ActivityFn],
|
|
164
|
+
]);
|
|
165
|
+
const result = await runOpLocally(config, activities, PROFILES);
|
|
166
|
+
expect(result.status).toBe("ok");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConvergeOp composite (#1484, epic #1487 feature 1) — the executable
|
|
3
|
+
* runbook. Observe, classify against a typed rule table, dispatch to
|
|
4
|
+
* declared ops within a per-tick budget, record, rest. Peer to `WatchOp` /
|
|
5
|
+
* `ReconcileOp` / `ApplyOp` — same `{ op }` shape, with the cadence on the
|
|
6
|
+
* Op itself (#2120).
|
|
7
|
+
*
|
|
8
|
+
* Phases: `Observe` (snapshot + a quick live diff, exactly `WatchOp`'s own
|
|
9
|
+
* shape, for the `Drift` outcome and per-phase visibility) ->
|
|
10
|
+
* `Converge` (one `convergeTick` activity call —
|
|
11
|
+
* ../activities/converge.ts — that does classify + dispatch + record;
|
|
12
|
+
* see that module's doc for why this can't be split into further static
|
|
13
|
+
* phases). `Observe`'s drift signal threads into `Converge`'s args via a
|
|
14
|
+
* step-output reference (#1290), not a search-attribute round-trip.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { ConvergeOp, eq, gt, run, report, when } from "@intentius/chant/op";
|
|
19
|
+
* import type { ConvergeSymptom } from "@intentius/chant/lifecycle/symptoms";
|
|
20
|
+
*
|
|
21
|
+
* export const { op } = ConvergeOp({
|
|
22
|
+
* name: "fountain-converge",
|
|
23
|
+
* env: "staging",
|
|
24
|
+
* dial: "apply",
|
|
25
|
+
* schedule: "*\/10 * * * *",
|
|
26
|
+
* rules: [
|
|
27
|
+
* when<ConvergeSymptom>(eq("status", "drifted"), run("fountain-apply"), {
|
|
28
|
+
* id: "drift-apply",
|
|
29
|
+
* why: "Live config drifted from declared source; re-apply converges it back.",
|
|
30
|
+
* }),
|
|
31
|
+
* when<ConvergeSymptom>(gt("adoptCount", 0), report("unowned resources present"), {
|
|
32
|
+
* id: "adopt-report",
|
|
33
|
+
* why: "An unowned resource is reported for a human to review — never auto-claimed.",
|
|
34
|
+
* }),
|
|
35
|
+
* when<ConvergeSymptom>(eq("status", "unknown"), report("environment could not be fully observed"), {
|
|
36
|
+
* id: "unknown-report",
|
|
37
|
+
* why: "unknown never remediates — a partial read is a hole to report, not a guess to act on.",
|
|
38
|
+
* }),
|
|
39
|
+
* ],
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @see #1484, #1487
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { Op, phase, activity } from "../builders";
|
|
47
|
+
import type { OpResource } from "../resource";
|
|
48
|
+
import { isWellFormedPredicate, duplicateRuleIds, type ConvergeRule } from "../converge-rule";
|
|
49
|
+
import type { ConvergeSymptom } from "../../lifecycle/symptoms";
|
|
50
|
+
import { CONVERGE_SYMPTOM_FIELDS } from "../../lifecycle/symptoms";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Authority level for this ConvergeOp's environment — issue #1484's own
|
|
54
|
+
* Autonomy table (dial × verb class):
|
|
55
|
+
*
|
|
56
|
+
* | verb class | observe | reconcile | apply |
|
|
57
|
+
* |-------------|-------------|------------------|------------------------|
|
|
58
|
+
* | read-only | free-run | free-run | free-run |
|
|
59
|
+
* | mutating | report only | open PR | run, gated per op |
|
|
60
|
+
* | destructive | refused | refused | always gated |
|
|
61
|
+
*
|
|
62
|
+
* `ConvergeOp` adds no authority an environment did not already grant.
|
|
63
|
+
* Default `"observe"` — not autonomous by default (issue: "a ConvergeOp in
|
|
64
|
+
* an observe environment is a report generator").
|
|
65
|
+
*
|
|
66
|
+
* **v1 implements a conservative subset of this table**, not the whole
|
|
67
|
+
* thing (pre-merge review of #1954 caught the gap between what the code did
|
|
68
|
+
* and what this table says — this doc now matches the issue, and the code
|
|
69
|
+
* matches this doc):
|
|
70
|
+
*
|
|
71
|
+
* | verb class | observe | reconcile | apply |
|
|
72
|
+
* |-------------|-------------|-------------------------------------|--------------------------------------|
|
|
73
|
+
* | read-only | free-run | free-run | free-run |
|
|
74
|
+
* | mutating | report only | **refused at build (OPS014)** | run |
|
|
75
|
+
* | destructive | refused | refused | **refused at build (OPS014), v1** |
|
|
76
|
+
*
|
|
77
|
+
* - **`reconcile` × mutating is "open PR" in the issue's table — not yet
|
|
78
|
+
* implemented.** Building that channel (reusing `ReconcileOp`'s
|
|
79
|
+
* `onDrift: "pull-request" | "issue" | "report"`) is out of v1 scope — see
|
|
80
|
+
* `../converge-rule.ts`'s `ReportAction` doc and epic #1487's
|
|
81
|
+
* onDrift-channel open question. Until it exists, a rule that would
|
|
82
|
+
* dispatch a mutating op under `reconcile` is refused at build
|
|
83
|
+
* (`OPS014`), not silently escalated to "run directly" the way `apply`
|
|
84
|
+
* would. A runtime backstop in `convergeTick`
|
|
85
|
+
* (`../activities/converge.ts`) re-checks the same thing at dispatch
|
|
86
|
+
* time, for a rule table that reached the tick without going through that
|
|
87
|
+
* build.
|
|
88
|
+
* - **`apply` × destructive is refused outright in v1, gate or not.** The
|
|
89
|
+
* issue's table says "always gated", but the dispatch executor can't
|
|
90
|
+
* honor a gate: `dispatchOp` shells `chant run <op>`, and a gated run ends
|
|
91
|
+
* pending approval (#2119) rather than continuing — so
|
|
92
|
+
* "destructive + apply + gated" reads as a path to dispatch but was a
|
|
93
|
+
* dead cell: it could never actually succeed. `OPS014` refuses the rule
|
|
94
|
+
* instead of shipping a path guaranteed to fail every time it's exercised.
|
|
95
|
+
* Gated dispatch that waits for the approval is #1485's design to own.
|
|
96
|
+
*/
|
|
97
|
+
export type ConvergeDial = "observe" | "reconcile" | "apply";
|
|
98
|
+
|
|
99
|
+
export interface ConvergeOpConfig {
|
|
100
|
+
/** Op name (kebab-case). Names the Op's output directory and is what `chant run` takes. */
|
|
101
|
+
name: string;
|
|
102
|
+
/** Environment to converge (e.g. "staging"). */
|
|
103
|
+
env: string;
|
|
104
|
+
/** @default "observe" */
|
|
105
|
+
dial?: ConvergeDial;
|
|
106
|
+
/** Max number of `run` dispatches per tick — the issue's "the worst tick is bounded" (Accessible Ops factor VI). Rules matched beyond the budget are recorded, not run. @default 3 */
|
|
107
|
+
budget?: number;
|
|
108
|
+
/** The rule table — build with `when()`/`eq()`/`run()`/`report()` from `@intentius/chant/op`. Non-empty; every rule id must be unique. */
|
|
109
|
+
rules: ConvergeRule<ConvergeSymptom>[];
|
|
110
|
+
/**
|
|
111
|
+
* Cron expression. When set, it lands on the Op as `schedule` — the cadence
|
|
112
|
+
* `chant operator` ticks this Op on and the CI generators render; omit for
|
|
113
|
+
* one-shot `chant run` on the local executor, also the test story (issue:
|
|
114
|
+
* "one-shot runnable locally for a single tick").
|
|
115
|
+
*/
|
|
116
|
+
schedule?: string;
|
|
117
|
+
/** Run `chant lifecycle diff --live` in the Observe phase (queries live state). @default true */
|
|
118
|
+
live?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface ConvergeOpResources {
|
|
122
|
+
/** Op resource — the observe-then-converge Op. */
|
|
123
|
+
op: InstanceType<typeof OpResource>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function ConvergeOp(config: ConvergeOpConfig): ConvergeOpResources {
|
|
127
|
+
const dial = config.dial ?? "observe";
|
|
128
|
+
const budget = config.budget ?? 3;
|
|
129
|
+
const live = config.live ?? true;
|
|
130
|
+
|
|
131
|
+
// ── Build-time refusals (#1484) ─────────────────────────────────────────
|
|
132
|
+
// Thrown here, at Op-construction time — the same "refuse in the factory,
|
|
133
|
+
// named, before anything runs" shape ApplyOp's `compensate` check already
|
|
134
|
+
// uses. Cross-Op refusals that need the *whole* discovered graph (an
|
|
135
|
+
// unknown op referenced by `run()`, a mutating/destructive dispatch target
|
|
136
|
+
// whose own verb class the dial disallows) can't be checked here — a
|
|
137
|
+
// sibling `*.op.ts` file's Op may not exist yet at the moment this factory
|
|
138
|
+
// runs — and are instead OPS014's job
|
|
139
|
+
// (../../lint/rules/op/ops014-converge-rule-refusals.ts), which runs after
|
|
140
|
+
// the whole project is resolved.
|
|
141
|
+
|
|
142
|
+
if (config.rules.length === 0) {
|
|
143
|
+
throw new Error(`ConvergeOp "${config.name}": at least one rule is required — an empty table has nothing to converge.`);
|
|
144
|
+
}
|
|
145
|
+
const dupes = duplicateRuleIds(config.rules);
|
|
146
|
+
if (dupes.length > 0) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`ConvergeOp "${config.name}": duplicate rule id(s) [${dupes.join(", ")}] — flap-damping counters are keyed by id, so ids must be unique.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (!Number.isInteger(budget) || budget < 0) {
|
|
152
|
+
throw new Error(`ConvergeOp "${config.name}": budget must be a non-negative integer, got ${budget}`);
|
|
153
|
+
}
|
|
154
|
+
for (const rule of config.rules) {
|
|
155
|
+
if (!rule.why || rule.why.trim().length === 0) {
|
|
156
|
+
// Belt-and-suspenders: when() already refuses this, but a rule table
|
|
157
|
+
// assembled by hand (bypassing when()) reaches here too.
|
|
158
|
+
throw new Error(`ConvergeOp "${config.name}", rule "${rule.id}": every rule must carry its why — refused at build.`);
|
|
159
|
+
}
|
|
160
|
+
if (!isWellFormedPredicate(rule.when, CONVERGE_SYMPTOM_FIELDS)) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`ConvergeOp "${config.name}", rule "${rule.id}": predicate is outside the evaluable subset — build it from ` +
|
|
163
|
+
`eq/neq/gt/gte/lt/lte/truthy/falsy/allOf/anyOf over a field ConvergeSymptom actually produces.`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const snapshotStep = activity("lifecycleSnapshot", { env: config.env }, { id: "snapshot" });
|
|
169
|
+
const diffStep = activity("lifecycleDiff", { env: config.env, live }, { id: "diff", profile: "fastIdempotent" });
|
|
170
|
+
diffStep.outcomeAttribute = { name: "Drift", from: "drifted" };
|
|
171
|
+
|
|
172
|
+
// convergeTick's args are the rule table itself — too shaped (recursive
|
|
173
|
+
// predicates, a discriminated action union) for a hand-mirrored zod
|
|
174
|
+
// contract to earn its keep, the same "deliberately partial" call
|
|
175
|
+
// activity-contracts.ts already makes for kubectlApply/helmInstall's
|
|
176
|
+
// multi-field args. OPS012 skips an uncontracted `fn`, not flags it — see
|
|
177
|
+
// that module's doc. The `preflightDrift` step-output reference below is
|
|
178
|
+
// still fully validated: OPS013's producer-side checks only need
|
|
179
|
+
// `lifecycleDiff`'s own contract, which is registered.
|
|
180
|
+
const tickStep = activity(
|
|
181
|
+
"convergeTick",
|
|
182
|
+
{
|
|
183
|
+
opName: config.name,
|
|
184
|
+
env: config.env,
|
|
185
|
+
dial,
|
|
186
|
+
budget,
|
|
187
|
+
rules: config.rules,
|
|
188
|
+
preflightDrift: diffStep.out.drifted,
|
|
189
|
+
},
|
|
190
|
+
"longInfra",
|
|
191
|
+
);
|
|
192
|
+
tickStep.outcomeAttribute = { name: "Remediated", from: "remediated" };
|
|
193
|
+
|
|
194
|
+
const op = Op({
|
|
195
|
+
name: config.name,
|
|
196
|
+
overview: `Converge the ${config.env} environment toward its declaration (dial: ${dial})`,
|
|
197
|
+
labels: {
|
|
198
|
+
Converge: "true",
|
|
199
|
+
Env: config.env,
|
|
200
|
+
Dial: dial,
|
|
201
|
+
},
|
|
202
|
+
// Overlap (#1484 acceptance criterion: "skip-and-report when a prior
|
|
203
|
+
// remediation is in flight ... never queue"). `"skip"` is the only value
|
|
204
|
+
// `OpSchedule` has, and it means exactly that: a fire arriving while the
|
|
205
|
+
// previous tick still runs is dropped rather than queued or run
|
|
206
|
+
// alongside. There is no per-tick "in flight" ledger record for the
|
|
207
|
+
// skipped fire to produce — nothing ran, so nothing observed, classified,
|
|
208
|
+
// or dispatched.
|
|
209
|
+
...(config.schedule ? { schedule: { cron: config.schedule, overlap: "skip" as const } } : {}),
|
|
210
|
+
phases: [phase("Observe", [snapshotStep, diffStep]), phase("Converge", [tickStep])],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return { op };
|
|
214
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Op composites (#2120) — the named verbs an operator authors instead of
|
|
3
|
+
* hand-writing phases: watch, reconcile, apply, converge, and the three audit
|
|
4
|
+
* runners.
|
|
5
|
+
*
|
|
6
|
+
* They lived in a hosting lexicon while a cadence meant a scheduler resource;
|
|
7
|
+
* now that the cadence is `OpConfig.schedule` they are runtime-neutral, each
|
|
8
|
+
* returns `{ op }`, and nothing here imports a hosting lexicon. A project that
|
|
9
|
+
* wants a real scheduler resource pairs one with the Op by hand (the pattern
|
|
10
|
+
* `lexicons/cedar/src/dogwood/replay-op.ts` documents).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { WatchOp } from "./watch-op";
|
|
14
|
+
export type { WatchOpConfig, WatchOpResources } from "./watch-op";
|
|
15
|
+
export { ReconcileOp } from "./reconcile-op";
|
|
16
|
+
export type { ReconcileOpConfig, ReconcileOpResources } from "./reconcile-op";
|
|
17
|
+
export { ApplyOp } from "./apply-op";
|
|
18
|
+
export type { ApplyOpConfig, ApplyOpResources } from "./apply-op";
|
|
19
|
+
export { ConvergeOp } from "./converge-op";
|
|
20
|
+
export type { ConvergeOpConfig, ConvergeOpResources, ConvergeDial } from "./converge-op";
|
|
21
|
+
export { WorkflowAuditOp } from "./workflow-audit-op";
|
|
22
|
+
export type { WorkflowAuditOpConfig, WorkflowAuditOpResources } from "./workflow-audit-op";
|
|
23
|
+
export { PipelineAuditOp } from "./pipeline-audit-op";
|
|
24
|
+
export type { PipelineAuditOpConfig, PipelineAuditOpResources } from "./pipeline-audit-op";
|
|
25
|
+
export { LexiconUpgradeOp, IN_SCOPE_LEXICONS } from "./lexicon-upgrade-op";
|
|
26
|
+
export type { LexiconUpgradeOpConfig, LexiconUpgradeOpResources } from "./lexicon-upgrade-op";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { LexiconUpgradeOp, IN_SCOPE_LEXICONS } from "./lexicon-upgrade-op";
|
|
3
|
+
|
|
4
|
+
describe("LexiconUpgradeOp composite (#527)", () => {
|
|
5
|
+
test("one-shot form: op only, no cadence", () => {
|
|
6
|
+
const { op } = LexiconUpgradeOp({ lexicon: "aws" });
|
|
7
|
+
expect(op).toBeDefined();
|
|
8
|
+
expect((op as unknown as { props: { schedule?: unknown } }).props.schedule).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("default name is <lexicon>-upgrade", () => {
|
|
12
|
+
const { op } = LexiconUpgradeOp({ lexicon: "k8s" });
|
|
13
|
+
const cfg = (op as unknown as { props: { name: string } }).props;
|
|
14
|
+
expect(cfg.name).toBe("k8s-upgrade");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("dispatches through the lexiconUpgrade activity with the lexicon + mode", () => {
|
|
18
|
+
const { op } = LexiconUpgradeOp({ lexicon: "gitlab", onFinding: "pull-request" });
|
|
19
|
+
const config = (op as unknown as { props: Record<string, unknown> }).props;
|
|
20
|
+
const phases = config.phases as Array<{ steps: Array<Record<string, unknown>> }>;
|
|
21
|
+
const step = phases[0].steps[0];
|
|
22
|
+
expect(step.fn).toBe("lexiconUpgrade");
|
|
23
|
+
expect((step.args as { lexicon: string }).lexicon).toBe("gitlab");
|
|
24
|
+
expect((step.args as { mode: string }).mode).toBe("pull-request");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("report is the default finding mode", () => {
|
|
28
|
+
const { op } = LexiconUpgradeOp({ lexicon: "azure" });
|
|
29
|
+
const config = (op as unknown as { props: Record<string, unknown> }).props;
|
|
30
|
+
const phases = config.phases as Array<{ steps: Array<{ args: { mode: string } }> }>;
|
|
31
|
+
expect(phases[0].steps[0].args.mode).toBe("report");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("scheduled form: the weekly cron rides on the op (#2120)", () => {
|
|
35
|
+
const { op } = LexiconUpgradeOp({
|
|
36
|
+
lexicon: "k8s",
|
|
37
|
+
schedule: "0 6 * * 1",
|
|
38
|
+
onFinding: "pull-request",
|
|
39
|
+
});
|
|
40
|
+
const config = (op as unknown as { props: Record<string, unknown> }).props;
|
|
41
|
+
expect(config.name).toBe("k8s-upgrade");
|
|
42
|
+
expect(config.schedule).toEqual({ cron: "0 6 * * 1", overlap: "skip" });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("surfaces HasUpgrade as a run outcome attribute", () => {
|
|
46
|
+
const { op } = LexiconUpgradeOp({ lexicon: "docker" });
|
|
47
|
+
const config = (op as unknown as { props: Record<string, unknown> }).props;
|
|
48
|
+
const phases = config.phases as Array<{ steps: Array<{ outcomeAttribute?: { name: string; from: string } }> }>;
|
|
49
|
+
expect(phases[0].steps[0].outcomeAttribute).toEqual({ name: "HasUpgrade", from: "hasUpgrade" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("in-scope set is exactly the 9 lexicons (helm/forgejo excluded)", () => {
|
|
53
|
+
// cedar joined with #1650: it pins `CEDAR_WASM_VERSION`, a dependency
|
|
54
|
+
// rather than a downloaded spec, but the pinned-constant machinery the Op
|
|
55
|
+
// drives is identical. helm/forgejo still have no upstream at all.
|
|
56
|
+
expect([...IN_SCOPE_LEXICONS].sort()).toEqual(
|
|
57
|
+
["aws", "azure", "cedar", "docker", "fly", "gcp", "github", "gitlab", "k8s"].sort(),
|
|
58
|
+
);
|
|
59
|
+
expect(IN_SCOPE_LEXICONS).not.toContain("helm");
|
|
60
|
+
expect(IN_SCOPE_LEXICONS).not.toContain("forgejo");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("rejects out-of-scope lexicons at construction", () => {
|
|
64
|
+
for (const l of ["helm", "forgejo"]) {
|
|
65
|
+
expect(() => LexiconUpgradeOp({ lexicon: l as never })).toThrow(/not in scope/);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LexiconUpgradeOp composite — the runner that dogfoods chant to keep each
|
|
3
|
+
* in-scope lexicon current with its upstream spec (#527, epic #523).
|
|
4
|
+
*
|
|
5
|
+
* The shared tooling (#524) does the regen + surface-diff; the pinned (#525)
|
|
6
|
+
* and rolling (#526) pipelines do the detection. This Op wires them into an
|
|
7
|
+
* audit-style operational layer, modeled on `WorkflowAuditOp` / `PipelineAuditOp`:
|
|
8
|
+
* phases detect → run the right check for the lexicon → surface-diff → finding.
|
|
9
|
+
*
|
|
10
|
+
* It runs one-shot on the **local Op executor** via `chant run` — nothing
|
|
11
|
+
* durable is required for an upgrade check (the scheduled CI runner is the
|
|
12
|
+
* trigger). A `schedule` may still be supplied for continuous re-check; it
|
|
13
|
+
* lands on the Op itself (#2120).
|
|
14
|
+
*
|
|
15
|
+
* Lexicon classification (which check the activity dispatches to):
|
|
16
|
+
* PINNED {k8s, gcp, docker, gitlab} → checkPinnedUpgrade
|
|
17
|
+
* ROLLING {aws, azure, github} → checkRollingUpgrade
|
|
18
|
+
* helm / forgejo are excluded — no upstream spec.
|
|
19
|
+
*
|
|
20
|
+
* Finding-modes mirror ReconcileOp: `report` (default, no external services)
|
|
21
|
+
* | `issue` | `pull-request`. For the epic goal, `pull-request` opens/updates a
|
|
22
|
+
* long-lived branch per lexicon.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // one-shot, local executor, report only
|
|
27
|
+
* export const { op } = LexiconUpgradeOp({ lexicon: "aws" });
|
|
28
|
+
*
|
|
29
|
+
* // weekly, opening a PR when an upgrade is ready
|
|
30
|
+
* export const { op } = LexiconUpgradeOp({
|
|
31
|
+
* lexicon: "k8s",
|
|
32
|
+
* schedule: "0 6 * * 1",
|
|
33
|
+
* onFinding: "pull-request",
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @see #524 — shared regen-validate + surface-diff tooling.
|
|
38
|
+
* @see #525 — pinned-version bump pipeline.
|
|
39
|
+
* @see #526 — rolling-spec drift pipeline.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { Op, phase } from "../builders";
|
|
43
|
+
import type { OpResource } from "../resource";
|
|
44
|
+
import type { LexiconUpgradeMode, SupportedLexicon } from "../activities/lexicon-upgrade";
|
|
45
|
+
|
|
46
|
+
/** The in-scope lexicons. helm / forgejo are excluded (no upstream spec). */
|
|
47
|
+
export const IN_SCOPE_LEXICONS: readonly SupportedLexicon[] = [
|
|
48
|
+
"k8s",
|
|
49
|
+
"gcp",
|
|
50
|
+
"docker",
|
|
51
|
+
"gitlab",
|
|
52
|
+
"aws",
|
|
53
|
+
"azure",
|
|
54
|
+
"github",
|
|
55
|
+
"fly",
|
|
56
|
+
"cedar",
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
export interface LexiconUpgradeOpConfig {
|
|
60
|
+
/** Which in-scope lexicon to check. */
|
|
61
|
+
lexicon: SupportedLexicon;
|
|
62
|
+
/**
|
|
63
|
+
* Op name (kebab-case). Names the Op's output directory and is what `chant
|
|
64
|
+
* run` takes. Defaults to `<lexicon>-upgrade`.
|
|
65
|
+
*/
|
|
66
|
+
name?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Cron expression. When set, it lands on the Op as `schedule` for
|
|
69
|
+
* continuous re-check; omit for one-shot `chant run` on the local executor.
|
|
70
|
+
*/
|
|
71
|
+
schedule?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Absolute path to the lexicon root, forwarded to the activity. Defaults to
|
|
74
|
+
* `<cwd>/lexicons/<lexicon>` at run time.
|
|
75
|
+
*/
|
|
76
|
+
lexiconDir?: string;
|
|
77
|
+
/**
|
|
78
|
+
* What to produce on findings. Default: "report".
|
|
79
|
+
* @default "report"
|
|
80
|
+
*/
|
|
81
|
+
onFinding?: LexiconUpgradeMode;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface LexiconUpgradeOpResources {
|
|
85
|
+
/** Op resource — the upgrade Op, emitted on `chant build`. */
|
|
86
|
+
op: InstanceType<typeof OpResource>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build a LexiconUpgradeOp for one lexicon. Rejects out-of-scope lexicons at
|
|
91
|
+
* construction time so a typo never silently produces a no-op Op.
|
|
92
|
+
*/
|
|
93
|
+
export function LexiconUpgradeOp(config: LexiconUpgradeOpConfig): LexiconUpgradeOpResources {
|
|
94
|
+
if (!IN_SCOPE_LEXICONS.includes(config.lexicon)) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`LexiconUpgradeOp: "${config.lexicon}" is not in scope. ` +
|
|
97
|
+
`In-scope lexicons: ${IN_SCOPE_LEXICONS.join(", ")} ` +
|
|
98
|
+
`(helm, forgejo have no upstream spec).`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const name = config.name ?? `${config.lexicon}-upgrade`;
|
|
103
|
+
const onFinding = config.onFinding ?? "report";
|
|
104
|
+
|
|
105
|
+
const op = Op({
|
|
106
|
+
name,
|
|
107
|
+
overview:
|
|
108
|
+
"Detect a newer upstream spec for the lexicon, regen + validate, and surface the API-surface delta",
|
|
109
|
+
labels: {
|
|
110
|
+
Upgrade: "true",
|
|
111
|
+
Lexicon: config.lexicon,
|
|
112
|
+
},
|
|
113
|
+
...(config.schedule ? { schedule: { cron: config.schedule, overlap: "skip" as const } } : {}),
|
|
114
|
+
phases: [
|
|
115
|
+
phase("Upgrade", [
|
|
116
|
+
{
|
|
117
|
+
kind: "activity",
|
|
118
|
+
fn: "lexiconUpgrade",
|
|
119
|
+
args: {
|
|
120
|
+
lexicon: config.lexicon,
|
|
121
|
+
...(config.lexiconDir ? { lexiconDir: config.lexiconDir } : {}),
|
|
122
|
+
mode: onFinding,
|
|
123
|
+
},
|
|
124
|
+
// Surface whether a PR-worthy upgrade was found as the run's
|
|
125
|
+
// `HasUpgrade` outcome on the run ledger, so a reader can pick out
|
|
126
|
+
// the lexicons with an upgrade ready.
|
|
127
|
+
outcomeAttribute: { name: "HasUpgrade", from: "hasUpgrade" },
|
|
128
|
+
},
|
|
129
|
+
]),
|
|
130
|
+
],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return { op };
|
|
134
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { PipelineAuditOp } from "./pipeline-audit-op";
|
|
3
|
+
|
|
4
|
+
function opProps(op: unknown): Record<string, unknown> {
|
|
5
|
+
return (op as { props: Record<string, unknown> }).props;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe("PipelineAuditOp composite (#303)", () => {
|
|
9
|
+
test("one-shot form: op only, no cadence", () => {
|
|
10
|
+
const { op } = PipelineAuditOp({ name: "pipeline-audit" });
|
|
11
|
+
expect(op).toBeDefined();
|
|
12
|
+
expect(opProps(op).schedule).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("scheduled form: the cron rides on the op, with the merge-request finding mode (#2120)", () => {
|
|
16
|
+
const { op } = PipelineAuditOp({
|
|
17
|
+
name: "pipeline-audit",
|
|
18
|
+
schedule: "0 6 * * *",
|
|
19
|
+
onFinding: "merge-request",
|
|
20
|
+
});
|
|
21
|
+
expect(opProps(op).schedule).toEqual({ cron: "0 6 * * *", overlap: "skip" });
|
|
22
|
+
const step = (opProps(op).phases as Array<{ steps: Array<{ args: { mode: string } }> }>)[0].steps[0];
|
|
23
|
+
expect(step.args.mode).toBe("merge-request");
|
|
24
|
+
});
|
|
25
|
+
});
|