@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant build --components --generate` inside a workspace member (#2542).
|
|
3
|
+
* Component discovery is stubbed with a fixed graph; the github generator is
|
|
4
|
+
* the real one, so the files checked here are what a user gets. A project
|
|
5
|
+
* outside any workspace is held to its old output byte for byte, and to a
|
|
6
|
+
* snapshot, since level 0 must not move (#2525).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import type { DriverComponent } from "../../components/driver";
|
|
15
|
+
import type { ComponentPipelineOptions } from "../../lexicon";
|
|
16
|
+
import type { ParsedArgs } from "../registry";
|
|
17
|
+
import { generateGithubPipeline } from "@intentius/chant-lexicon-github/components/generate-pipeline";
|
|
18
|
+
import { twoMemberWorkspace } from "../../workspace/__fixtures__/two-member-workspace";
|
|
19
|
+
|
|
20
|
+
const components: DriverComponent[] = [
|
|
21
|
+
{ name: "shared-alb", dependsOn: [], deploy: [] },
|
|
22
|
+
{ name: "api", dependsOn: ["shared-alb"], deploy: [] },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const received: (ComponentPipelineOptions | undefined)[] = [];
|
|
26
|
+
vi.mock("../../components/cli-support", () => ({
|
|
27
|
+
generateComponentsPipeline: async (_path: string, _lexicon: string, options?: ComponentPipelineOptions) => {
|
|
28
|
+
received.push(options);
|
|
29
|
+
const { yaml, stages, jobs, env } = generateGithubPipeline(components, options);
|
|
30
|
+
return { success: true, yaml, stages, jobs, env };
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
vi.mock("../../config", async () => {
|
|
34
|
+
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
35
|
+
return { ...actual, loadChantConfigUpward: async () => ({ config: {} }) };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const { runBuild } = await import("./build");
|
|
39
|
+
|
|
40
|
+
function args(overrides: Partial<ParsedArgs>): ParsedArgs {
|
|
41
|
+
return {
|
|
42
|
+
command: "build",
|
|
43
|
+
path: ".",
|
|
44
|
+
format: "",
|
|
45
|
+
fix: false,
|
|
46
|
+
watch: false,
|
|
47
|
+
verbose: false,
|
|
48
|
+
help: false,
|
|
49
|
+
live: false,
|
|
50
|
+
components: true,
|
|
51
|
+
generate: "github",
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let repo: string;
|
|
57
|
+
let logs: string[];
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
received.length = 0;
|
|
60
|
+
logs = [];
|
|
61
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
62
|
+
vi.spyOn(console, "log").mockImplementation((s: string) => {
|
|
63
|
+
logs.push(s);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.restoreAllMocks();
|
|
68
|
+
rmSync(repo, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("a project inside a workspace member", () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
repo = twoMemberWorkspace();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("the pipeline is written to the member's file at the repository root, filtered and recorded", async () => {
|
|
77
|
+
expect(await runBuild({ args: args({ path: join(repo, "services/api"), env: "staging" }), plugins: [], serializers: [] })).toBe(0);
|
|
78
|
+
|
|
79
|
+
expect(received[0]?.member).toEqual({ name: "api", dir: "services/api", file: ".github/workflows/chant-api-staging.yml" });
|
|
80
|
+
const file = join(repo, ".github/workflows/chant-api-staging.yml");
|
|
81
|
+
const text = readFileSync(file, "utf-8");
|
|
82
|
+
const [header, ...rest] = text.split("\n");
|
|
83
|
+
expect(header).toContain("Regenerate with: chant build --components --generate github --env staging");
|
|
84
|
+
expect(rest.join("\n")).toBe(generateGithubPipeline(components, { env: "staging", member: received[0]!.member }).yaml);
|
|
85
|
+
expect(text).toContain("working-directory: services/api");
|
|
86
|
+
|
|
87
|
+
const record = JSON.parse(readFileSync(join(repo, "services/api/.chant/generated.json"), "utf-8"));
|
|
88
|
+
expect(record.files).toEqual([{ path: ".github/workflows/chant-api-staging.yml", command: "chant build --components --generate github --env staging", env: "staging" }]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("two members write two files and never collide", async () => {
|
|
92
|
+
await runBuild({ args: args({ path: join(repo, "services/api"), env: "prod" }), plugins: [], serializers: [] });
|
|
93
|
+
await runBuild({ args: args({ path: join(repo, "apps/web"), env: "prod" }), plugins: [], serializers: [] });
|
|
94
|
+
expect(existsSync(join(repo, ".github/workflows/chant-api-prod.yml"))).toBe(true);
|
|
95
|
+
expect(existsSync(join(repo, ".github/workflows/chant-web-prod.yml"))).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("--format json names the member and the file and writes nothing", async () => {
|
|
99
|
+
expect(await runBuild({ args: args({ path: join(repo, "apps/web"), env: "prod", format: "json" }), plugins: [], serializers: [] })).toBe(0);
|
|
100
|
+
const out = JSON.parse(logs.join("\n"));
|
|
101
|
+
expect(out).toMatchObject({ member: "web", file: ".github/workflows/chant-web-prod.yml", env: "prod" });
|
|
102
|
+
expect(existsSync(join(repo, ".github/workflows/chant-web-prod.yml"))).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("an example project in a group is not a member, and gets the plain pipeline", async () => {
|
|
106
|
+
expect(await runBuild({ args: args({ path: join(repo, "examples/demo") }), plugins: [], serializers: [] })).toBe(0);
|
|
107
|
+
expect(received[0]).toEqual({ env: undefined });
|
|
108
|
+
expect(logs.join("\n")).toBe(generateGithubPipeline(components, {}).yaml);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("a project outside any workspace (level 0)", () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
repo = realpathSync(mkdtempSync(join(tmpdir(), "chant-build-level0-")));
|
|
115
|
+
execFileSync("git", ["init", "-q"], { cwd: repo });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("the pipeline is the generator's output, unchanged", async () => {
|
|
119
|
+
const output = join(repo, "pipeline.yml");
|
|
120
|
+
expect(await runBuild({ args: args({ path: repo, env: "staging", output }), plugins: [], serializers: [] })).toBe(0);
|
|
121
|
+
expect(received[0]).toEqual({ env: "staging" });
|
|
122
|
+
const text = readFileSync(output, "utf-8");
|
|
123
|
+
expect(text).toBe(generateGithubPipeline(components, { env: "staging" }).yaml);
|
|
124
|
+
expect(text).toMatchSnapshot();
|
|
125
|
+
expect(existsSync(join(repo, ".chant"))).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -70,6 +70,17 @@ describe("runBuild --components --generate (chant #1108 build-time parameters)",
|
|
|
70
70
|
vi.restoreAllMocks();
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
test("--promote-to is forwarded to the generator as promoteTo (#2575)", async () => {
|
|
74
|
+
generateComponentsPipelineMock.mockResolvedValue({ success: true, yaml: "stages: []", stages: [], jobs: [] });
|
|
75
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
76
|
+
|
|
77
|
+
const exit = await runBuild({ args: makeArgs({ env: "staging", promoteTo: "prod" }), plugins: [], serializers: [] });
|
|
78
|
+
|
|
79
|
+
expect(exit).toBe(0);
|
|
80
|
+
expect(generateComponentsPipelineMock).toHaveBeenCalledWith(".", "gitlab", { env: "staging", promoteTo: "prod" }, undefined, []);
|
|
81
|
+
vi.restoreAllMocks();
|
|
82
|
+
});
|
|
83
|
+
|
|
73
84
|
test("chant.config.ts's declared buildParams resolve, log, and are forwarded to generateComponentsPipeline", async () => {
|
|
74
85
|
loadChantConfigUpwardMock.mockResolvedValue({
|
|
75
86
|
config: { buildParams: { tier: { type: "string", default: "light" } } },
|
|
@@ -6,6 +6,7 @@ import type { CommandContext } from "../registry";
|
|
|
6
6
|
import { generateComponentsPipeline } from "../../components/cli-support";
|
|
7
7
|
import { loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
8
8
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
9
|
+
import { findWorkspaceRoot } from "../../project-root";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* `chant build --components --generate <lexicon>` — generate mode (#563,
|
|
@@ -44,6 +45,33 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
44
45
|
return 1;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// A project inside a workspace member gets that member's pipeline (#2542):
|
|
49
|
+
// path-filtered, run in the member's directory, written to a file named
|
|
50
|
+
// after the member and recorded as the member's generated file. The
|
|
51
|
+
// workspace module loads only when a declaration was found, so a project
|
|
52
|
+
// outside any workspace generates exactly what it did before.
|
|
53
|
+
const found = findWorkspaceRoot(resolve(args.path));
|
|
54
|
+
let plan: import("../../workspace/member-pipeline").MemberComponentPlan | undefined;
|
|
55
|
+
let memberPipeline: typeof import("../../workspace/member-pipeline") | undefined;
|
|
56
|
+
if (found) {
|
|
57
|
+
memberPipeline = await import("../../workspace/member-pipeline");
|
|
58
|
+
try {
|
|
59
|
+
plan = memberPipeline.planMemberComponentPipeline({
|
|
60
|
+
projectDir: resolve(args.path),
|
|
61
|
+
found,
|
|
62
|
+
lexicon,
|
|
63
|
+
env: args.env,
|
|
64
|
+
promoteTo: args.promoteTo,
|
|
65
|
+
output: args.output,
|
|
66
|
+
params: args.param,
|
|
67
|
+
paramsFile: args.paramsFile,
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(formatError({ message: memberPipeline.describeError(err) }));
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
// Which lexicons support generate mode is a property of the loaded lexicon
|
|
48
76
|
// plugins (those implementing `generateComponentPipeline`, #688), not a
|
|
49
77
|
// hard-coded core list — `generateComponentsPipeline` returns a descriptive
|
|
@@ -51,7 +79,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
51
79
|
const result = await generateComponentsPipeline(
|
|
52
80
|
args.path,
|
|
53
81
|
lexicon,
|
|
54
|
-
{ env: args.env },
|
|
82
|
+
{ env: args.env, ...(args.promoteTo ? { promoteTo: args.promoteTo } : {}), ...(plan ? { member: plan.member } : {}) },
|
|
55
83
|
args.sandbox,
|
|
56
84
|
paramsResolution.provenance,
|
|
57
85
|
);
|
|
@@ -73,11 +101,17 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
73
101
|
// generator's own resolution, forwarded rather than left for a
|
|
74
102
|
// consumer to re-derive by parsing the YAML back (#2060).
|
|
75
103
|
...(result.env ? { env: result.env } : {}),
|
|
104
|
+
// A workspace member's pipeline (#2542): the member and the file
|
|
105
|
+
// the pipeline belongs in, relative to the repository root.
|
|
106
|
+
...(plan ? { member: plan.member.name, file: plan.file } : {}),
|
|
76
107
|
},
|
|
77
108
|
null,
|
|
78
109
|
2,
|
|
79
110
|
),
|
|
80
111
|
);
|
|
112
|
+
} else if (plan && memberPipeline) {
|
|
113
|
+
memberPipeline.writeMemberComponentPipeline(plan, yaml);
|
|
114
|
+
console.error(formatSuccess(`wrote ${formatBold(plan.file)} for member ${formatBold(plan.member.name)}`));
|
|
81
115
|
} else if (args.output) {
|
|
82
116
|
const outputPath = resolve(args.output);
|
|
83
117
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
@@ -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> {
|
|
@@ -33,6 +33,11 @@ export async function runDevOnboard(ctx: CommandContext): Promise<number> {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const { onboardCommand, printOnboardResult } = await import("../commands/onboard");
|
|
36
|
+
// chant#2578 — so a lexicon the project declares by path is recognised.
|
|
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 }));
|
|
36
41
|
const result = onboardCommand({ name, verbose: ctx.args.verbose });
|
|
37
42
|
await printOnboardResult(result, name);
|
|
38
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
|
}
|
|
@@ -2,6 +2,7 @@ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
2
2
|
import type { ParsedArgs } from "../registry";
|
|
3
3
|
import { DECLARABLE_MARKER, type Declarable } from "../../declarable";
|
|
4
4
|
import { AttrRef } from "../../attrref";
|
|
5
|
+
import { GRAPH_IR_VERSION } from "../../graph-ir";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The aws emulator capability, as the real plugin declares it. `--live`
|
|
@@ -226,6 +227,25 @@ describe("runGraph", () => {
|
|
|
226
227
|
expect(ir.edges).toContainEqual({ from: "subnet", to: "vpc", kind: "ref", viaAttr: "network" });
|
|
227
228
|
});
|
|
228
229
|
|
|
230
|
+
// #2529 — the IR carries its version, as the first key, so a reader can
|
|
231
|
+
// decide how to read the rest before it reads anything else.
|
|
232
|
+
test("--format ir leads with the IR version", async () => {
|
|
233
|
+
lintClean(); discovered();
|
|
234
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
235
|
+
expect(exit).toBe(0);
|
|
236
|
+
const ir = JSON.parse(stdoutBuf.join("\n"));
|
|
237
|
+
expect(ir.version).toBe(GRAPH_IR_VERSION);
|
|
238
|
+
expect(GRAPH_IR_VERSION).toBe(1);
|
|
239
|
+
expect(Object.keys(ir)[0]).toBe("version");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("the other view formats carry no version field", async () => {
|
|
243
|
+
lintClean(); discovered();
|
|
244
|
+
const exit = await runGraph({ args: makeArgs({ format: "mermaid" }), plugins: [], serializers: [] });
|
|
245
|
+
expect(exit).toBe(0);
|
|
246
|
+
expect(stdoutBuf.join("\n")).not.toContain("version");
|
|
247
|
+
});
|
|
248
|
+
|
|
229
249
|
// #1675 — discovery scans sourceDir, but ops live beside it (project root,
|
|
230
250
|
// `ops/`) and are found by discoverOps from the git root. The IR joins them.
|
|
231
251
|
describe("ops outside sourceDir reach the IR (#1675)", () => {
|
|
@@ -2,7 +2,7 @@ import { dirname, resolve } from "node:path";
|
|
|
2
2
|
import { discoverOps } from "../../op/discover";
|
|
3
3
|
import { discover } from "../../discovery/index";
|
|
4
4
|
import { partitionByLexicon, computeStackGraph, build, mergeBuildRootEntities } from "../../build";
|
|
5
|
-
import { buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, type GraphIR, type IRPipeline, type LiveObservation } from "../../graph-ir";
|
|
5
|
+
import { buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, GRAPH_IR_VERSION, type GraphIR, type IRPipeline, type LiveObservation } from "../../graph-ir";
|
|
6
6
|
import { behaviourOverlay } from "../../behaviour-overlay";
|
|
7
7
|
import type { SerializerResult } from "../../serializer";
|
|
8
8
|
import { applyBehaviourOverlay, behaviourRequestFromIr } from "../../behaviour-graph";
|
|
@@ -886,7 +886,9 @@ async function emitIr(
|
|
|
886
886
|
}
|
|
887
887
|
case "ir":
|
|
888
888
|
default:
|
|
889
|
-
|
|
889
|
+
// The version leads the document (#2529), so a reader sees it before
|
|
890
|
+
// anything it might not understand.
|
|
891
|
+
console.log(JSON.stringify({ version: GRAPH_IR_VERSION, ...ir }, null, 2));
|
|
890
892
|
return 0;
|
|
891
893
|
}
|
|
892
894
|
}
|