@intentius/chant 0.79.0 → 0.81.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/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +30 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +13 -0
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/command-group.d.ts +6 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +44 -3
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +6 -0
- 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.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/conflict-check.d.ts +6 -0
- package/dist/cli/conflict-check.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +20 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -0
- package/dist/cli/handlers/run-generate.d.ts +39 -0
- package/dist/cli/handlers/run-generate.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +14 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +46 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
- package/dist/components/presets/ecs-fargate.d.ts +10 -4
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
- package/dist/components/promote.d.ts +264 -0
- package/dist/components/promote.d.ts.map +1 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +104 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +48 -4
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +32 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +17 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +110 -0
- package/dist/lexicon-module.d.ts.map +1 -0
- package/dist/lexicon.d.ts +82 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +59 -0
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +42 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +146 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +22 -0
- package/dist/workspace/record-source.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +81 -0
- package/dist/workspace/records-cli.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +118 -0
- package/dist/workspace/records.d.ts.map +1 -0
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +4 -3
- package/src/audit/core.ts +13 -2
- package/src/audit/discover.ts +102 -36
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +81 -1
- package/src/audit/terraform-state.ts +25 -0
- package/src/cli/command-group.ts +7 -0
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +132 -29
- package/src/cli/commands/check-lexicon-examples.ts +2 -1
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +21 -0
- package/src/cli/commands/import-agents.ts +15 -4
- package/src/cli/commands/init.ts +76 -25
- package/src/cli/commands/lint.test.ts +52 -1
- package/src/cli/commands/lint.ts +41 -71
- package/src/cli/commands/onboard.ts +15 -0
- package/src/cli/commands/update.ts +18 -5
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/conflict-check.test.ts +18 -1
- package/src/cli/conflict-check.ts +13 -0
- package/src/cli/discovery-skip.test.ts +168 -0
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.test.ts +11 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +6 -1
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +4 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/lifecycle.ts +2 -1
- package/src/cli/handlers/misc.ts +16 -0
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +39 -10
- package/src/cli/handlers/promote.test.ts +377 -0
- package/src/cli/handlers/promote.ts +391 -0
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +158 -0
- package/src/cli/handlers/run-generate.ts +168 -0
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +41 -6
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +137 -1
- package/src/cli/main.ts +290 -24
- package/src/cli/mcp/op-tools.ts +2 -1
- package/src/cli/path-lexicon-messages.test.ts +152 -0
- package/src/cli/plugins.ts +43 -5
- package/src/cli/registry.ts +46 -1
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/__fixtures__/alb-ecs-service.json +0 -8
- package/src/components/capability-plugin-loader.ts +3 -1
- package/src/components/cli-support.ts +9 -3
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +20 -51
- package/src/components/driver.test.ts +90 -12
- package/src/components/driver.ts +43 -5
- package/src/components/fan-out-support.ts +2 -1
- package/src/components/gate-plan.ts +53 -0
- package/src/components/pilots/README.md +1 -1
- package/src/components/pilots/alb-ecs.pilot.ts +7 -7
- package/src/components/presets/ecs-fargate.ts +10 -5
- package/src/components/promote.test.ts +645 -0
- package/src/components/promote.ts +641 -0
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +40 -0
- package/src/config.ts +161 -11
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +127 -1
- package/src/discovery/files.ts +91 -59
- package/src/discovery/fold-import.ts +127 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +18 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.test.ts +203 -0
- package/src/lexicon-module.ts +215 -0
- package/src/lexicon.ts +105 -1
- package/src/lifecycle/build-ledger-store.ts +13 -2
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +206 -0
- package/src/lifecycle/legacy-digest.ts +103 -0
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +53 -3
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/activity-contract-registry.ts +18 -4
- package/src/op/activity-registry.ts +26 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -23
- package/src/op/gate-approval.ts +3 -1
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/op/generate-pipeline.ts +2 -1
- package/src/op/runtimes/local.ts +2 -1
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +162 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +624 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/record-source.ts +117 -0
- package/src/workspace/records-cli.ts +198 -0
- package/src/workspace/records-contract.test.ts +93 -0
- package/src/workspace/records.schema.json +150 -0
- package/src/workspace/records.test.ts +243 -0
- package/src/workspace/records.ts +384 -0
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { CapabilityRegistry } from "./capability";
|
|
25
|
+
import { lexiconModulePath } from "../lexicon-module";
|
|
25
26
|
import type { CapabilityPlugin } from "./capability-plugin";
|
|
26
27
|
import { isCapabilityPlugin } from "./capability-plugin";
|
|
27
28
|
import { starterCapabilityPlugin } from "./starter-plugin";
|
|
@@ -113,7 +114,8 @@ export async function loadCapabilityPlugins(names: string[]): Promise<Capability
|
|
|
113
114
|
* where the package's whole reason to exist is the plugin.
|
|
114
115
|
*/
|
|
115
116
|
export async function loadCapabilityPluginFromLexicon(name: string): Promise<CapabilityPlugin | null> {
|
|
116
|
-
|
|
117
|
+
// chant #2520 — a lexicon declared by module path is imported from that path.
|
|
118
|
+
const packageName = lexiconModulePath(name) ?? `@intentius/chant-lexicon-${name}`;
|
|
117
119
|
let mod: Record<string, unknown>;
|
|
118
120
|
try {
|
|
119
121
|
mod = (await import(packageName)) as Record<string, unknown>;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* executor, the CLI entrypoint the driver (#556) never had.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { lexiconModulePath, lexiconNames } from "../lexicon-module";
|
|
25
26
|
import { discoverComponents } from "./discover";
|
|
26
27
|
import type { BuildParamProvenance } from "../provenance";
|
|
27
28
|
import { projectToJson, type Archetype } from "./component";
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
type DriverRunResult,
|
|
39
40
|
} from "./driver";
|
|
40
41
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
41
|
-
import type { GateLedgerPort } from "../op/gate";
|
|
42
|
+
import type { GateDigestMismatch, GateLedgerPort } from "../op/gate";
|
|
42
43
|
import { gateName } from "../op/gate-name";
|
|
43
44
|
import { isLexiconPlugin, type LexiconPlugin, type ComponentPipelineOptions } from "../lexicon";
|
|
44
45
|
import type { RunProgressEvent } from "./run-progress";
|
|
@@ -237,7 +238,7 @@ export interface GenerateComponentsResult {
|
|
|
237
238
|
async function loadLexiconPlugin(name: string): Promise<LexiconPlugin | null> {
|
|
238
239
|
let mod: Record<string, unknown>;
|
|
239
240
|
try {
|
|
240
|
-
mod = (await import(`@intentius/chant-lexicon-${name}`)) as Record<string, unknown>;
|
|
241
|
+
mod = (await import(lexiconModulePath(name) ?? `@intentius/chant-lexicon-${name}`)) as Record<string, unknown>;
|
|
241
242
|
} catch {
|
|
242
243
|
return null;
|
|
243
244
|
}
|
|
@@ -431,6 +432,8 @@ export interface RunComponentsResult {
|
|
|
431
432
|
/** Whether this run's own append reached the remote (#2310); absent when the pending fact was already standing. */
|
|
432
433
|
pushed?: boolean;
|
|
433
434
|
pushWarning?: string;
|
|
435
|
+
/** Set when an approval stands for this gate, but for another plan or none (#2574). */
|
|
436
|
+
mismatch?: GateDigestMismatch;
|
|
434
437
|
};
|
|
435
438
|
/** This run's resolved build-time parameters (chant #1108) — the component-driver counterpart of `../cli/commands/build.ts`'s `BuildResult.buildParams`. Present only once the run actually reached dispatch (mirrors `BuildResult.buildParams`, which is likewise absent on an early-error return). */
|
|
436
439
|
buildParams?: BuildParamProvenance[];
|
|
@@ -550,7 +553,7 @@ export async function runComponents(
|
|
|
550
553
|
options.registry ??
|
|
551
554
|
(await buildCapabilityRegistry({
|
|
552
555
|
plugins: options.capabilityPlugins ?? config.capabilities,
|
|
553
|
-
lexicons: config.lexicons,
|
|
556
|
+
lexicons: lexiconNames(config.lexicons),
|
|
554
557
|
}));
|
|
555
558
|
const env = options.env ?? "local";
|
|
556
559
|
const selected = targets.map((c) => c.name);
|
|
@@ -583,6 +586,7 @@ export async function runComponents(
|
|
|
583
586
|
gate: run.gate,
|
|
584
587
|
...(run.gatePushed !== undefined ? { pushed: run.gatePushed } : {}),
|
|
585
588
|
...(run.gatePushWarning ? { pushWarning: run.gatePushWarning } : {}),
|
|
589
|
+
...(run.gateMismatch ? { mismatch: run.gateMismatch } : {}),
|
|
586
590
|
},
|
|
587
591
|
}
|
|
588
592
|
: {}),
|
|
@@ -631,6 +635,7 @@ export async function runComponents(
|
|
|
631
635
|
gate: componentResult.gate,
|
|
632
636
|
...(componentResult.gatePushed !== undefined ? { gatePushed: componentResult.gatePushed } : {}),
|
|
633
637
|
...(componentResult.gatePushWarning ? { gatePushWarning: componentResult.gatePushWarning } : {}),
|
|
638
|
+
...(componentResult.gateMismatch ? { gateMismatch: componentResult.gateMismatch } : {}),
|
|
634
639
|
}
|
|
635
640
|
: {}),
|
|
636
641
|
componentOutputs,
|
|
@@ -647,6 +652,7 @@ export async function runComponents(
|
|
|
647
652
|
gate: componentResult.gate,
|
|
648
653
|
...(componentResult.gatePushed !== undefined ? { pushed: componentResult.gatePushed } : {}),
|
|
649
654
|
...(componentResult.gatePushWarning ? { pushWarning: componentResult.gatePushWarning } : {}),
|
|
655
|
+
...(componentResult.gateMismatch ? { mismatch: componentResult.gateMismatch } : {}),
|
|
650
656
|
},
|
|
651
657
|
}
|
|
652
658
|
: {}),
|
|
@@ -280,76 +280,46 @@ describe("discoverComponents", () => {
|
|
|
280
280
|
expect(result.errors.some((e) => e.type === "import")).toBe(true);
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// directory — the skip only applies to a *second* nested config found
|
|
287
|
-
// after that (see the next test).
|
|
288
|
-
const childDir = join(testDir, "child-project");
|
|
289
|
-
await mkdir(childDir, { recursive: true });
|
|
290
|
-
await writeFile(join(childDir, "chant.config.ts"), `export default {};`);
|
|
291
|
-
await writeFile(
|
|
292
|
-
join(childDir, "child.component.ts"),
|
|
293
|
-
`
|
|
294
|
-
export const childSvc = {
|
|
295
|
-
name: "child-svc",
|
|
296
|
-
dependsOn: [],
|
|
297
|
-
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
298
|
-
};
|
|
299
|
-
`,
|
|
300
|
-
);
|
|
301
|
-
await writeFile(
|
|
302
|
-
join(testDir, "root.component.ts"),
|
|
303
|
-
`
|
|
304
|
-
export const rootSvc = {
|
|
305
|
-
name: "root-svc",
|
|
283
|
+
const svc = (exportName: string, name: string) => `
|
|
284
|
+
export const ${exportName} = {
|
|
285
|
+
name: "${name}",
|
|
306
286
|
dependsOn: [],
|
|
307
287
|
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
308
288
|
};
|
|
309
|
-
|
|
310
|
-
);
|
|
289
|
+
`;
|
|
311
290
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test("a second nested chant.config.ts (a true child project) is not descended into", async () => {
|
|
291
|
+
test("from outside every project, every child project is read (#2527)", async () => {
|
|
292
|
+
// The source-root quirk is gone: no child config is special for being
|
|
293
|
+
// found first. The scan root has no config at or above it, so every
|
|
294
|
+
// child project's components are discovered, nested ones included.
|
|
319
295
|
const firstConfigDir = join(testDir, "first-config");
|
|
320
296
|
const nestedChildProject = join(firstConfigDir, "nested-child-project");
|
|
321
297
|
await mkdir(nestedChildProject, { recursive: true });
|
|
322
298
|
await writeFile(join(firstConfigDir, "chant.config.ts"), `export default {};`);
|
|
323
299
|
await writeFile(join(nestedChildProject, "chant.config.ts"), `export default {};`);
|
|
324
|
-
await writeFile(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
export const firstSvc = {
|
|
328
|
-
name: "first-svc",
|
|
329
|
-
dependsOn: [],
|
|
330
|
-
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
331
|
-
};
|
|
332
|
-
`,
|
|
333
|
-
);
|
|
334
|
-
await writeFile(
|
|
335
|
-
join(nestedChildProject, "nested.component.ts"),
|
|
336
|
-
`
|
|
337
|
-
export const nestedSvc = {
|
|
338
|
-
name: "nested-svc",
|
|
339
|
-
dependsOn: [],
|
|
340
|
-
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
341
|
-
};
|
|
342
|
-
`,
|
|
343
|
-
);
|
|
300
|
+
await writeFile(join(testDir, "root.component.ts"), svc("rootSvc", "root-svc"));
|
|
301
|
+
await writeFile(join(firstConfigDir, "first.component.ts"), svc("firstSvc", "first-svc"));
|
|
302
|
+
await writeFile(join(nestedChildProject, "nested.component.ts"), svc("nestedSvc", "nested-svc"));
|
|
344
303
|
|
|
345
304
|
const result = await discoverComponents(testDir);
|
|
346
305
|
|
|
347
|
-
|
|
348
|
-
// and is scanned; nested-child-project/'s own chant.config.ts is the
|
|
349
|
-
// *second* config encountered, so it is treated as a separate project
|
|
350
|
-
// scope and not descended into.
|
|
306
|
+
expect(result.components.has("root-svc")).toBe(true);
|
|
351
307
|
expect(result.components.has("first-svc")).toBe(true);
|
|
352
|
-
expect(result.components.has("nested-svc")).toBe(
|
|
308
|
+
expect(result.components.has("nested-svc")).toBe(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("from inside a project, a child project is a boundary (#2527)", async () => {
|
|
312
|
+
const childDir = join(testDir, "child-project");
|
|
313
|
+
await mkdir(childDir, { recursive: true });
|
|
314
|
+
await writeFile(join(testDir, "chant.config.ts"), `export default {};`);
|
|
315
|
+
await writeFile(join(childDir, "chant.config.ts"), `export default {};`);
|
|
316
|
+
await writeFile(join(testDir, "root.component.ts"), svc("rootSvc", "root-svc"));
|
|
317
|
+
await writeFile(join(childDir, "child.component.ts"), svc("childSvc", "child-svc"));
|
|
318
|
+
|
|
319
|
+
const result = await discoverComponents(testDir);
|
|
320
|
+
|
|
321
|
+
expect(result.components.has("root-svc")).toBe(true);
|
|
322
|
+
expect(result.components.has("child-svc")).toBe(false);
|
|
353
323
|
});
|
|
354
324
|
|
|
355
325
|
// ── chant #1108 — build-time parameters populated before import ────────────
|
|
@@ -40,15 +40,16 @@
|
|
|
40
40
|
* `./component.ts`, already proves the plain-JSON round-trip).
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import {
|
|
44
|
-
import { join, resolve } from "node:path";
|
|
43
|
+
import { resolve } from "node:path";
|
|
45
44
|
import { pathToFileURL } from "node:url";
|
|
46
|
-
import { existsSync } from "node:fs";
|
|
47
45
|
import { DiscoveryError } from "../errors";
|
|
48
46
|
import { isComponent, type Component } from "./component";
|
|
49
47
|
import type { BuildParamProvenance } from "../provenance";
|
|
50
48
|
import { buildParamValues } from "../build-params";
|
|
51
49
|
import { setBuildParams } from "../params";
|
|
50
|
+
import { hasDiscoveryMarkerSync } from "../discovery/files";
|
|
51
|
+
import { resolveDiscoveryGlobs } from "../config";
|
|
52
|
+
import { walkDiscovery, workspaceMemberDirs } from "../discovery/walk";
|
|
52
53
|
|
|
53
54
|
/** One discovered component, paired with the file it was exported from. */
|
|
54
55
|
export interface DiscoveredComponent {
|
|
@@ -115,56 +116,24 @@ export interface ImportedComponentModule {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
119
|
+
* Every `*.component.ts` file under `path`, through the one discovery walk
|
|
120
|
+
* (`../discovery/walk.ts`, #2527): the same dot-directory, `dist`,
|
|
121
|
+
* git-ignore and child-project rules as source discovery, the project's
|
|
122
|
+
* `exclude`/`include` globs and the skip marker (#2519). Scanned from outside
|
|
123
|
+
* any project, every child project is read.
|
|
122
124
|
*/
|
|
123
125
|
async function findComponentFiles(path: string): Promise<string[]> {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
for (const entry of entries) {
|
|
136
|
-
const fullPath = join(dir, entry.name);
|
|
137
|
-
|
|
138
|
-
if (entry.isDirectory()) {
|
|
139
|
-
// Skip dependency and tool/VCS directories: component sources never live
|
|
140
|
-
// in them, and descending in is both wasted work and a correctness bug —
|
|
141
|
-
// e.g. a CI runner like gitlab-ci-local stages a copy of the project
|
|
142
|
-
// under `.gitlab-ci-local/`, which discovery would otherwise pick up as
|
|
143
|
-
// duplicate component names alongside the originals.
|
|
144
|
-
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
145
|
-
|
|
146
|
-
const configPath = join(fullPath, "chant.config.ts");
|
|
147
|
-
if (existsSync(configPath)) {
|
|
148
|
-
if (sourceRoot === null) {
|
|
149
|
-
sourceRoot = fullPath;
|
|
150
|
-
} else {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
await scanDirectory(fullPath);
|
|
155
|
-
} else if (
|
|
156
|
-
entry.isFile() &&
|
|
157
|
-
entry.name.endsWith(".component.ts") &&
|
|
158
|
-
!entry.name.endsWith(".test.component.ts") &&
|
|
159
|
-
!entry.name.endsWith(".spec.component.ts")
|
|
160
|
-
) {
|
|
161
|
-
files.push(fullPath);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
await scanDirectory(path);
|
|
167
|
-
return files;
|
|
126
|
+
return walkDiscovery({
|
|
127
|
+
walker: "components",
|
|
128
|
+
root: path,
|
|
129
|
+
globs: await resolveDiscoveryGlobs(path),
|
|
130
|
+
excludeDirs: await workspaceMemberDirs(path),
|
|
131
|
+
accept: (name, full) =>
|
|
132
|
+
name.endsWith(".component.ts") &&
|
|
133
|
+
!name.endsWith(".test.component.ts") &&
|
|
134
|
+
!name.endsWith(".spec.component.ts") &&
|
|
135
|
+
!hasDiscoveryMarkerSync(full),
|
|
136
|
+
});
|
|
168
137
|
}
|
|
169
138
|
|
|
170
139
|
/**
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import { describe, expect, it } from "vitest";
|
|
14
14
|
import { CapabilityRegistry, type DeployContext } from "./capability";
|
|
15
15
|
import { stubCapability } from "./verbs/stub";
|
|
16
|
-
import { memoryGateLedgerPort } from "../op/gate";
|
|
16
|
+
import { describeGateMismatch, memoryGateLedgerPort } from "../op/gate";
|
|
17
|
+
import { componentPlanDigest } from "./gate-plan";
|
|
17
18
|
import type { PendingGateInput } from "../lifecycle/gate-ledger";
|
|
18
19
|
import {
|
|
19
20
|
DependencyCycleError,
|
|
@@ -311,14 +312,17 @@ describe("runComponentDeploy — gate as fact (#2119)", () => {
|
|
|
311
312
|
|
|
312
313
|
it("a resolution newer than the pending fact passes the gate and carries the approver", async () => {
|
|
313
314
|
const { registry, calls } = registryWithCalls();
|
|
315
|
+
const planDigest = componentPlanDigest({ environment: "dev", component: gatedComponent() });
|
|
314
316
|
const port = memoryGateLedgerPort({
|
|
315
317
|
pending: [{
|
|
316
318
|
version: 1, kind: "pending", op: "neo4j-cluster", gate: "approve-node-1",
|
|
317
319
|
timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
|
|
320
|
+
planDigest, environment: "dev",
|
|
318
321
|
}],
|
|
319
322
|
resolutions: [{
|
|
320
323
|
version: 1, op: "neo4j-cluster", gate: "approve-node-1",
|
|
321
324
|
resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z",
|
|
325
|
+
planDigest, environment: "dev",
|
|
322
326
|
}],
|
|
323
327
|
});
|
|
324
328
|
const result = await runComponentDeploy(
|
|
@@ -332,6 +336,79 @@ describe("runComponentDeploy — gate as fact (#2119)", () => {
|
|
|
332
336
|
.toEqual({ gate: "approve-node-1", resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z" });
|
|
333
337
|
});
|
|
334
338
|
|
|
339
|
+
// #2574: a component gate approval is bound to the environment and the plan.
|
|
340
|
+
describe("approvals bound to environment and plan (#2574)", () => {
|
|
341
|
+
const run = (port: ReturnType<typeof memoryGateLedgerPort>, env: string, component = gatedComponent()) =>
|
|
342
|
+
runComponentDeploy(component, { env, component: "neo4j-cluster" }, registryWithCalls().registry, {}, undefined, { port, now: NOW });
|
|
343
|
+
|
|
344
|
+
it("the pending fact records the environment and the plan", async () => {
|
|
345
|
+
const port = memoryGateLedgerPort();
|
|
346
|
+
const result = await run(port, "staging");
|
|
347
|
+
expect(result.gate).toMatchObject({
|
|
348
|
+
environment: "staging",
|
|
349
|
+
planDigest: componentPlanDigest({ environment: "staging", component: gatedComponent() }),
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("an approval given for another environment does not pass the gate", async () => {
|
|
354
|
+
const port = memoryGateLedgerPort();
|
|
355
|
+
const staging = await run(port, "staging");
|
|
356
|
+
const approval = {
|
|
357
|
+
version: 1 as const, op: "neo4j-cluster", gate: "approve-node-1", resolvedBy: "alex",
|
|
358
|
+
timestamp: "2026-09-05T12:30:00.000Z",
|
|
359
|
+
planDigest: staging.gate!.planDigest!, environment: "staging",
|
|
360
|
+
};
|
|
361
|
+
const withApproval = memoryGateLedgerPort({ pending: [staging.gate!], resolutions: [approval] });
|
|
362
|
+
|
|
363
|
+
expect((await run(withApproval, "staging")).status).toBe("ok");
|
|
364
|
+
const prod = await run(withApproval, "prod");
|
|
365
|
+
expect(prod.status).toBe("gated");
|
|
366
|
+
expect(prod.gate).toMatchObject({ environment: "prod" });
|
|
367
|
+
// The staging approval is not this run's business, so it is not reported as a mismatch either.
|
|
368
|
+
expect(prod.gateMismatch).toBeUndefined();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("an approval given for another plan in the same environment is refused and named", async () => {
|
|
372
|
+
const port = memoryGateLedgerPort();
|
|
373
|
+
const before = await run(port, "prod");
|
|
374
|
+
const approval = {
|
|
375
|
+
version: 1 as const, op: "neo4j-cluster", gate: "approve-node-1", resolvedBy: "alex",
|
|
376
|
+
timestamp: "2026-09-05T12:30:00.000Z",
|
|
377
|
+
planDigest: before.gate!.planDigest!, environment: "prod",
|
|
378
|
+
};
|
|
379
|
+
const changed = gatedComponent();
|
|
380
|
+
changed.deploy[1]!.steps.push({ kind: "cfn-deploy", stack: "extra" });
|
|
381
|
+
const result = await run(memoryGateLedgerPort({ pending: [before.gate!], resolutions: [approval] }), "prod", changed);
|
|
382
|
+
|
|
383
|
+
expect(result.status).toBe("gated");
|
|
384
|
+
expect(result.gateMismatch).toMatchObject({
|
|
385
|
+
approved: before.gate!.planDigest, planned: result.gate!.planDigest, environment: "prod", resolvedBy: "alex",
|
|
386
|
+
});
|
|
387
|
+
expect(result.gate!.planDigest).not.toBe(before.gate!.planDigest);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("an approval recorded before the binding is refused, with the command that replaces it", async () => {
|
|
391
|
+
const legacyPending = {
|
|
392
|
+
version: 1 as const, kind: "pending" as const, op: "neo4j-cluster", gate: "approve-node-1",
|
|
393
|
+
timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
|
|
394
|
+
};
|
|
395
|
+
const legacy = {
|
|
396
|
+
version: 1 as const, op: "neo4j-cluster", gate: "approve-node-1",
|
|
397
|
+
resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z",
|
|
398
|
+
};
|
|
399
|
+
const port = memoryGateLedgerPort({ pending: [legacyPending], resolutions: [legacy] });
|
|
400
|
+
const result = await run(port, "prod");
|
|
401
|
+
|
|
402
|
+
expect(result.status).toBe("gated");
|
|
403
|
+
expect(port.appended).toHaveLength(1);
|
|
404
|
+
expect(result.gateMismatch).toMatchObject({ resolvedBy: "alex", environment: "prod" });
|
|
405
|
+
expect(result.gateMismatch!.approved).toBeUndefined();
|
|
406
|
+
const message = describeGateMismatch("neo4j-cluster", "approve-node-1", result.gateMismatch!);
|
|
407
|
+
expect(message).toContain("predates environment- and plan-bound component gates (#2574)");
|
|
408
|
+
expect(message).toContain("chant approve neo4j-cluster approve-node-1 --env prod");
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
335
412
|
it("the neo4j pilot's gated Node 1 phase ends the run pending approval", async () => {
|
|
336
413
|
const registry = new CapabilityRegistry();
|
|
337
414
|
for (const kind of ["cfn-deploy", "code-deploy", "wait-cluster-healthy"]) {
|
|
@@ -450,7 +527,7 @@ describe("runComponentDeploy — onFailure saga rollback", () => {
|
|
|
450
527
|
);
|
|
451
528
|
});
|
|
452
529
|
|
|
453
|
-
it("runs a component-level rollback (schema `rollback` field) after step rollback
|
|
530
|
+
it("runs a component-level rollback (schema `rollback` field) after step rollback", async () => {
|
|
454
531
|
const registry = new CapabilityRegistry();
|
|
455
532
|
registry.register(fakeCapability("docker-build", { run: () => ({ digest: "sha256:abc" }) }).capability);
|
|
456
533
|
registry.register(
|
|
@@ -458,26 +535,29 @@ describe("runComponentDeploy — onFailure saga rollback", () => {
|
|
|
458
535
|
.capability,
|
|
459
536
|
);
|
|
460
537
|
registry.register(fakeCapability("cfn-deploy", { run: () => ({ stackStatus: "UPDATE_COMPLETE", outputs: {} }) }).capability);
|
|
461
|
-
// ecs-update-service fails
|
|
462
|
-
//
|
|
538
|
+
// ecs-update-service fails; the component's own `rollback` phase is what
|
|
539
|
+
// should fire after the executed steps unwind.
|
|
463
540
|
registry.register(fakeCapability("ecs-update-service", { failRun: true }).capability);
|
|
464
|
-
const { capability:
|
|
541
|
+
const { capability: restoreCap, calls: restoreCalls } = fakeCapability("restore-service", {
|
|
465
542
|
run: () => ({ restored: true }),
|
|
466
543
|
});
|
|
467
|
-
registry.register(
|
|
544
|
+
registry.register(restoreCap);
|
|
468
545
|
// Verify/rollback phases never reached since Apply fails first, but register
|
|
469
546
|
// stubs anyway so a hypothetical reordering wouldn't crash the test on an
|
|
470
547
|
// unregistered kind.
|
|
471
548
|
registry.register(fakeCapability("wait-steady-state", { run: () => ({ runningCount: 1 }) }).capability);
|
|
472
549
|
registry.register(fakeCapability("health-gate", { run: () => ({ healthy: true }) }).capability);
|
|
473
550
|
|
|
474
|
-
const json =
|
|
551
|
+
const json: DriverComponent = {
|
|
552
|
+
...(projectToJson(searchService) as unknown as DriverComponent),
|
|
553
|
+
rollback: [{ phase: "Rollback", steps: [{ kind: "restore-service", service: "search" }] }],
|
|
554
|
+
};
|
|
475
555
|
const result = await runComponentDeploy(json, { env: "dev", component: "search-service" }, registry, {});
|
|
476
556
|
|
|
477
557
|
expect(result.ok).toBe(false);
|
|
478
|
-
expect(
|
|
479
|
-
expect(
|
|
480
|
-
const rollbackRecord = result.records.find((r) => r.phase === "Rollback" && r.kind === "
|
|
558
|
+
expect(restoreCalls).toHaveLength(1);
|
|
559
|
+
expect(restoreCalls[0]!.input).toMatchObject({ service: "search" });
|
|
560
|
+
const rollbackRecord = result.records.find((r) => r.phase === "Rollback" && r.kind === "restore-service");
|
|
481
561
|
expect(rollbackRecord?.status).toBe("ok");
|
|
482
562
|
});
|
|
483
563
|
});
|
|
@@ -630,7 +710,6 @@ describe("runInterpretDriver — end to end", () => {
|
|
|
630
710
|
"ecs-update-service",
|
|
631
711
|
"wait-steady-state",
|
|
632
712
|
"health-gate",
|
|
633
|
-
"rollback-previous",
|
|
634
713
|
"wait-for-stack",
|
|
635
714
|
"run-migration",
|
|
636
715
|
];
|
|
@@ -750,7 +829,6 @@ describe("runInterpretDriver — end to end", () => {
|
|
|
750
829
|
"ecs-update-service",
|
|
751
830
|
"wait-steady-state",
|
|
752
831
|
"health-gate",
|
|
753
|
-
"rollback-previous",
|
|
754
832
|
"wait-for-stack",
|
|
755
833
|
"run-migration",
|
|
756
834
|
"lambda-deploy",
|
package/src/components/driver.ts
CHANGED
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import { topoSort } from "../codegen/topo-sort";
|
|
50
|
-
import { evaluateGate, gitGateLedgerPort, type GateLedgerPort } from "../op/gate";
|
|
50
|
+
import { evaluateGate, gitGateLedgerPort, type GateDigestMismatch, type GateLedgerPort } from "../op/gate";
|
|
51
51
|
import { gateName } from "../op/gate-name";
|
|
52
|
+
import { componentPlanDigest } from "./gate-plan";
|
|
52
53
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
53
54
|
import type { CapabilityRegistry, DeployContext } from "./capability";
|
|
54
55
|
import type { RunProgressEvent, RunProgressStatus } from "./run-progress";
|
|
@@ -167,6 +168,8 @@ export interface DriverComponentResult {
|
|
|
167
168
|
gatePushed?: boolean;
|
|
168
169
|
/** Set when `gatePushed` is false: why, in one line. */
|
|
169
170
|
gatePushWarning?: string;
|
|
171
|
+
/** Present when `status === "gated"` because an approval stands for this gate, but for another plan or none (#2574). */
|
|
172
|
+
gateMismatch?: GateDigestMismatch;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
175
|
export interface DriverRunResult {
|
|
@@ -188,6 +191,8 @@ export interface DriverRunResult {
|
|
|
188
191
|
gatePushed?: boolean;
|
|
189
192
|
/** Set when `gatePushed` is false: why, in one line. */
|
|
190
193
|
gatePushWarning?: string;
|
|
194
|
+
/** Present when `status === "gated"` because an approval stands for another plan or none (#2574). */
|
|
195
|
+
gateMismatch?: GateDigestMismatch;
|
|
191
196
|
/**
|
|
192
197
|
* The accumulated cross-component/cross-stack outputs after the run — each
|
|
193
198
|
* component's `publish` output and, for an applied stack, its `cfn-deploy`
|
|
@@ -370,6 +375,8 @@ class GateStop extends Error {
|
|
|
370
375
|
/** Whether this run's own append reached the remote — see {@link DriverComponentResult.gatePushed} (#2310). */
|
|
371
376
|
public readonly pushed?: boolean,
|
|
372
377
|
public readonly pushWarning?: string,
|
|
378
|
+
/** Set when an approval stands for this gate but not for this plan (#2574). */
|
|
379
|
+
public readonly mismatch?: GateDigestMismatch,
|
|
373
380
|
) {
|
|
374
381
|
super(`gate "${pending.gate}" is pending approval`);
|
|
375
382
|
this.name = "GateStop";
|
|
@@ -385,6 +392,14 @@ function progressStatus(status: DriverComponentResult["status"]): RunProgressSta
|
|
|
385
392
|
export interface GateContext {
|
|
386
393
|
port: GateLedgerPort;
|
|
387
394
|
now?: string;
|
|
395
|
+
/**
|
|
396
|
+
* The recorded digest each component is deployed at, for a promote or a
|
|
397
|
+
* rollback (#2574). It goes into the plan digest a gate approval is bound
|
|
398
|
+
* to, keyed by component name.
|
|
399
|
+
*/
|
|
400
|
+
releases?: ReadonlyMap<string, string>;
|
|
401
|
+
/** This component's plan digest. Set by {@link runComponentDeploy}, never by a caller. */
|
|
402
|
+
planDigest?: string;
|
|
388
403
|
}
|
|
389
404
|
|
|
390
405
|
/** Run a single capability step. Never throws for a capability-run failure; returns a fail record instead. */
|
|
@@ -467,6 +482,9 @@ async function runPhase(
|
|
|
467
482
|
const check = await evaluateGate(gates.port, {
|
|
468
483
|
op: ctx.component,
|
|
469
484
|
gate: gateName(gate),
|
|
485
|
+
// #2574: an approval counts only for this environment and this plan.
|
|
486
|
+
environment: ctx.env,
|
|
487
|
+
...(gates.planDigest !== undefined ? { planDigest: gates.planDigest } : {}),
|
|
470
488
|
...(gate.description ? { description: gate.description } : {}),
|
|
471
489
|
...(gate.timeout ? { timeout: gate.timeout } : {}),
|
|
472
490
|
...(gates.now ? { now: gates.now } : {}),
|
|
@@ -477,7 +495,7 @@ async function runPhase(
|
|
|
477
495
|
for (const skipped of phaseDef.steps.filter((s): s is DriverStep | DriverPhase => !isGateStep(s))) {
|
|
478
496
|
gateRecords.push(skippedRecord(skipped));
|
|
479
497
|
}
|
|
480
|
-
throw new GateStop(gateRecords, [], check.pending, check.pushed, check.pushWarning);
|
|
498
|
+
throw new GateStop(gateRecords, [], check.pending, check.pushed, check.pushWarning, check.mismatch);
|
|
481
499
|
}
|
|
482
500
|
gateRecords.push({
|
|
483
501
|
...base,
|
|
@@ -505,6 +523,7 @@ async function runPhase(
|
|
|
505
523
|
pending?: PendingGateRecord;
|
|
506
524
|
pushed?: boolean;
|
|
507
525
|
pushWarning?: string;
|
|
526
|
+
mismatch?: GateDigestMismatch;
|
|
508
527
|
}> => {
|
|
509
528
|
if (isPhaseStep(entry)) {
|
|
510
529
|
try {
|
|
@@ -522,6 +541,7 @@ async function runPhase(
|
|
|
522
541
|
pending: err.pending,
|
|
523
542
|
pushed: err.pushed,
|
|
524
543
|
pushWarning: err.pushWarning,
|
|
544
|
+
mismatch: err.mismatch,
|
|
525
545
|
};
|
|
526
546
|
}
|
|
527
547
|
throw err;
|
|
@@ -570,7 +590,7 @@ async function runPhase(
|
|
|
570
590
|
const executed = results.flatMap((r) => r.executed);
|
|
571
591
|
const withPending = results.find((r) => r.pending);
|
|
572
592
|
if (withPending?.pending) {
|
|
573
|
-
throw new GateStop(records, executed, withPending.pending, withPending.pushed, withPending.pushWarning);
|
|
593
|
+
throw new GateStop(records, executed, withPending.pending, withPending.pushed, withPending.pushWarning, withPending.mismatch);
|
|
574
594
|
}
|
|
575
595
|
if (results.some((r) => r.failed)) throw new StepFailure(records, executed);
|
|
576
596
|
return { records, executed };
|
|
@@ -584,7 +604,7 @@ async function runPhase(
|
|
|
584
604
|
executed.push(...result.executed);
|
|
585
605
|
if (result.pending) {
|
|
586
606
|
for (const skipped of entries.slice(i + 1)) records.push(skippedRecord(skipped));
|
|
587
|
-
throw new GateStop(records, executed, result.pending, result.pushed, result.pushWarning);
|
|
607
|
+
throw new GateStop(records, executed, result.pending, result.pushed, result.pushWarning, result.mismatch);
|
|
588
608
|
}
|
|
589
609
|
if (result.failed) {
|
|
590
610
|
for (const skipped of entries.slice(i + 1)) records.push(skippedRecord(skipped));
|
|
@@ -693,9 +713,22 @@ export async function runComponentDeploy(
|
|
|
693
713
|
registry: CapabilityRegistry,
|
|
694
714
|
componentOutputs: Record<string, Record<string, unknown>>,
|
|
695
715
|
onProgress?: (event: RunProgressEvent) => void,
|
|
696
|
-
|
|
716
|
+
gateContext: GateContext = { port: gitGateLedgerPort() },
|
|
697
717
|
): Promise<DriverComponentResult> {
|
|
698
718
|
const phaseOutputs: Record<string, Record<string, unknown>> = {};
|
|
719
|
+
// #2574: every gate this component reaches binds its approval to this
|
|
720
|
+
// environment and this composition, so an approval for staging or for last
|
|
721
|
+
// week's deploy cannot pass it.
|
|
722
|
+
const release = gateContext.releases?.get(component.name);
|
|
723
|
+
const gates: GateContext = {
|
|
724
|
+
...gateContext,
|
|
725
|
+
planDigest: componentPlanDigest({
|
|
726
|
+
environment: ctx.env,
|
|
727
|
+
component,
|
|
728
|
+
...(ctx.vars ? { vars: ctx.vars } : {}),
|
|
729
|
+
...(release !== undefined ? { release } : {}),
|
|
730
|
+
}),
|
|
731
|
+
};
|
|
699
732
|
const records: DriverStepRecord[] = [];
|
|
700
733
|
const allExecuted: ExecutedStep[] = [];
|
|
701
734
|
|
|
@@ -720,6 +753,7 @@ export async function runComponentDeploy(
|
|
|
720
753
|
gate: err.pending,
|
|
721
754
|
...(err.pushed !== undefined ? { gatePushed: err.pushed } : {}),
|
|
722
755
|
...(err.pushWarning ? { gatePushWarning: err.pushWarning } : {}),
|
|
756
|
+
...(err.mismatch ? { gateMismatch: err.mismatch } : {}),
|
|
723
757
|
};
|
|
724
758
|
}
|
|
725
759
|
|
|
@@ -873,6 +907,8 @@ export interface InterpretRunOptions {
|
|
|
873
907
|
gates?: GateLedgerPort;
|
|
874
908
|
/** ISO-8601 "now" for gate decisions, so a test is deterministic. */
|
|
875
909
|
now?: string;
|
|
910
|
+
/** The recorded digest a promote or rollback deploys, per component (#2574). Part of the plan a gate approval is bound to. */
|
|
911
|
+
releases?: ReadonlyMap<string, string>;
|
|
876
912
|
}
|
|
877
913
|
|
|
878
914
|
/**
|
|
@@ -906,6 +942,7 @@ export async function runInterpretDriver(
|
|
|
906
942
|
const gates: GateContext = {
|
|
907
943
|
port: options.gates ?? gitGateLedgerPort(),
|
|
908
944
|
...(options.now ? { now: options.now } : {}),
|
|
945
|
+
...(options.releases ? { releases: options.releases } : {}),
|
|
909
946
|
};
|
|
910
947
|
|
|
911
948
|
onProgress?.({ type: "run-start", waves });
|
|
@@ -957,6 +994,7 @@ export async function runInterpretDriver(
|
|
|
957
994
|
gate: gated.gate,
|
|
958
995
|
...(gated.gatePushed !== undefined ? { gatePushed: gated.gatePushed } : {}),
|
|
959
996
|
...(gated.gatePushWarning ? { gatePushWarning: gated.gatePushWarning } : {}),
|
|
997
|
+
...(gated.gateMismatch ? { gateMismatch: gated.gateMismatch } : {}),
|
|
960
998
|
}
|
|
961
999
|
: {}),
|
|
962
1000
|
};
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* over.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { lexiconNames } from "../lexicon-module";
|
|
16
17
|
import { resolveComponentTargets } from "./cli-support";
|
|
17
18
|
import { applyConfigDefaults } from "./config-defaults";
|
|
18
19
|
import { buildCapabilityRegistry } from "./capability-plugin-loader";
|
|
@@ -91,5 +92,5 @@ export async function deriveFanOut(options: DeriveFanOutOptions): Promise<Derive
|
|
|
91
92
|
*/
|
|
92
93
|
export async function fanOutRegistry(path: string, config?: ChantConfig): Promise<CapabilityRegistry> {
|
|
93
94
|
const resolved = config ?? (await loadChantConfig(path)).config;
|
|
94
|
-
return buildCapabilityRegistry({ plugins: resolved.capabilities, lexicons: resolved.lexicons });
|
|
95
|
+
return buildCapabilityRegistry({ plugins: resolved.capabilities, lexicons: lexiconNames(resolved.lexicons) });
|
|
95
96
|
}
|