@intentius/chant 0.80.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/discover.d.ts +8 -16
- 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 +4 -5
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +25 -9
- package/dist/cli/commands/audit.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.map +1 -1
- package/dist/cli/commands/lint.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/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/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +7 -4
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/run-generate.d.ts.map +1 -1
- 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/plugins.d.ts +12 -7
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +32 -0
- 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/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/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/promote.d.ts +60 -7
- package/dist/components/promote.d.ts.map +1 -1
- 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 +32 -10
- 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 +10 -5
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +20 -4
- 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 +4 -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 +51 -6
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon.d.ts +58 -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 +41 -8
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
- 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 +18 -2
- 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/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/discover.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/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/records-cli.d.ts +28 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records.d.ts.map +1 -1
- 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 +2 -1
- package/src/audit/discover.ts +66 -78
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +4 -9
- package/src/audit/terraform-state.ts +4 -5
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +89 -60
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +18 -0
- package/src/cli/commands/import-agents.ts +6 -2
- package/src/cli/commands/init.ts +22 -3
- package/src/cli/commands/lint.test.ts +17 -23
- package/src/cli/commands/lint.ts +24 -84
- package/src/cli/commands/update.ts +13 -4
- package/src/cli/commands/vendor.ts +11 -6
- 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.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 +5 -3
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +37 -9
- package/src/cli/handlers/promote.test.ts +118 -2
- package/src/cli/handlers/promote.ts +82 -16
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +6 -8
- package/src/cli/handlers/run-generate.ts +36 -1
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +28 -5
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +105 -1
- package/src/cli/main.ts +230 -17
- package/src/cli/plugins.ts +17 -17
- package/src/cli/registry.ts +32 -0
- 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/cli-support.ts +6 -1
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +13 -68
- package/src/components/driver.test.ts +78 -1
- package/src/components/driver.ts +43 -7
- package/src/components/gate-plan.ts +53 -0
- package/src/components/promote.test.ts +276 -2
- package/src/components/promote.ts +119 -15
- 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 +2 -2
- package/src/config.ts +48 -15
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +10 -10
- package/src/discovery/files.ts +25 -81
- package/src/discovery/fold-import.ts +49 -5
- 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/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +4 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.ts +108 -10
- package/src/lexicon.ts +79 -1
- package/src/lifecycle/build-ledger-store.ts +11 -10
- 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 +115 -23
- package/src/lifecycle/legacy-digest.ts +60 -19
- 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 +23 -7
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- 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/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -27
- 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/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/records-cli.ts +86 -14
- package/src/workspace/records-contract.test.ts +5 -0
- package/src/workspace/records.schema.json +32 -2
- package/src/workspace/records.ts +3 -2
- 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
- package/dist/components/unbound-gate-approval.d.ts +0 -24
- package/dist/components/unbound-gate-approval.d.ts.map +0 -1
- package/dist/discovery/convergence.d.ts +0 -97
- package/dist/discovery/convergence.d.ts.map +0 -1
- package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
- package/src/components/unbound-gate-approval.test.ts +0 -82
- package/src/components/unbound-gate-approval.ts +0 -51
- package/src/discovery/convergence.test.ts +0 -231
- package/src/discovery/convergence.ts +0 -399
- package/src/op/discover-convergence.test.ts +0 -70
|
@@ -23,6 +23,7 @@ const awsEmulatorStub = {
|
|
|
23
23
|
const getHeadCommitMock = vi.fn();
|
|
24
24
|
const fetchLifecycleMock = vi.fn();
|
|
25
25
|
const pushLifecycleMock = vi.fn();
|
|
26
|
+
const readSnapshotMock = vi.fn();
|
|
26
27
|
|
|
27
28
|
const appendReleaseRecordMock = vi.fn();
|
|
28
29
|
const readReleaseLedgerMock = vi.fn();
|
|
@@ -38,6 +39,8 @@ vi.mock("../../lifecycle/git", () => ({
|
|
|
38
39
|
getHeadCommit: (...args: unknown[]) => getHeadCommitMock(...args),
|
|
39
40
|
fetchLifecycle: (...args: unknown[]) => fetchLifecycleMock(...args),
|
|
40
41
|
pushLifecycle: (...args: unknown[]) => pushLifecycleMock(...args),
|
|
42
|
+
readSnapshot: (...args: unknown[]) => readSnapshotMock(...args),
|
|
43
|
+
snapshotStorageKey: (lexicon: string, stack?: string) => (stack ? `${stack}__${lexicon}` : lexicon),
|
|
41
44
|
StaleLifecycleBranchError: class StaleLifecycleBranchError extends Error {},
|
|
42
45
|
}));
|
|
43
46
|
|
|
@@ -125,6 +128,7 @@ describe("components handlers", () => {
|
|
|
125
128
|
getHeadCommitMock.mockReset().mockResolvedValue("abc123headsha");
|
|
126
129
|
fetchLifecycleMock.mockReset().mockResolvedValue(true);
|
|
127
130
|
pushLifecycleMock.mockReset().mockResolvedValue(true);
|
|
131
|
+
readSnapshotMock.mockReset().mockResolvedValue(null);
|
|
128
132
|
appendReleaseRecordMock.mockReset();
|
|
129
133
|
readReleaseLedgerMock.mockReset().mockResolvedValue({ records: [], malformed: 0 });
|
|
130
134
|
listReleaseEnvironmentsMock.mockReset().mockResolvedValue([]);
|
|
@@ -567,6 +571,129 @@ describe("components handlers", () => {
|
|
|
567
571
|
});
|
|
568
572
|
});
|
|
569
573
|
|
|
574
|
+
// #2513: `--live` could never report `drifted` — the change set was built
|
|
575
|
+
// with no prior observation, so a live, declared entity was always `noop`,
|
|
576
|
+
// and nothing compared the live identity with the recorded release. These
|
|
577
|
+
// go through the handler end to end with a fake lexicon.
|
|
578
|
+
describe("drifted (#2513)", () => {
|
|
579
|
+
const released = {
|
|
580
|
+
version: 1,
|
|
581
|
+
component: "svc",
|
|
582
|
+
env: "prod",
|
|
583
|
+
digest: "sha256:released",
|
|
584
|
+
gitSha: "1111111aaaaaaa",
|
|
585
|
+
runId: "run-1",
|
|
586
|
+
timestamp: "2026-02-01T00:00:00.000Z",
|
|
587
|
+
actor: "alice",
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
async function statusRow(
|
|
591
|
+
resources: Record<string, ResourceMetadata>,
|
|
592
|
+
opts: { snapshot?: { timestamp: string; resources: Record<string, ResourceMetadata> }; failRead?: boolean } = {},
|
|
593
|
+
) {
|
|
594
|
+
readReleaseLedgerMock.mockResolvedValue({ records: [released], malformed: 0 });
|
|
595
|
+
buildMock.mockResolvedValue(makeBuildResult({ aws: ["svc"] }));
|
|
596
|
+
readSnapshotMock.mockImplementation(async (env: string, key: string) =>
|
|
597
|
+
opts.snapshot && env === "prod" && key === "aws"
|
|
598
|
+
? JSON.stringify({ lexicon: "aws", environment: "prod", commit: "c", ...opts.snapshot })
|
|
599
|
+
: null,
|
|
600
|
+
);
|
|
601
|
+
const plugins: LexiconPlugin[] = [
|
|
602
|
+
createMockPlugin({
|
|
603
|
+
name: "aws",
|
|
604
|
+
emulator: awsEmulatorStub,
|
|
605
|
+
describeResources: opts.failRead
|
|
606
|
+
? async () => { throw new Error("credentials expired"); }
|
|
607
|
+
: staticDescribeResources(resources),
|
|
608
|
+
}),
|
|
609
|
+
];
|
|
610
|
+
stdoutBuf.length = 0;
|
|
611
|
+
const exit = await runComponentsStatus({
|
|
612
|
+
args: makeArgs({ extraPositional: "prod", live: true, json: true }),
|
|
613
|
+
plugins,
|
|
614
|
+
serializers: plugins.map((p) => p.serializer),
|
|
615
|
+
});
|
|
616
|
+
expect(exit).toBe(0);
|
|
617
|
+
return JSON.parse(stdoutBuf.join(""))[0];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
test("reconciled: live identity matches the recorded digest and git sha", async () => {
|
|
621
|
+
const row = await statusRow({ svc: meta({ attributes: { digest: "sha256:released", gitSha: "1111111" } }) });
|
|
622
|
+
expect(row).toMatchObject({ component: "svc", reconciliation: "reconciled", live: true });
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test("drifted: live resource reports a digest other than the recorded one", async () => {
|
|
626
|
+
const row = await statusRow({ svc: meta({ attributes: { digest: "sha256:foreign" } }) });
|
|
627
|
+
expect(row).toMatchObject({ component: "svc", reconciliation: "drifted", live: true });
|
|
628
|
+
expect(row.detail).toContain("sha256:foreign");
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test("drifted: live resource reports a different git sha", async () => {
|
|
632
|
+
const row = await statusRow({ svc: meta({ attributes: { gitSha: "2222222bbbbbbb" } }) });
|
|
633
|
+
expect(row).toMatchObject({ reconciliation: "drifted" });
|
|
634
|
+
expect(row.detail).toContain("2222222bbbbbbb");
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("drifted: live attributes changed since a snapshot taken after the release", async () => {
|
|
638
|
+
const row = await statusRow(
|
|
639
|
+
{ svc: meta({ attributes: { revision: "7" } }) },
|
|
640
|
+
{ snapshot: { timestamp: "2026-02-01T00:05:00.000Z", resources: { svc: meta({ attributes: { revision: "6" } }) } } },
|
|
641
|
+
);
|
|
642
|
+
expect(row).toMatchObject({ reconciliation: "drifted" });
|
|
643
|
+
expect(row.detail).toContain("attributes.revision");
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test("drifted: chant's ownership marker disappeared since a post-release snapshot", async () => {
|
|
647
|
+
const row = await statusRow(
|
|
648
|
+
{ svc: meta({ ownership: "foreign" }) },
|
|
649
|
+
{ snapshot: { timestamp: "2026-02-02T00:00:00.000Z", resources: { svc: meta({ ownership: "owned" }) } } },
|
|
650
|
+
);
|
|
651
|
+
expect(row).toMatchObject({ reconciliation: "drifted" });
|
|
652
|
+
expect(row.detail).toContain("ownership marker");
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test("reconciled: a snapshot older than the release does not attribute the release's own change to drift", async () => {
|
|
656
|
+
const row = await statusRow(
|
|
657
|
+
{ svc: meta({ attributes: { revision: "7" } }) },
|
|
658
|
+
{ snapshot: { timestamp: "2026-01-15T00:00:00.000Z", resources: { svc: meta({ attributes: { revision: "6" } }) } } },
|
|
659
|
+
);
|
|
660
|
+
expect(row).toMatchObject({ reconciliation: "reconciled" });
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test("reconciled: unchanged since the snapshot, and no identity reported (no identity is not drift)", async () => {
|
|
664
|
+
const row = await statusRow(
|
|
665
|
+
{ svc: meta() },
|
|
666
|
+
{ snapshot: { timestamp: "2026-02-01T00:05:00.000Z", resources: { svc: meta() } } },
|
|
667
|
+
);
|
|
668
|
+
expect(row).toMatchObject({ reconciliation: "reconciled" });
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("stale: recorded, and the provider confirmed nothing live", async () => {
|
|
672
|
+
const row = await statusRow(
|
|
673
|
+
{},
|
|
674
|
+
{ snapshot: { timestamp: "2026-02-01T00:05:00.000Z", resources: { svc: meta({ attributes: { digest: "sha256:foreign" } }) } } },
|
|
675
|
+
);
|
|
676
|
+
expect(row).toMatchObject({ reconciliation: "stale", live: false });
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test("unknown: a failed read is never drifted, whatever the snapshot says", async () => {
|
|
680
|
+
const row = await statusRow(
|
|
681
|
+
{},
|
|
682
|
+
{
|
|
683
|
+
failRead: true,
|
|
684
|
+
snapshot: { timestamp: "2026-02-01T00:05:00.000Z", resources: { svc: meta({ attributes: { digest: "sha256:foreign" } }) } },
|
|
685
|
+
},
|
|
686
|
+
);
|
|
687
|
+
expect(row).toMatchObject({ reconciliation: "unknown" });
|
|
688
|
+
expect(row).not.toHaveProperty("live");
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test("reads the same unstacked snapshot `lifecycle plan` reads", async () => {
|
|
692
|
+
await statusRow({ svc: meta() });
|
|
693
|
+
expect(readSnapshotMock).toHaveBeenCalledWith("prod", "aws");
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
570
697
|
test("reports malformed ledger lines as a warning without failing", async () => {
|
|
571
698
|
readReleaseLedgerMock.mockResolvedValue({ records: [], malformed: 2 });
|
|
572
699
|
const ctx = { args: makeArgs({ extraPositional: "prod" }), plugins: [], serializers: [] };
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { resolve, join, dirname } from "node:path";
|
|
30
30
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
31
|
-
import { getHeadCommit, fetchLifecycle, pushLifecycle, StaleLifecycleBranchError } from "../../lifecycle/git";
|
|
31
|
+
import { getHeadCommit, fetchLifecycle, pushLifecycle, readSnapshot, snapshotStorageKey, StaleLifecycleBranchError } from "../../lifecycle/git";
|
|
32
|
+
import type { LifecycleSnapshot } from "../../lifecycle/types";
|
|
32
33
|
import {
|
|
33
34
|
appendReleaseRecord,
|
|
34
35
|
readReleaseLedger,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
resolveRunId,
|
|
38
39
|
InvalidReleaseRecordError,
|
|
39
40
|
} from "../../lifecycle/release-ledger";
|
|
40
|
-
import { reconcileStatus, liveEvidenceFromChangeSet, compareAcrossEnvironments, mergeLiveEvidence, type LiveComponentEvidence } from "../../lifecycle/status";
|
|
41
|
+
import { reconcileStatus, liveEvidenceFromChangeSet, compareAcrossEnvironments, mergeLiveEvidence, type LiveComponentEvidence, type EntityLiveRead } from "../../lifecycle/status";
|
|
41
42
|
import { commandBuildParams } from "../build-params-cli";
|
|
42
43
|
import { buildChangeSet } from "../../lifecycle/change-set";
|
|
43
44
|
import { buildLedgerEntries, componentBomSummary, type BuildLedgerEntry } from "../../lifecycle/build-ledger";
|
|
@@ -51,11 +52,12 @@ import { build } from "../../build";
|
|
|
51
52
|
import { discoverComponents } from "../../components/discover";
|
|
52
53
|
import { formatError, formatWarning, formatSuccess, formatBold } from "../format";
|
|
53
54
|
import type { CommandContext } from "../registry";
|
|
54
|
-
import type { LexiconPlugin } from "../../lexicon";
|
|
55
|
+
import type { LexiconPlugin, ResourceMetadata } from "../../lexicon";
|
|
55
56
|
import { normalizeObservation, mergeObservations, unobservedAll, type NormalizedObservation } from "../../observation";
|
|
56
57
|
import type { Phase, Component } from "../../components/component";
|
|
57
58
|
import { deployUnits } from "../../components/deploy-units";
|
|
58
59
|
import { sortedJsonReplacer } from "../../utils";
|
|
60
|
+
import { withoutReadFlags } from "../../lifecycle/legacy-digest";
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* chant components release <env> --component <name> --digest <sha256:...>
|
|
@@ -276,7 +278,8 @@ export async function runComponentsExport(ctx: CommandContext): Promise<number>
|
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
|
|
281
|
+
// The exported manifest is the recorded one; read-side flags (#2514) stay out of it.
|
|
282
|
+
await writeFile(join(targetDir, "manifest.json"), JSON.stringify(withoutReadFlags(manifest), sortedJsonReplacer, 2));
|
|
280
283
|
|
|
281
284
|
const missing = results.filter((r) => r.status === "missing");
|
|
282
285
|
|
|
@@ -284,6 +287,7 @@ export async function runComponentsExport(ctx: CommandContext): Promise<number>
|
|
|
284
287
|
console.log(JSON.stringify({
|
|
285
288
|
component: manifest.component,
|
|
286
289
|
manifestDigest: manifest.manifestDigest,
|
|
290
|
+
...(manifest.flags ? { flags: manifest.flags } : {}),
|
|
287
291
|
outDir: targetDir,
|
|
288
292
|
entries: results,
|
|
289
293
|
}, null, 2));
|
|
@@ -318,6 +322,7 @@ interface StatusJsonRow {
|
|
|
318
322
|
runId: string;
|
|
319
323
|
timestamp: string;
|
|
320
324
|
actor: string;
|
|
325
|
+
flags?: string[];
|
|
321
326
|
} | null;
|
|
322
327
|
build: {
|
|
323
328
|
manifestDigest: string;
|
|
@@ -464,6 +469,42 @@ async function observeComponentStacks(
|
|
|
464
469
|
return evidence;
|
|
465
470
|
}
|
|
466
471
|
|
|
472
|
+
/**
|
|
473
|
+
* The last lifecycle snapshot(s) for one lexicon, as `components status --live`
|
|
474
|
+
* reads them for its drift baseline (#2513). Reads the stack-keyed snapshot for
|
|
475
|
+
* each stack the status read targets and the unstacked one `lifecycle plan`
|
|
476
|
+
* reads, merging resources (a stack's own snapshot wins) and remembering, per
|
|
477
|
+
* entity, which snapshot's timestamp it came from. A missing or unreadable
|
|
478
|
+
* snapshot is no baseline, never an error: with none, nothing can have changed
|
|
479
|
+
* since, and the status falls back to identity and presence.
|
|
480
|
+
*/
|
|
481
|
+
async function readStatusBaseline(
|
|
482
|
+
environment: string,
|
|
483
|
+
lexicon: string,
|
|
484
|
+
stacks: Array<string | undefined>,
|
|
485
|
+
): Promise<{ resources: Record<string, ResourceMetadata> | undefined; timestamps: Map<string, string> }> {
|
|
486
|
+
const keys = [...new Set([...stacks.filter((s): s is string => !!s).map((s) => snapshotStorageKey(lexicon, s)), snapshotStorageKey(lexicon)])];
|
|
487
|
+
let resources: Record<string, ResourceMetadata> | undefined;
|
|
488
|
+
const timestamps = new Map<string, string>();
|
|
489
|
+
for (const key of keys) {
|
|
490
|
+
let snap: LifecycleSnapshot | undefined;
|
|
491
|
+
try {
|
|
492
|
+
const content = await readSnapshot(environment, key);
|
|
493
|
+
snap = content ? (JSON.parse(content) as LifecycleSnapshot) : undefined;
|
|
494
|
+
} catch {
|
|
495
|
+
snap = undefined;
|
|
496
|
+
}
|
|
497
|
+
if (!snap?.resources) continue;
|
|
498
|
+
resources ??= {};
|
|
499
|
+
for (const [name, meta] of Object.entries(snap.resources)) {
|
|
500
|
+
if (Object.prototype.hasOwnProperty.call(resources, name)) continue;
|
|
501
|
+
resources[name] = meta;
|
|
502
|
+
if (snap.timestamp) timestamps.set(name, snap.timestamp);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return { resources, timestamps };
|
|
506
|
+
}
|
|
507
|
+
|
|
467
508
|
export async function runComponentsStatus(ctx: CommandContext): Promise<number> {
|
|
468
509
|
const { args, plugins, serializers } = ctx;
|
|
469
510
|
const requestedEnv = args.extraPositional;
|
|
@@ -554,6 +595,7 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
554
595
|
for (const stack of config.stacks ?? []) componentStackNames.add(stack.name);
|
|
555
596
|
const readTargets: Array<string | undefined> = componentStackNames.size ? [...componentStackNames] : [undefined];
|
|
556
597
|
const merged: { env: string; entries: import("../../lifecycle/change-set").ChangeSetEntry[] } = { env: environment, entries: [] };
|
|
598
|
+
const observations = new Map<string, EntityLiveRead>();
|
|
557
599
|
for (const plugin of plugins) {
|
|
558
600
|
if (!plugin.describeResources) continue;
|
|
559
601
|
const declared = new Set<string>();
|
|
@@ -593,15 +635,29 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
593
635
|
console.error(formatWarning({ message: `${plugin.name}: describeResources failed — ${message} (components in this lexicon report unknown, not stale)` }));
|
|
594
636
|
observed = { resources: {}, unobserved: unobservedAll(declared, "read-failed", message, entities), queried: {}, sources: {}, notes: [] };
|
|
595
637
|
}
|
|
638
|
+
// The last lifecycle snapshot is the prior observation drift is
|
|
639
|
+
// measured against (#2513) — the same one `lifecycle plan` reads, so
|
|
640
|
+
// `drifted` here means what `update` means there. Without it every
|
|
641
|
+
// live, declared entity classified `noop` and `drifted` could not be
|
|
642
|
+
// reached. Each entity remembers its snapshot's timestamp so the
|
|
643
|
+
// status join can ignore a change made before the release.
|
|
644
|
+
const baseline = await readStatusBaseline(environment, plugin.name, readTargets);
|
|
596
645
|
const cs = buildChangeSet(environment, {
|
|
597
646
|
declared,
|
|
598
647
|
observedNow: observed.resources,
|
|
599
|
-
observedThen:
|
|
648
|
+
observedThen: baseline.resources,
|
|
600
649
|
unobserved: observed.unobserved,
|
|
601
650
|
}, { lexicon: plugin.name });
|
|
602
651
|
merged.entries.push(...cs.entries);
|
|
652
|
+
for (const entry of cs.entries) {
|
|
653
|
+
observations.set(entry.name, {
|
|
654
|
+
now: observed.resources[entry.name],
|
|
655
|
+
then: baseline.resources?.[entry.name],
|
|
656
|
+
thenAt: baseline.timestamps.get(entry.name),
|
|
657
|
+
});
|
|
658
|
+
}
|
|
603
659
|
}
|
|
604
|
-
liveEvidence = liveEvidenceFromChangeSet(merged, liveNameMapping);
|
|
660
|
+
liveEvidence = liveEvidenceFromChangeSet(merged, liveNameMapping, { observations });
|
|
605
661
|
|
|
606
662
|
// Multi-stack component projects (each component owns its own stack) are
|
|
607
663
|
// invisible to the entity-keyed, single-stack `describeResources` above —
|
|
@@ -652,6 +708,7 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
652
708
|
runId: row.recorded.runId,
|
|
653
709
|
timestamp: row.recorded.timestamp,
|
|
654
710
|
actor: row.recorded.actor,
|
|
711
|
+
...(row.recorded.flags ? { flags: row.recorded.flags } : {}),
|
|
655
712
|
}
|
|
656
713
|
: null,
|
|
657
714
|
build: row.build
|
|
@@ -729,14 +786,19 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
729
786
|
row.env.padEnd(12) +
|
|
730
787
|
digestShort.padEnd(20) +
|
|
731
788
|
row.reconciliation.padEnd(14) +
|
|
732
|
-
row.detail
|
|
789
|
+
row.detail +
|
|
790
|
+
(row.recorded?.flags?.length ? ` [${row.recorded.flags.join(", ")}]` : ""),
|
|
733
791
|
);
|
|
734
792
|
}
|
|
735
793
|
|
|
736
794
|
if (comparisons) {
|
|
737
795
|
console.log(`\n${formatBold(`Cross-check: ${requestedEnv} vs ${args.compareTo}`)}`);
|
|
738
796
|
for (const c of comparisons) {
|
|
739
|
-
const verdict = c.same
|
|
797
|
+
const verdict = c.same
|
|
798
|
+
? "same build"
|
|
799
|
+
: c.mixedDigestForms
|
|
800
|
+
? "different digests, one recorded before real SHA-256 (legacy-digest), so this cannot tell"
|
|
801
|
+
: "DIFFERENT builds";
|
|
740
802
|
console.log(` ${c.component}: ${c.digestA ?? "(none)"} vs ${c.digestB ?? "(none)"} — ${verdict}`);
|
|
741
803
|
}
|
|
742
804
|
}
|
package/src/cli/handlers/dev.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { formatError, formatSuccess } from "../format";
|
|
2
|
+
import { formatError, formatSuccess, formatWarning } from "../format";
|
|
3
3
|
import type { CommandContext } from "../registry";
|
|
4
4
|
|
|
5
5
|
export async function runDevGenerate(ctx: CommandContext): Promise<number> {
|
|
@@ -34,8 +34,10 @@ export async function runDevOnboard(ctx: CommandContext): Promise<number> {
|
|
|
34
34
|
|
|
35
35
|
const { onboardCommand, printOnboardResult } = await import("../commands/onboard");
|
|
36
36
|
// chant#2578 — so a lexicon the project declares by path is recognised.
|
|
37
|
-
|
|
38
|
-
await
|
|
37
|
+
// chant#2591 — read without running chant.config.ts.
|
|
38
|
+
const { recordProjectLexicons, unknownPathLexiconsNotice } = await import("../plugins");
|
|
39
|
+
const notice = unknownPathLexiconsNotice(recordProjectLexicons(process.cwd()));
|
|
40
|
+
if (notice !== undefined) console.error(formatWarning({ message: notice }));
|
|
39
41
|
const result = onboardCommand({ name, verbose: ctx.args.verbose });
|
|
40
42
|
await printOnboardResult(result, name);
|
|
41
43
|
return result.success ? 0 : 1;
|
|
@@ -43,7 +43,8 @@ import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support";
|
|
|
43
43
|
import { runFanOut } from "../../components/fan-out-run";
|
|
44
44
|
import { renderFanOutHuman, renderFanOutJson, renderFanOutPlan } from "../../components/fan-out-output";
|
|
45
45
|
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
46
|
-
import { writeGatedRunSummary } from "../../op/gate-summary";
|
|
46
|
+
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary";
|
|
47
|
+
import { approveCommand } from "../../op/gate";
|
|
47
48
|
import { remainingFanOut, type ChangedUnits, type FanOutProgress } from "../../components/fan-out";
|
|
48
49
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
49
50
|
import { formatError, formatInfo, formatWarning } from "../format";
|
|
@@ -320,7 +321,11 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
320
321
|
|
|
321
322
|
if (result.status === "gated" && result.gate) {
|
|
322
323
|
const pending = result.gate;
|
|
323
|
-
|
|
324
|
+
// The pending fact's own plan: the fan-out's digest for the gate over the
|
|
325
|
+
// set, or a component's plan and environment for a gate inside one (#2574).
|
|
326
|
+
console.error(formatInfo(
|
|
327
|
+
`approve : ${approveCommand(pending.op, pending.gate, pending.environment)} --plan ${pending.planDigest ?? result.plan.digest}`,
|
|
328
|
+
));
|
|
324
329
|
writeGatedRunSummary({
|
|
325
330
|
op: pending.op,
|
|
326
331
|
gate: pending.gate,
|
|
@@ -328,6 +333,8 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
328
333
|
expiresAt: pending.expiresAt,
|
|
329
334
|
...(pending.url ? { url: pending.url } : {}),
|
|
330
335
|
...(pending.planDigest ? { planDigest: pending.planDigest } : {}),
|
|
336
|
+
...(await summaryLedgerPrefix()),
|
|
337
|
+
...(pending.environment ? { environment: pending.environment } : {}),
|
|
331
338
|
});
|
|
332
339
|
return GATED_EXIT_CODE;
|
|
333
340
|
}
|
package/src/cli/handlers/init.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { formatError } from "../format";
|
|
1
|
+
import { formatError, formatSuccess, formatWarning } from "../format";
|
|
2
2
|
import type { CommandContext } from "../registry";
|
|
3
3
|
|
|
4
4
|
export async function runInit(ctx: CommandContext): Promise<number> {
|
|
5
5
|
const { args } = ctx;
|
|
6
6
|
|
|
7
|
+
if (args.migrateFrom !== undefined) return runInitFrom(ctx);
|
|
8
|
+
|
|
7
9
|
if (!args.lexicon) {
|
|
8
10
|
console.error(formatError({
|
|
9
11
|
message: "Missing --lexicon flag",
|
|
@@ -47,3 +49,54 @@ export async function runInitLexicon(ctx: CommandContext): Promise<number> {
|
|
|
47
49
|
await printInitLexiconResult(result);
|
|
48
50
|
return result.success ? 0 : 1;
|
|
49
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `chant init --from <repo>@<ref>[#<member>] [--param <name>=<value>]... [path]`
|
|
55
|
+
* (#2540, #2627): copy a template repository at a ref, substitute the
|
|
56
|
+
* parameters its `chant.template.json` declares, and record its lineage.
|
|
57
|
+
* The template brings its own lexicons and configuration, so `--lexicon` and
|
|
58
|
+
* `--template` do not apply.
|
|
59
|
+
*/
|
|
60
|
+
async function runInitFrom(ctx: CommandContext): Promise<number> {
|
|
61
|
+
const { args } = ctx;
|
|
62
|
+
const usage = "Usage: chant init --from <repo>@<ref>[#<member>] [--param <name>=<value>]... [path]";
|
|
63
|
+
if (!args.migrateFrom) {
|
|
64
|
+
console.error(formatError({ message: "--from needs a template: <repo>@<ref>[#<member>]", hint: usage }));
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
if (args.lexicon || args.template) {
|
|
68
|
+
console.error(formatError({ message: "--from cannot be combined with --lexicon or --template", hint: usage }));
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
if (args.paramsFile) {
|
|
72
|
+
console.error(formatError({ message: "--params-file does not apply to init --from; pass each value with --param <name>=<value>", hint: usage }));
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
const { parseParamArgs } = await import("../../workspace/template-manifest");
|
|
76
|
+
let params: Record<string, string>;
|
|
77
|
+
try {
|
|
78
|
+
params = parseParamArgs(args.param ?? []);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(formatError({ message: (err as Error).message, hint: usage }));
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
const { initFromCommand } = await import("../../workspace/lineage-init");
|
|
84
|
+
const result = await initFromCommand({
|
|
85
|
+
from: args.migrateFrom,
|
|
86
|
+
path: args.path === "." ? undefined : args.path,
|
|
87
|
+
force: args.force,
|
|
88
|
+
params,
|
|
89
|
+
});
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
console.error(formatError({ message: result.error ?? "init --from failed", hint: usage }));
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
for (const warning of result.warnings) console.error(formatWarning({ message: warning }));
|
|
95
|
+
console.log(formatSuccess("Created:"));
|
|
96
|
+
for (const file of result.createdFiles) console.log(` ${file}`);
|
|
97
|
+
console.log("");
|
|
98
|
+
console.log(`Lineage: ${result.spec!.id} at ${result.spec!.ref} (${result.commit!.slice(0, 12)}), recorded in .chant/workspace.lock.json`);
|
|
99
|
+
const parameters = Object.entries(result.parameters ?? {});
|
|
100
|
+
if (parameters.length > 0) console.log(`Parameters: ${parameters.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ")}`);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
@@ -897,6 +897,74 @@ describe("runApprove — the resolution names the plan it approves (#2300)", ()
|
|
|
897
897
|
});
|
|
898
898
|
});
|
|
899
899
|
|
|
900
|
+
/**
|
|
901
|
+
* #2574 — a component's gate ledger holds every environment's pending facts.
|
|
902
|
+
* The approval records the environment it answers, and `--env` picks it.
|
|
903
|
+
*/
|
|
904
|
+
describe("runApprove — a component gate approval names its environment (#2574)", () => {
|
|
905
|
+
const PLAN_STAGING = `sha256:${"c".repeat(64)}`;
|
|
906
|
+
const PLAN_PROD = `sha256:${"d".repeat(64)}`;
|
|
907
|
+
function seedTwoEnvironments(): void {
|
|
908
|
+
readGateLedgerMock.mockResolvedValue({
|
|
909
|
+
resolutions: [],
|
|
910
|
+
pending: [
|
|
911
|
+
{
|
|
912
|
+
version: 1, kind: "pending", op: "search-service", gate: "release",
|
|
913
|
+
timestamp: "2026-01-01T00:00:00.000Z", expiresAt: "2099-01-01T00:00:00.000Z",
|
|
914
|
+
planDigest: PLAN_STAGING, environment: "staging",
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
version: 1, kind: "pending", op: "search-service", gate: "release",
|
|
918
|
+
timestamp: "2026-01-02T00:00:00.000Z", expiresAt: "2099-01-01T00:00:00.000Z",
|
|
919
|
+
planDigest: PLAN_PROD, environment: "prod",
|
|
920
|
+
},
|
|
921
|
+
],
|
|
922
|
+
malformed: 0,
|
|
923
|
+
});
|
|
924
|
+
appendGateResolutionMock.mockImplementation(async (input: object) => ({ commit: "sha", record: { version: 1, ...input } }));
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
test("without --env it approves the newest pending fact and records its environment", async () => {
|
|
928
|
+
seedTwoEnvironments();
|
|
929
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
930
|
+
|
|
931
|
+
expect(await runApprove(ctx({ path: "search-service", extraPositional: "release", actor: "alex" }))).toBe(0);
|
|
932
|
+
expect(appendGateResolutionMock).toHaveBeenCalledWith(expect.objectContaining({ planDigest: PLAN_PROD, environment: "prod" }));
|
|
933
|
+
expect(errSpy.mock.calls.map((c) => String(c[0])).join("\n")).toContain(`approves the plan ${PLAN_PROD} in environment "prod"`);
|
|
934
|
+
errSpy.mockRestore();
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test("--env picks that environment's pending fact", async () => {
|
|
938
|
+
seedTwoEnvironments();
|
|
939
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
940
|
+
|
|
941
|
+
expect(await runApprove(ctx({ path: "search-service", extraPositional: "release", actor: "alex", env: "staging" }))).toBe(0);
|
|
942
|
+
expect(appendGateResolutionMock).toHaveBeenCalledWith(expect.objectContaining({ planDigest: PLAN_STAGING, environment: "staging" }));
|
|
943
|
+
errSpy.mockRestore();
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
test("--env with no pending fact there refuses and writes nothing", async () => {
|
|
947
|
+
seedTwoEnvironments();
|
|
948
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
949
|
+
|
|
950
|
+
expect(await runApprove(ctx({ path: "search-service", extraPositional: "release", actor: "alex", env: "dev" }))).toBe(1);
|
|
951
|
+
expect(appendGateResolutionMock).not.toHaveBeenCalled();
|
|
952
|
+
expect(errSpy.mock.calls.map((c) => String(c[0])).join("\n")).toContain('no pending fact in environment "dev"');
|
|
953
|
+
errSpy.mockRestore();
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test("--env is ignored for a gate whose pending facts record no environment", async () => {
|
|
957
|
+
seedPending("fountain-apply", "rollout-gate", PLAN_A);
|
|
958
|
+
appendGateResolutionMock.mockImplementation(async (input: object) => ({ commit: "sha", record: { version: 1, ...input } }));
|
|
959
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
960
|
+
|
|
961
|
+
expect(await runApprove(ctx({ path: "fountain-apply", extraPositional: "rollout-gate", actor: "alex", env: "prod" }))).toBe(0);
|
|
962
|
+
expect(appendGateResolutionMock.mock.calls[0][0]).toMatchObject({ planDigest: PLAN_A });
|
|
963
|
+
expect(appendGateResolutionMock.mock.calls[0][0]).not.toHaveProperty("environment");
|
|
964
|
+
errSpy.mockRestore();
|
|
965
|
+
});
|
|
966
|
+
});
|
|
967
|
+
|
|
900
968
|
describe("runApprove — the ledger branch is read before it is appended to (#2303)", () => {
|
|
901
969
|
test("reads the branch before recording the resolution", async () => {
|
|
902
970
|
seedPending("fountain-apply", "rollout-gate", PLAN_A);
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
type GateApprover, type GatePolicyDecision, type GatePolicyEvaluator, type ResolvedGateApproval,
|
|
45
45
|
} from "../../op/gate-approval";
|
|
46
46
|
import { approverOf, tallyGateApprovals } from "../../op/gate";
|
|
47
|
-
import { FAN_OUT_GATE_OP } from "../../op/gate-name";
|
|
47
|
+
import { FAN_OUT_GATE_OP, WORKSPACE_UPGRADE_GATE_OP } from "../../op/gate-name";
|
|
48
48
|
import { pushLifecycle, requireLifecycleLedger } from "../../lifecycle/git";
|
|
49
49
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
50
50
|
import type { CommandContext } from "../registry";
|
|
@@ -689,7 +689,9 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
689
689
|
console.error(formatInfo(
|
|
690
690
|
opName === FAN_OUT_GATE_OP
|
|
691
691
|
? `The next \`chant components fan-out\` decides this gate from scratch and records a fresh pending fact.`
|
|
692
|
-
:
|
|
692
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
693
|
+
? `The next \`chant workspace upgrade ${gate}\` decides this gate from scratch and records a fresh pending fact.`
|
|
694
|
+
: `The next \`chant run ${opName}\` decides this gate from scratch and records a fresh pending fact.`,
|
|
693
695
|
));
|
|
694
696
|
return 0;
|
|
695
697
|
}
|
|
@@ -703,6 +705,7 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
703
705
|
note: ctx.args.note,
|
|
704
706
|
url: ctx.args.url,
|
|
705
707
|
plan: ctx.args.plan,
|
|
708
|
+
...(ctx.args.env !== undefined ? { environment: ctx.args.env } : {}),
|
|
706
709
|
allowSameOrigin: ctx.args.allowSameOrigin,
|
|
707
710
|
...(ctx.args.roles ? { roles: ctx.args.roles } : {}),
|
|
708
711
|
...(ctx.args.agent ? { agent: true } : {}),
|
|
@@ -713,7 +716,9 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
713
716
|
`This records the resolution as a fact; it does not itself re-run anything. ` +
|
|
714
717
|
(opName === FAN_OUT_GATE_OP
|
|
715
718
|
? `Repeat the \`chant components fan-out\` command and it walks through gate "${gate}".`
|
|
716
|
-
:
|
|
719
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
720
|
+
? `Repeat \`chant workspace upgrade ${gate}\` with the same target, and it applies the patch this approval names.`
|
|
721
|
+
: `Run \`chant run ${opName}\` and it walks through gate "${gate}".`),
|
|
717
722
|
));
|
|
718
723
|
return 0;
|
|
719
724
|
}
|
|
@@ -747,6 +752,13 @@ export interface GateApprovalOptions {
|
|
|
747
752
|
* stays one command: run, read what it planned, approve it.
|
|
748
753
|
*/
|
|
749
754
|
plan?: string;
|
|
755
|
+
/**
|
|
756
|
+
* `--env <env>` (#2574) — which environment's pending fact a component gate
|
|
757
|
+
* approval answers. A component's gate ledger holds every environment's
|
|
758
|
+
* facts, so without it the newest one is approved. Ignored for a gate whose
|
|
759
|
+
* pending facts record no environment (every Op gate).
|
|
760
|
+
*/
|
|
761
|
+
environment?: string;
|
|
750
762
|
/** `--role` (#2508) — roles the approver claims. */
|
|
751
763
|
roles?: string[];
|
|
752
764
|
/**
|
|
@@ -786,7 +798,7 @@ export async function recordGateApproval(
|
|
|
786
798
|
// `fan-out` is the op name every `chant components fan-out` gate is recorded
|
|
787
799
|
// under (../handlers/fan-out.ts). It is a command rather than a declaration,
|
|
788
800
|
// so there is no `*.op.ts` to find and nothing is wrong when none is there.
|
|
789
|
-
if (!ops.has(opName) && opName !== FAN_OUT_GATE_OP) {
|
|
801
|
+
if (!ops.has(opName) && opName !== FAN_OUT_GATE_OP && opName !== WORKSPACE_UPGRADE_GATE_OP) {
|
|
790
802
|
console.error(formatWarning({
|
|
791
803
|
message: `Op "${opName}" was not found among discovered *.op.ts declarations — recording the resolution anyway`,
|
|
792
804
|
}));
|
|
@@ -826,6 +838,18 @@ export async function recordGateApproval(
|
|
|
826
838
|
// third fallback: approving with no plan in sight is the behaviour
|
|
827
839
|
// INTENTIUS/choudoufu#1026 measured, where the resolution authorised the
|
|
828
840
|
// next run rather than anything anyone had seen.
|
|
841
|
+
// #2574: a component gate's pending facts carry the environment they were
|
|
842
|
+
// recorded in, and an approval answers one environment. `--env` picks it;
|
|
843
|
+
// without it, the newest standing fact decides, and the success line below
|
|
844
|
+
// names the environment it chose.
|
|
845
|
+
const ledgerBefore = await readGateLedger(opName);
|
|
846
|
+
const pendingFor = (all: PendingGateRecord[]): PendingGateRecord[] => {
|
|
847
|
+
const bound = all.some((p) => p.gate === gate && p.environment !== undefined);
|
|
848
|
+
return bound && opts.environment !== undefined ? all.filter((p) => p.environment === opts.environment) : all;
|
|
849
|
+
};
|
|
850
|
+
const standing = latestPendingGate(pendingFor(ledgerBefore.pending), gate);
|
|
851
|
+
const environment = standing?.environment;
|
|
852
|
+
|
|
829
853
|
let planDigest: string | undefined;
|
|
830
854
|
if (opts.plan !== undefined) {
|
|
831
855
|
if (!isPlanDigest(opts.plan)) {
|
|
@@ -837,10 +861,12 @@ export async function recordGateApproval(
|
|
|
837
861
|
}
|
|
838
862
|
planDigest = opts.plan;
|
|
839
863
|
} else {
|
|
840
|
-
const standing = latestPendingGate((await readGateLedger(opName)).pending, gate);
|
|
841
864
|
if (!standing) {
|
|
842
865
|
console.error(formatError({
|
|
843
|
-
message:
|
|
866
|
+
message:
|
|
867
|
+
opts.environment !== undefined && ledgerBefore.pending.some((p) => p.gate === gate && p.environment !== undefined)
|
|
868
|
+
? `Gate "${gate}" on "${opName}" has no pending fact in environment "${opts.environment}", so there is no plan to approve`
|
|
869
|
+
: `Gate "${gate}" on "${opName}" has no pending fact, so there is no plan to approve`,
|
|
844
870
|
hint:
|
|
845
871
|
`Run \`chant run ${opName}\` first — it plans, stops at the gate, and records the plan this ` +
|
|
846
872
|
`approval would be for. To approve a plan you already have the digest for, pass ` +
|
|
@@ -859,7 +885,7 @@ export async function recordGateApproval(
|
|
|
859
885
|
// the way #2300 refuses a plan nobody approved.
|
|
860
886
|
const origin = opts.origin ?? currentGateOrigin();
|
|
861
887
|
const ledger = await readGateLedger(opName);
|
|
862
|
-
const standingForOrigin = latestPendingGate(ledger.pending, gate);
|
|
888
|
+
const standingForOrigin = latestPendingGate(pendingFor(ledger.pending), gate);
|
|
863
889
|
const refusal = sameOriginRefusal(standingForOrigin?.origin, origin);
|
|
864
890
|
if (refusal && !opts.allowSameOrigin) {
|
|
865
891
|
console.error(formatError({
|
|
@@ -935,6 +961,7 @@ export async function recordGateApproval(
|
|
|
935
961
|
...(opts.note ? { note: opts.note } : {}),
|
|
936
962
|
...(url ? { url } : {}),
|
|
937
963
|
...(planDigest !== undefined ? { planDigest } : {}),
|
|
964
|
+
...(environment !== undefined ? { environment } : {}),
|
|
938
965
|
origin,
|
|
939
966
|
...(refusal && opts.allowSameOrigin ? { sameOriginOverride: true } : {}),
|
|
940
967
|
});
|
|
@@ -950,8 +977,9 @@ export async function recordGateApproval(
|
|
|
950
977
|
));
|
|
951
978
|
if (record.planDigest) {
|
|
952
979
|
console.error(formatInfo(
|
|
953
|
-
`This approves the plan ${record.planDigest}
|
|
954
|
-
|
|
980
|
+
`This approves the plan ${record.planDigest}` +
|
|
981
|
+
(record.environment !== undefined ? ` in environment "${record.environment}"` : "") +
|
|
982
|
+
", and only that plan. A run whose fresh plan differs refuses rather than applying it.",
|
|
955
983
|
));
|
|
956
984
|
}
|
|
957
985
|
if (approval) {
|