@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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a project's lifecycle ledger lives on the `chant/lifecycle` branch
|
|
3
|
+
* (#2538, #2524 D7, ws-036).
|
|
4
|
+
*
|
|
5
|
+
* A workspace member writes every lifecycle store under
|
|
6
|
+
* `_members/<member>/` on the one existing branch: releases, snapshots, runs,
|
|
7
|
+
* converge records and observation baselines under `<env>/`, gates under
|
|
8
|
+
* `_gates/`, build records under `_builds/`. Its operator leases move the same
|
|
9
|
+
* way, to `refs/chant/lease/_members/<member>/<op>`. Push, fetch, lease and
|
|
10
|
+
* staleness code are untouched: they still see one branch and plain refs.
|
|
11
|
+
*
|
|
12
|
+
* Everything else keeps today's flat layout, byte for byte:
|
|
13
|
+
*
|
|
14
|
+
* - a project with no `chant.workspace.json` between it and the git root
|
|
15
|
+
* (level 0);
|
|
16
|
+
* - the root member `.`;
|
|
17
|
+
* - a directory no member owns;
|
|
18
|
+
* - an example group's match, since a group has no ledger of its own (ws-051).
|
|
19
|
+
*
|
|
20
|
+
* A member of kind `workspace` holds a nested declaration. The nested
|
|
21
|
+
* workspace's ledgers sit inside the outer member's, so its member `api` in
|
|
22
|
+
* the outer member `platform` writes under `_members/platform/_members/api/`,
|
|
23
|
+
* and the nested root member `.` writes under `_members/platform/`.
|
|
24
|
+
*
|
|
25
|
+
* Level 0 pays one upward `existsSync` walk here and nothing more. The
|
|
26
|
+
* declaration reader under `../workspace/` is imported only once a
|
|
27
|
+
* declaration has been found, so a project without one never loads workspace
|
|
28
|
+
* code (#2525 rule 5, pinned by #2526's goldens).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
33
|
+
import { findWorkspaceRoot } from "../project-root";
|
|
34
|
+
|
|
35
|
+
/** The directory on `chant/lifecycle` that holds every member's ledger. */
|
|
36
|
+
export const MEMBERS_DIR = "_members";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The first chant that writes member ledgers. A member whose own toolchain
|
|
40
|
+
* resolves an older chant still writes the flat layout; `chant workspace
|
|
41
|
+
* check` fails when two flat writers share an environment name (#2524 D7).
|
|
42
|
+
*/
|
|
43
|
+
export const MEMBER_LEDGER_FLOOR = "0.81.0";
|
|
44
|
+
|
|
45
|
+
/** Where one project's ledger lives. */
|
|
46
|
+
export interface MemberLedger {
|
|
47
|
+
/**
|
|
48
|
+
* The owning member's path of names, outermost first: `["api"]`, or
|
|
49
|
+
* `["platform", "api"]` inside a nested workspace. Empty for the flat
|
|
50
|
+
* layout.
|
|
51
|
+
*/
|
|
52
|
+
members: string[];
|
|
53
|
+
/** The directory prefix on the branch: `""`, or `_members/<member>/` per level. */
|
|
54
|
+
prefix: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const FLAT: MemberLedger = { members: [], prefix: "" };
|
|
58
|
+
|
|
59
|
+
/** The branch prefix for a path of member names. */
|
|
60
|
+
export function memberLedgerPrefix(members: readonly string[]): string {
|
|
61
|
+
return members.map((m) => `${MEMBERS_DIR}/${m}/`).join("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when a project sits in a workspace whose declaration cannot be read.
|
|
66
|
+
* Writing the flat layout then could land in another member's ledger, so no
|
|
67
|
+
* lifecycle store guesses.
|
|
68
|
+
*/
|
|
69
|
+
export class MemberLedgerError extends Error {
|
|
70
|
+
constructor(message: string) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = "MemberLedgerError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const cache = new Map<string, Promise<MemberLedger>>();
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The ledger of the project at `projectDir` (any directory inside it will do;
|
|
80
|
+
* ownership goes to the deepest member directory holding it). Results are
|
|
81
|
+
* cached per directory for the life of the process.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveMemberLedger(projectDir: string): Promise<MemberLedger> {
|
|
84
|
+
const dir = resolve(projectDir);
|
|
85
|
+
let hit = cache.get(dir);
|
|
86
|
+
if (!hit) {
|
|
87
|
+
hit = resolveUncached(dir);
|
|
88
|
+
cache.set(dir, hit);
|
|
89
|
+
// A failure is not remembered: the next call reads the declaration again.
|
|
90
|
+
hit.catch(() => cache.delete(dir));
|
|
91
|
+
}
|
|
92
|
+
return hit;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Forget cached resolutions, for tests that write a declaration mid-run. */
|
|
96
|
+
export function clearMemberLedgerCache(): void {
|
|
97
|
+
cache.clear();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function treePath(root: string, dir: string): string {
|
|
101
|
+
const rel = relative(root, dir);
|
|
102
|
+
return rel === "" ? "" : rel.split(sep).join("/");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function resolveUncached(dir: string): Promise<MemberLedger> {
|
|
106
|
+
const found = findWorkspaceRoot(dir);
|
|
107
|
+
if (!found) return FLAT;
|
|
108
|
+
return ledgerIn(found.dir, dir);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The ledger of `dir` in the workspace whose declaration sits in `root`. When
|
|
113
|
+
* that workspace is nested, its whole ledger sits inside the ledger of the
|
|
114
|
+
* outer member holding `root`. The git root ends the walk outwards, as it
|
|
115
|
+
* ends {@link findWorkspaceRoot}'s.
|
|
116
|
+
*/
|
|
117
|
+
async function ledgerIn(root: string, dir: string): Promise<MemberLedger> {
|
|
118
|
+
// Only now does workspace code load.
|
|
119
|
+
const { readDeclaration, resolveGroups, ownerOf, WorkspaceReadError } = await import("../workspace/declaration");
|
|
120
|
+
const { workingTree } = await import("../workspace/tree");
|
|
121
|
+
|
|
122
|
+
let own: string[];
|
|
123
|
+
try {
|
|
124
|
+
const tree = workingTree(root);
|
|
125
|
+
const declaration = readDeclaration(tree);
|
|
126
|
+
const owner = ownerOf(declaration, resolveGroups(declaration, tree), treePath(root, dir));
|
|
127
|
+
own = owner && "member" in owner && owner.member.dir !== "." ? [owner.member.name] : [];
|
|
128
|
+
} catch (err) {
|
|
129
|
+
if (!(err instanceof WorkspaceReadError)) throw err;
|
|
130
|
+
throw new MemberLedgerError(
|
|
131
|
+
`cannot place this project's lifecycle ledger: ${err.describe()}. ` +
|
|
132
|
+
`A project inside a workspace writes under ${MEMBERS_DIR}/<member>/ on chant/lifecycle, ` +
|
|
133
|
+
`and chant does not guess the member. Fix the declaration and retry.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let outer: MemberLedger = FLAT;
|
|
138
|
+
const parent = dirname(root);
|
|
139
|
+
if (parent !== root && !existsSync(join(root, ".git"))) {
|
|
140
|
+
const outerFound = findWorkspaceRoot(parent);
|
|
141
|
+
if (outerFound) outer = await ledgerIn(outerFound.dir, root);
|
|
142
|
+
}
|
|
143
|
+
const members = [...outer.members, ...own];
|
|
144
|
+
return { members, prefix: memberLedgerPrefix(members) };
|
|
145
|
+
}
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
* The identity is therefore a claim about consequence, not about provenance.
|
|
37
37
|
* That is the claim an approver is actually making.
|
|
38
38
|
*/
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { canonicalJson } from "../effect-receipt";
|
|
40
|
+
import { contentDigest } from "../content-digest";
|
|
41
41
|
|
|
42
42
|
/** The hash a plan digest is taken with, and the prefix every digest carries. */
|
|
43
43
|
export const PLAN_DIGEST_ALGORITHM = "sha256";
|
|
@@ -54,15 +54,15 @@ const PLAN_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
|
54
54
|
* to a terraform plan must not be satisfiable by a lifecycle diff that
|
|
55
55
|
* happened to serialize identically.
|
|
56
56
|
*
|
|
57
|
-
* `subject` is
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* `subject` is put in canonical form by `canonicalJson` (../effect-receipt.ts)
|
|
58
|
+
* and hashed by `contentDigest` (../content-digest.ts), so object key order,
|
|
59
|
+
* which neither terraform's JSON writer nor `JSON.parse` guarantees across
|
|
60
|
+
* versions, does not change the answer. It is the caller's job to
|
|
60
61
|
* hand in a projection that already excludes the volatile fields this
|
|
61
62
|
* module's doc comment lists.
|
|
62
63
|
*/
|
|
63
64
|
export function computePlanDigest(kind: string, subject: unknown): string {
|
|
64
|
-
|
|
65
|
-
return `${PLAN_DIGEST_ALGORITHM}:${getRuntime().hash(canonical)}`;
|
|
65
|
+
return contentDigest(canonicalJson({ kind, subject }));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -297,5 +297,25 @@ describe("release-ledger", () => {
|
|
|
297
297
|
expect(records[0].runOrigin).toEqual(input.runOrigin);
|
|
298
298
|
});
|
|
299
299
|
});
|
|
300
|
+
|
|
301
|
+
test("a redeploy record round-trips and stays the current release (#2604)", async () => {
|
|
302
|
+
await withTestDir(async (dir) => {
|
|
303
|
+
await initRepo(dir);
|
|
304
|
+
const first = makeInput({ runId: "run-1", timestamp: "2026-01-01T00:00:00.000Z" });
|
|
305
|
+
const redeploy = makeInput({
|
|
306
|
+
runId: "run-2",
|
|
307
|
+
timestamp: "2026-01-02T00:00:00.000Z",
|
|
308
|
+
redeploys: { env: first.env, runId: "run-1", timestamp: "2026-01-01T00:00:00.000Z" },
|
|
309
|
+
});
|
|
310
|
+
await appendReleaseRecord(first, { cwd: dir });
|
|
311
|
+
await appendReleaseRecord(redeploy, { cwd: dir });
|
|
312
|
+
const { records, malformed } = await readReleaseLedger(first.env, { cwd: dir });
|
|
313
|
+
expect(malformed).toBe(0);
|
|
314
|
+
expect(records[1].redeploys).toEqual(redeploy.redeploys);
|
|
315
|
+
const current = latestPerComponent(records).get(first.component)!;
|
|
316
|
+
expect(current.runId).toBe("run-2");
|
|
317
|
+
expect(current.digest).toBe(first.digest);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
300
320
|
});
|
|
301
321
|
});
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { sortedJsonReplacer } from "../utils";
|
|
32
|
+
import { flagLegacyDigest, warnOnLegacyDigests, withoutReadFlags, type LegacyDigestFlag } from "./legacy-digest";
|
|
32
33
|
import { appendReleaseRecordLine, readReleaseLedgerLines, listLedgerEnvironments as gitListLedgerEnvironments } from "./git";
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -50,6 +51,20 @@ export interface RunOrigin {
|
|
|
50
51
|
url?: string;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Points at one earlier release record (#2530, #2531, #2604). A record has no id of its own,
|
|
56
|
+
* so it is named by the environment whose ledger holds it plus the run id and
|
|
57
|
+
* timestamp it was written with, which is enough to find the line again.
|
|
58
|
+
*/
|
|
59
|
+
export interface ReleaseRef {
|
|
60
|
+
/** The environment whose ledger holds the referenced record. */
|
|
61
|
+
env: string;
|
|
62
|
+
/** The referenced record's `runId`. */
|
|
63
|
+
runId: string;
|
|
64
|
+
/** The referenced record's `timestamp`. */
|
|
65
|
+
timestamp: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
/**
|
|
54
69
|
* One immutable deploy record: `(component, env, artifact digest, git sha,
|
|
55
70
|
* run id, timestamp, actor)`, referencing the build archive by digest — the
|
|
@@ -128,6 +143,33 @@ export interface ReleaseRecord {
|
|
|
128
143
|
* bytes each cluster got. Absent when `digest` is already input-side.
|
|
129
144
|
*/
|
|
130
145
|
inputDigest?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Optional: set when this release was a promotion (`chant components
|
|
148
|
+
* promote`, #2530). Names the source environment's release whose digest
|
|
149
|
+
* was deployed here without a build. Absent on every other record.
|
|
150
|
+
*/
|
|
151
|
+
promotedFrom?: ReleaseRef;
|
|
152
|
+
/**
|
|
153
|
+
* Optional: set when this release was a rollback (`chant components
|
|
154
|
+
* rollback`, #2531). Names the earlier release of the same environment
|
|
155
|
+
* whose digest was deployed again. Absent on every other record.
|
|
156
|
+
*/
|
|
157
|
+
restores?: ReleaseRef;
|
|
158
|
+
/**
|
|
159
|
+
* Set on read, never written. `["legacy-digest"]` when `digest`,
|
|
160
|
+
* `manifestDigest` or `inputDigest` was recorded by the 32-bit hash chant
|
|
161
|
+
* used before real SHA-256 (#2514). Such a record is kept exactly as it
|
|
162
|
+
* was written, never re-keyed, and is accepted through
|
|
163
|
+
* `LEGACY_DIGEST_ACCEPTED_THROUGH` (./legacy-digest.ts).
|
|
164
|
+
*/
|
|
165
|
+
flags?: LegacyDigestFlag[];
|
|
166
|
+
/**
|
|
167
|
+
* Optional: set when this release was a redeploy (`chant components
|
|
168
|
+
* redeploy`, #2604). Names the release the ledger recorded as current,
|
|
169
|
+
* whose digest was deployed again, usually after a deploy that failed
|
|
170
|
+
* partway. Absent on every other record.
|
|
171
|
+
*/
|
|
172
|
+
redeploys?: ReleaseRef;
|
|
131
173
|
}
|
|
132
174
|
|
|
133
175
|
/** Required, non-empty-string fields every `ReleaseRecord` must carry. */
|
|
@@ -160,7 +202,7 @@ export function validateReleaseRecord(record: Partial<ReleaseRecord>): string[]
|
|
|
160
202
|
}
|
|
161
203
|
|
|
162
204
|
/** Input a caller supplies to record one deploy — `version` is filled in, everything else is required (see `ReleaseRecord`). */
|
|
163
|
-
export type ReleaseRecordInput = Omit<ReleaseRecord, "version">;
|
|
205
|
+
export type ReleaseRecordInput = Omit<ReleaseRecord, "version" | "flags">;
|
|
164
206
|
|
|
165
207
|
/**
|
|
166
208
|
* Resolve a run id and its origin from the same environment, in one step, so
|
|
@@ -227,7 +269,8 @@ export async function appendReleaseRecord(
|
|
|
227
269
|
const missing = validateReleaseRecord(input);
|
|
228
270
|
if (missing.length > 0) throw new InvalidReleaseRecordError(missing);
|
|
229
271
|
|
|
230
|
-
|
|
272
|
+
// `flags` are read-side only (#2514); a record passed back in never persists them.
|
|
273
|
+
const record: ReleaseRecord = { version: 1, ...withoutReadFlags(input as ReleaseRecordInput & { flags?: unknown }) };
|
|
231
274
|
const json = JSON.stringify(record, sortedJsonReplacer);
|
|
232
275
|
const commit = await appendReleaseRecordLine(record.env, json, opts);
|
|
233
276
|
return { commit, record };
|
|
@@ -253,11 +296,18 @@ export async function readReleaseLedger(
|
|
|
253
296
|
malformed++;
|
|
254
297
|
continue;
|
|
255
298
|
}
|
|
256
|
-
|
|
299
|
+
// chant #2514 — a digest from before real SHA-256 is flagged, never re-keyed.
|
|
300
|
+
records.push(flagLegacyDigest(parsed, [parsed.digest, parsed.manifestDigest, parsed.inputDigest]));
|
|
257
301
|
} catch {
|
|
258
302
|
malformed++;
|
|
259
303
|
}
|
|
260
304
|
}
|
|
305
|
+
// chant #2514 — say once that this ledger holds values in the old form.
|
|
306
|
+
warnOnLegacyDigests(
|
|
307
|
+
records.flatMap((r) => [r.digest, r.manifestDigest, r.inputDigest]),
|
|
308
|
+
"release ledger",
|
|
309
|
+
opts?.cwd,
|
|
310
|
+
);
|
|
261
311
|
return { records, malformed };
|
|
262
312
|
}
|
|
263
313
|
|
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
mergeLiveEvidence,
|
|
8
8
|
type LiveComponentEvidence,
|
|
9
9
|
type LiveNameMapping,
|
|
10
|
+
type EntityLiveRead,
|
|
10
11
|
} from "./status";
|
|
11
12
|
import type { ReleaseRecord } from "./release-ledger";
|
|
12
|
-
import type { ChangeSet } from "./change-set";
|
|
13
|
+
import type { ChangeSet, ChangeSetEntry } from "./change-set";
|
|
14
|
+
import type { ResourceMetadata } from "../lexicon";
|
|
13
15
|
import type { BuildLedgerEntry, ComponentBomSummary } from "./build-ledger";
|
|
14
16
|
|
|
15
17
|
function record(overrides?: Partial<ReleaseRecord>): ReleaseRecord {
|
|
@@ -631,3 +633,151 @@ describe("partial unit presence is said, not rounded down to nothing (#1528)", (
|
|
|
631
633
|
expect(merged.get("operator")!.partial).toEqual(partial);
|
|
632
634
|
});
|
|
633
635
|
});
|
|
636
|
+
|
|
637
|
+
// ── Live evidence that contradicts the recorded release (#2513) ─────────────
|
|
638
|
+
|
|
639
|
+
describe("drifted: live evidence contradicts the recorded release (#2513)", () => {
|
|
640
|
+
const released = record({ component: "svc", digest: "sha256:released", gitSha: "1111111aaaaaaa", timestamp: "2026-02-01T00:00:00.000Z" });
|
|
641
|
+
const live = (meta: Partial<ResourceMetadata> = {}): ResourceMetadata => ({ type: "T", status: "ACTIVE", ownership: "owned", ...meta });
|
|
642
|
+
const entry = (overrides: Partial<ChangeSetEntry> = {}): ChangeSetEntry => ({
|
|
643
|
+
name: "svc",
|
|
644
|
+
type: "T",
|
|
645
|
+
action: "noop",
|
|
646
|
+
evidence: { declared: true, inSnapshot: true, live: true, observed: true },
|
|
647
|
+
ownership: "owned",
|
|
648
|
+
...overrides,
|
|
649
|
+
});
|
|
650
|
+
const reconcile = (entries: ChangeSetEntry[], reads: Map<string, EntityLiveRead>, rec: ReleaseRecord = released) =>
|
|
651
|
+
reconcileStatus("prod", [rec], {
|
|
652
|
+
liveEvidence: liveEvidenceFromChangeSet({ env: "prod", entries }, undefined, { observations: reads }),
|
|
653
|
+
})[0];
|
|
654
|
+
|
|
655
|
+
test("reconciled: the reported identity matches digest and (abbreviated) git sha", () => {
|
|
656
|
+
const row = reconcile([entry()], new Map([["svc", { now: live({ attributes: { digest: "sha256:released", gitSha: "1111111" } }) }]]));
|
|
657
|
+
expect(row.reconciliation).toBe("reconciled");
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test("reconciled: a bare hex digest names the same digest as its sha256: form", () => {
|
|
661
|
+
const row = reconcile([entry()], new Map([["svc", { now: live({ attributes: { digest: "released" } }) }]]));
|
|
662
|
+
expect(row.reconciliation).toBe("reconciled");
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("reconciled: a pinned record's inputDigest is an accepted identity", () => {
|
|
666
|
+
const row = reconcile(
|
|
667
|
+
[entry()],
|
|
668
|
+
new Map([["svc", { now: live({ attributes: { digest: "sha256:input" } }) }]]),
|
|
669
|
+
{ ...released, inputDigest: "sha256:input" },
|
|
670
|
+
);
|
|
671
|
+
expect(row.reconciliation).toBe("reconciled");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("drifted: a foreign digest", () => {
|
|
675
|
+
const row = reconcile([entry()], new Map([["svc", { now: live({ attributes: { digest: "sha256:other" } }) }]]));
|
|
676
|
+
expect(row.reconciliation).toBe("drifted");
|
|
677
|
+
expect(row.detail).toContain("sha256:other");
|
|
678
|
+
expect(row.live).toBe(true);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("drifted: an unknown git sha", () => {
|
|
682
|
+
const row = reconcile([entry()], new Map([["svc", { now: live({ attributes: { gitSha: "2222222" } }) }]]));
|
|
683
|
+
expect(row.reconciliation).toBe("drifted");
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test("drifted: an update against a snapshot taken at or after the release", () => {
|
|
687
|
+
const row = reconcile(
|
|
688
|
+
[entry({ action: "update", deltas: [{ path: "attributes.revision", oldValue: "6", newValue: "7" }] })],
|
|
689
|
+
new Map([["svc", { now: live(), then: live(), thenAt: "2026-02-01T00:00:00.000Z" }]]),
|
|
690
|
+
);
|
|
691
|
+
expect(row.reconciliation).toBe("drifted");
|
|
692
|
+
expect(row.detail).toContain("attributes.revision");
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("reconciled: an update against a snapshot older than the release is the release itself", () => {
|
|
696
|
+
const row = reconcile(
|
|
697
|
+
[entry({ action: "update", deltas: [{ path: "lastUpdated", oldValue: "a", newValue: "b" }] })],
|
|
698
|
+
new Map([["svc", { now: live(), then: live(), thenAt: "2026-01-31T23:59:59.000Z" }]]),
|
|
699
|
+
);
|
|
700
|
+
expect(row.reconciliation).toBe("reconciled");
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test("drifted: owned at the snapshot, foreign now", () => {
|
|
704
|
+
const row = reconcile(
|
|
705
|
+
[entry({ ownership: "foreign" })],
|
|
706
|
+
new Map([["svc", { now: live({ ownership: "foreign" }), then: live({ ownership: "owned" }), thenAt: "2026-02-02T00:00:00.000Z" }]]),
|
|
707
|
+
);
|
|
708
|
+
expect(row.reconciliation).toBe("drifted");
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test("drifted: the ownership marker now names a different stack", () => {
|
|
712
|
+
const row = reconcile(
|
|
713
|
+
[entry()],
|
|
714
|
+
new Map([["svc", {
|
|
715
|
+
now: live({ marker: { stack: "other", env: "prod" } }),
|
|
716
|
+
then: live({ marker: { stack: "app", env: "prod" } }),
|
|
717
|
+
thenAt: "2026-02-02T00:00:00.000Z",
|
|
718
|
+
}]]),
|
|
719
|
+
);
|
|
720
|
+
expect(row.reconciliation).toBe("drifted");
|
|
721
|
+
expect(row.detail).toContain("app/prod");
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
test("reconciled: ownership going to unknown is not a contradiction", () => {
|
|
725
|
+
const row = reconcile(
|
|
726
|
+
[entry({ ownership: "unknown" })],
|
|
727
|
+
new Map([["svc", { now: live({ ownership: "unknown" }), then: live({ ownership: "owned" }), thenAt: "2026-02-02T00:00:00.000Z" }]]),
|
|
728
|
+
);
|
|
729
|
+
expect(row.reconciliation).toBe("reconciled");
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test("stale: confirmed absent, whatever the snapshot said", () => {
|
|
733
|
+
const row = reconcile(
|
|
734
|
+
[entry({ action: "create", evidence: { declared: true, inSnapshot: true, live: false, observed: true } })],
|
|
735
|
+
new Map([["svc", { then: live({ attributes: { digest: "sha256:other" } }), thenAt: "2026-02-02T00:00:00.000Z" }]]),
|
|
736
|
+
);
|
|
737
|
+
expect(row.reconciliation).toBe("stale");
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test("unknown: not observed never becomes drifted", () => {
|
|
741
|
+
const row = reconcile(
|
|
742
|
+
[entry({ action: "unobserved", unobservedReason: "read-failed", evidence: { declared: true, inSnapshot: true, live: false, observed: false } })],
|
|
743
|
+
new Map([["svc", { then: live({ attributes: { digest: "sha256:other" } }), thenAt: "2026-02-02T00:00:00.000Z" }]]),
|
|
744
|
+
);
|
|
745
|
+
expect(row.reconciliation).toBe("unknown");
|
|
746
|
+
expect(row.live).toBeUndefined();
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test("a mapped component drifts when any of its live resources contradicts the record", () => {
|
|
750
|
+
const evidence = liveEvidenceFromChangeSet(
|
|
751
|
+
{ env: "prod", entries: [entry({ name: "svc-a" }), entry({ name: "svc-b" })] },
|
|
752
|
+
new Map([["svc", ["svc-a", "svc-b"]]]),
|
|
753
|
+
{
|
|
754
|
+
observations: new Map([
|
|
755
|
+
["svc-a", { now: live({ attributes: { digest: "sha256:released" } }) }],
|
|
756
|
+
["svc-b", { now: live({ attributes: { digest: "sha256:other" } }) }],
|
|
757
|
+
]),
|
|
758
|
+
},
|
|
759
|
+
);
|
|
760
|
+
const row = reconcileStatus("prod", [released], { liveEvidence: evidence })[0];
|
|
761
|
+
expect(row.reconciliation).toBe("drifted");
|
|
762
|
+
expect(row.detail).toContain("svc-b");
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("identity and drift signals survive the stack-presence overlay", () => {
|
|
766
|
+
const base = liveEvidenceFromChangeSet({ env: "prod", entries: [entry()] }, undefined, {
|
|
767
|
+
observations: new Map([["svc", { now: live({ attributes: { digest: "sha256:other" } }) }]]),
|
|
768
|
+
});
|
|
769
|
+
const merged = mergeLiveEvidence(base, new Map([["svc", { live: true, ownership: "owned" }]]));
|
|
770
|
+
const row = reconcileStatus("prod", [released], { liveEvidence: merged })[0];
|
|
771
|
+
expect(row.reconciliation).toBe("drifted");
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("without per-entity reads the evidence is unchanged from before (no new fields)", () => {
|
|
775
|
+
const evidence = liveEvidenceFromChangeSet({ env: "prod", entries: [entry()] });
|
|
776
|
+
expect(evidence.get("svc")).toEqual({
|
|
777
|
+
live: true,
|
|
778
|
+
action: "noop",
|
|
779
|
+
ownership: "owned",
|
|
780
|
+
rollup: { total: 1, present: 1, absent: 0, unobserved: 0 },
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
});
|