@intentius/chant 0.80.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts +8 -16
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +4 -5
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +25 -9
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +7 -4
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/run-generate.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +12 -7
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +32 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/promote.d.ts +60 -7
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +32 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +10 -5
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +20 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +4 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +51 -6
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon.d.ts +58 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +41 -8
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +18 -2
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +146 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +28 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +2 -1
- package/src/audit/discover.ts +66 -78
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +4 -9
- package/src/audit/terraform-state.ts +4 -5
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +89 -60
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +18 -0
- package/src/cli/commands/import-agents.ts +6 -2
- package/src/cli/commands/init.ts +22 -3
- package/src/cli/commands/lint.test.ts +17 -23
- package/src/cli/commands/lint.ts +24 -84
- package/src/cli/commands/update.ts +13 -4
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +5 -3
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/init.ts +54 -1
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +37 -9
- package/src/cli/handlers/promote.test.ts +118 -2
- package/src/cli/handlers/promote.ts +82 -16
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +6 -8
- package/src/cli/handlers/run-generate.ts +36 -1
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +28 -5
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +105 -1
- package/src/cli/main.ts +230 -17
- package/src/cli/plugins.ts +17 -17
- package/src/cli/registry.ts +32 -0
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/cli-support.ts +6 -1
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +13 -68
- package/src/components/driver.test.ts +78 -1
- package/src/components/driver.ts +43 -7
- package/src/components/gate-plan.ts +53 -0
- package/src/components/promote.test.ts +276 -2
- package/src/components/promote.ts +119 -15
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +2 -2
- package/src/config.ts +48 -15
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +10 -10
- package/src/discovery/files.ts +25 -81
- package/src/discovery/fold-import.ts +49 -5
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +4 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.ts +108 -10
- package/src/lexicon.ts +79 -1
- package/src/lifecycle/build-ledger-store.ts +11 -10
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +115 -23
- package/src/lifecycle/legacy-digest.ts +60 -19
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +23 -7
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -27
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +162 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +624 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/records-cli.ts +86 -14
- package/src/workspace/records-contract.test.ts +5 -0
- package/src/workspace/records.schema.json +32 -2
- package/src/workspace/records.ts +3 -2
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
- package/dist/components/unbound-gate-approval.d.ts +0 -24
- package/dist/components/unbound-gate-approval.d.ts.map +0 -1
- package/dist/discovery/convergence.d.ts +0 -97
- package/dist/discovery/convergence.d.ts.map +0 -1
- package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
- package/src/components/unbound-gate-approval.test.ts +0 -82
- package/src/components/unbound-gate-approval.ts +0 -51
- package/src/discovery/convergence.test.ts +0 -231
- package/src/discovery/convergence.ts +0 -399
- package/src/op/discover-convergence.test.ts +0 -70
package/src/lifecycle/git.ts
CHANGED
|
@@ -5,9 +5,23 @@
|
|
|
5
5
|
* no working tree changes.
|
|
6
6
|
*/
|
|
7
7
|
import { getRuntime } from "../runtime-adapter";
|
|
8
|
+
import { resolveMemberLedger } from "./member-ledger";
|
|
8
9
|
|
|
9
10
|
const STATE_BRANCH = "chant/lifecycle";
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The branch path of `dir` for the project at `cwd` (#2538): `dir` itself at
|
|
14
|
+
* level 0 and for the root member `.`, `_members/<member>/<dir>` for any
|
|
15
|
+
* other workspace member. Every store in this module and the ones built on it
|
|
16
|
+
* (releases, snapshots, observation baselines, runs, converge records, gates,
|
|
17
|
+
* build records) goes through here, so none of them names the prefix itself.
|
|
18
|
+
* See ./member-ledger.ts for who counts as a member.
|
|
19
|
+
*/
|
|
20
|
+
export async function ledgerDir(dir: string, opts?: { cwd?: string }): Promise<string> {
|
|
21
|
+
const { prefix } = await resolveMemberLedger(opts?.cwd ?? process.cwd());
|
|
22
|
+
return `${prefix}${dir}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
/**
|
|
12
26
|
* The identity chant's ledger commits fall back to when the checkout has none
|
|
13
27
|
* of its own (#2301).
|
|
@@ -71,6 +85,16 @@ function ledgerWriteError(stage: string, path: string, stderr: string): Error {
|
|
|
71
85
|
);
|
|
72
86
|
}
|
|
73
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Bounded retry budget for {@link writeBlobToPath}'s own internal CAS retry
|
|
90
|
+
* (#1959 finding 1). A conflict caused by some *other* env/file entry
|
|
91
|
+
* changing concurrently (the ordinary case — e.g. two operator ticks
|
|
92
|
+
* targeting different envs) is safe to resolve by simply rebuilding the tree
|
|
93
|
+
* against the new tip and retrying; this bounds how many times it does that
|
|
94
|
+
* before giving up and surfacing the conflict.
|
|
95
|
+
*/
|
|
96
|
+
const WRITE_BLOB_RETRY_ATTEMPTS = 5;
|
|
97
|
+
|
|
74
98
|
/**
|
|
75
99
|
* Write a blob to an arbitrary `<environment>/<filename>` path on the orphan
|
|
76
100
|
* branch, preserving every other env/file entry already on the branch.
|
|
@@ -91,43 +115,84 @@ function ledgerWriteError(stage: string, path: string, stderr: string): Error {
|
|
|
91
115
|
* entry name this function's tree-building logic groups by; callers outside
|
|
92
116
|
* this module that pass a non-env value (like `_builds`) are relying on that
|
|
93
117
|
* generic behavior, not on any env-specific semantics.
|
|
118
|
+
*
|
|
119
|
+
* `environment` is relative to the project's ledger (#2538): a workspace
|
|
120
|
+
* member's write lands under `_members/<member>/` ({@link ledgerDir}). The
|
|
121
|
+
* write itself is {@link writeLedgerFiles} with one file.
|
|
94
122
|
*/
|
|
95
|
-
/**
|
|
96
|
-
* Bounded retry budget for {@link writeBlobToPath}'s own internal CAS retry
|
|
97
|
-
* (#1959 finding 1). A conflict caused by some *other* env/file entry
|
|
98
|
-
* changing concurrently (the ordinary case — e.g. two operator ticks
|
|
99
|
-
* targeting different envs) is safe to resolve by simply rebuilding the tree
|
|
100
|
-
* against the new tip and retrying; this bounds how many times it does that
|
|
101
|
-
* before giving up and surfacing the conflict.
|
|
102
|
-
*/
|
|
103
|
-
const WRITE_BLOB_RETRY_ATTEMPTS = 5;
|
|
104
|
-
|
|
105
123
|
export async function writeBlobToPath(
|
|
106
124
|
environment: string,
|
|
107
125
|
filename: string,
|
|
108
126
|
content: string,
|
|
109
127
|
commitMessage: string,
|
|
110
128
|
opts?: { cwd?: string; expectPriorPathSha?: string | null },
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
const dir = await ledgerDir(environment, opts);
|
|
131
|
+
return writeLedgerFiles(
|
|
132
|
+
[{ path: `${dir}/${filename}`, content, ...(opts?.expectPriorPathSha !== undefined ? { expectPriorSha: opts.expectPriorPathSha } : {}) }],
|
|
133
|
+
commitMessage,
|
|
134
|
+
opts,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** One file for {@link writeLedgerFiles}. */
|
|
139
|
+
export interface LedgerFileWrite {
|
|
140
|
+
/** The file's full path on the branch, such as `_members/api/prod/releases.jsonl`. No prefix is added. */
|
|
141
|
+
path: string;
|
|
142
|
+
content: string;
|
|
143
|
+
/**
|
|
144
|
+
* For a read-modify-write caller: the blob sha the caller's content was
|
|
145
|
+
* computed from (`null` for "the file did not exist"). A different sha on
|
|
146
|
+
* the branch is a conflict, never a blind retry.
|
|
147
|
+
*/
|
|
148
|
+
expectPriorSha?: string | null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Write several files to the branch in one commit (#2538). Paths are full
|
|
153
|
+
* branch paths, so one commit can write the ledgers of several members, for
|
|
154
|
+
* example `_members/api/prod/releases.jsonl` and
|
|
155
|
+
* `_members/web/prod/releases.jsonl` together. {@link writeBlobToPath} is the
|
|
156
|
+
* one-file case with the project's own member prefix applied.
|
|
157
|
+
*
|
|
158
|
+
* Pipeline, per attempt: read the directories each path passes through at
|
|
159
|
+
* the branch tip, replace the entries, rebuild the touched trees bottom-up
|
|
160
|
+
* with `git mktree`, `commit-tree` on that tip, then CAS-update the ref.
|
|
161
|
+
* Every entry the write does not touch is carried over as it is, so the tree
|
|
162
|
+
* of an unchanged directory keeps its sha.
|
|
163
|
+
*/
|
|
164
|
+
export async function writeLedgerFiles(
|
|
165
|
+
files: LedgerFileWrite[],
|
|
166
|
+
commitMessage: string,
|
|
167
|
+
opts?: { cwd?: string },
|
|
111
168
|
): Promise<string> {
|
|
112
169
|
const rt = getRuntime();
|
|
113
170
|
const cwd = opts?.cwd;
|
|
171
|
+
if (files.length === 0) throw new Error("writeLedgerFiles needs at least one file");
|
|
172
|
+
// Failures name the first path; one file is the ordinary case.
|
|
173
|
+
const path = files[0].path;
|
|
174
|
+
for (const f of files) {
|
|
175
|
+
const segs = f.path.split("/");
|
|
176
|
+
if (segs.length < 2 || segs.some((seg) => seg === "")) {
|
|
177
|
+
throw new Error(`cannot write ${f.path} on the ${STATE_BRANCH} branch: a ledger path is <dir>/<file>`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
114
180
|
|
|
115
|
-
// 1. Write
|
|
116
|
-
// stdin (no shell involved), so content is written byte-for-byte:
|
|
117
|
-
// `echo` reinterpreting backslash-escape sequences (e.g. `\n`
|
|
118
|
-
// as in a serialized
|
|
119
|
-
// annotation) and no
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
181
|
+
// 1. Write the blobs — hash-object reads from stdin. Fed directly via
|
|
182
|
+
// spawn's stdin (no shell involved), so content is written byte-for-byte:
|
|
183
|
+
// no `sh` `echo` reinterpreting backslash-escape sequences (e.g. `\n`
|
|
184
|
+
// inside JSON, as in a serialized
|
|
185
|
+
// kubectl.kubernetes.io/last-applied-configuration annotation) and no
|
|
186
|
+
// shell-quoting dance around embedded single quotes. Content-addressed
|
|
187
|
+
// and idempotent, so this stays outside the retry loop below.
|
|
188
|
+
const blobs: { path: string; sha: string }[] = [];
|
|
189
|
+
for (const f of files) {
|
|
190
|
+
const blobResult = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd, stdin: f.content });
|
|
191
|
+
if (blobResult.exitCode !== 0) {
|
|
192
|
+
throw ledgerWriteError("git hash-object", f.path, blobResult.stderr);
|
|
193
|
+
}
|
|
194
|
+
blobs.push({ path: f.path, sha: blobResult.stdout.trim() });
|
|
129
195
|
}
|
|
130
|
-
const blobSha = blobResult.stdout.trim();
|
|
131
196
|
|
|
132
197
|
// Resolved once, outside the retry loop: whether the checkout can name a
|
|
133
198
|
// committer does not change between attempts (#2301).
|
|
@@ -156,99 +221,94 @@ export async function writeBlobToPath(
|
|
|
156
221
|
await requireLifecycleLedger({ ...(cwd ? { cwd } : {}) });
|
|
157
222
|
}
|
|
158
223
|
|
|
159
|
-
// 2-5. Read
|
|
160
|
-
// on a conflict (#1959 finding 1).
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
224
|
+
// 2-5. Read the trees, build the commit, and CAS-update the branch ref —
|
|
225
|
+
// retried on a conflict (#1959 finding 1). Before that retry existed a
|
|
226
|
+
// conflict from step 5's `updateRefCAS` simply threw, breaking every caller
|
|
227
|
+
// (observation-baseline.ts, snapshot.ts, build-ledger-store.ts, and — via
|
|
228
|
+
// `appendReleaseRecordLine` below — release-ledger.ts) the moment any
|
|
229
|
+
// concurrent writer touched the orphan branch, e.g. a live `chant
|
|
230
|
+
// operator` ticking a different env.
|
|
166
231
|
//
|
|
167
|
-
// Safety of a *blind* retry (same
|
|
168
|
-
//
|
|
169
|
-
//
|
|
232
|
+
// Safety of a *blind* retry (same content and blob, freshly rebuilt tree)
|
|
233
|
+
// hinges on whether *our own* target paths are what actually caused the
|
|
234
|
+
// conflict:
|
|
170
235
|
// - If some OTHER path changed (the ordinary multi-env/multi-file case),
|
|
171
236
|
// our data is unaffected — rebuilding the tree from the new tip and
|
|
172
237
|
// retrying the ref update is always correct, no matter what kind of
|
|
173
238
|
// write the caller is doing (replace or append).
|
|
174
|
-
// - If OUR
|
|
175
|
-
// computed before that race would silently clobber the other
|
|
176
|
-
// change — safe only for a caller whose
|
|
177
|
-
// replacement (writeObservationBaseline, writeSnapshot,
|
|
239
|
+
// - If one of OUR paths changed concurrently, a blind retry using
|
|
240
|
+
// content computed before that race would silently clobber the other
|
|
241
|
+
// writer's change — safe only for a caller whose content is a
|
|
242
|
+
// self-contained replacement (writeObservationBaseline, writeSnapshot,
|
|
178
243
|
// persistBuildManifest), never for a read-modify-write caller whose
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// fresh before trying again, exactly as they already do.
|
|
244
|
+
// content already embeds a stale read (an append). Since this function
|
|
245
|
+
// can't tell those apart, it does NOT blind-retry in that case — it
|
|
246
|
+
// re-throws immediately so a read-modify-write caller's own outer retry
|
|
247
|
+
// (appendConvergeRecord, appendGateResolution, appendReleaseRecordLine
|
|
248
|
+
// below) can re-read and recompute content fresh before trying again.
|
|
185
249
|
//
|
|
186
|
-
// - A read-modify-write caller must pass `
|
|
250
|
+
// - A read-modify-write caller must pass `expectPriorSha`. Without it
|
|
187
251
|
// attempt 1 has no prior sha to compare, so a commit landing between the
|
|
188
|
-
// caller's baseline read and this function's first tree read reads as
|
|
189
|
-
// ambient starting state rather than a conflict, and is overwritten
|
|
190
|
-
// the caller's stale content.
|
|
252
|
+
// caller's baseline read and this function's first tree read reads as
|
|
253
|
+
// the ambient starting state rather than a conflict, and is overwritten
|
|
254
|
+
// from the caller's stale content.
|
|
255
|
+
const prior = new Map<string, string | null | undefined>(files.map((f) => [f.path, f.expectPriorSha]));
|
|
191
256
|
let lastErr: unknown;
|
|
192
|
-
let priorPathSha: string | null | undefined = opts?.expectPriorPathSha;
|
|
193
257
|
for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
|
|
194
|
-
// 2. Read
|
|
195
|
-
// entries. `tip` is the commit sha `entries` was read from, and must stay
|
|
258
|
+
// 2. Read the tip once and every listing from that sha. `tip` must stay
|
|
196
259
|
// the commit parent and CAS oldValue below. Re-resolving the branch name
|
|
197
|
-
// there instead can observe a newer tip than
|
|
260
|
+
// there instead can observe a newer tip than the listings reflect, which
|
|
198
261
|
// makes the CAS succeed against a tree built from a stale read.
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
262
|
+
const tip = await getStateBranchTip(cwd);
|
|
263
|
+
const trees = new Map<string, Map<string, TreeLine>>();
|
|
264
|
+
const listing = async (dir: string): Promise<Map<string, TreeLine>> => {
|
|
265
|
+
let entries = trees.get(dir);
|
|
266
|
+
if (!entries) {
|
|
267
|
+
entries = tip ? await listTreeAt(tip, dir, cwd) : new Map();
|
|
268
|
+
trees.set(dir, entries);
|
|
269
|
+
}
|
|
270
|
+
return entries;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
for (const b of blobs) {
|
|
274
|
+
const { dir, name } = splitPath(b.path);
|
|
275
|
+
const current = (await listing(dir)).get(name);
|
|
276
|
+
const currentSha = current?.type === "blob" ? current.sha : null;
|
|
277
|
+
const expected = prior.get(b.path);
|
|
278
|
+
if (expected !== undefined && currentSha !== expected) {
|
|
279
|
+
// Our own path moved since the caller read it or our previous
|
|
280
|
+
// attempt started — a genuine content-level race on the exact file
|
|
281
|
+
// we're writing, not just a CAS conflict from a sibling path. Not
|
|
282
|
+
// safe to paper over here.
|
|
283
|
+
throw lastErr ?? new RefCASConflictError(`refs/heads/${STATE_BRANCH}`, expected, "target path changed concurrently");
|
|
284
|
+
}
|
|
285
|
+
prior.set(b.path, currentSha);
|
|
207
286
|
}
|
|
208
|
-
priorPathSha = currentPathSha;
|
|
209
|
-
|
|
210
|
-
// 3. Build new tree entries
|
|
211
|
-
const entries = mergeTreeEntry(existingTree, path, blobSha);
|
|
212
|
-
|
|
213
|
-
// mktree needs a nested tree structure. Build env subtree first, then root tree.
|
|
214
|
-
// Build env subtree
|
|
215
|
-
const envEntries = entries
|
|
216
|
-
.filter((e) => e.env === environment)
|
|
217
|
-
.map((e) => `${e.mode} ${e.type} ${e.sha}\t${e.name}`)
|
|
218
|
-
.join("\n");
|
|
219
287
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// Build root tree: collect env subtrees
|
|
227
|
-
const rootEntries: string[] = [];
|
|
228
|
-
const envsSeen = new Set<string>();
|
|
229
|
-
for (const e of entries) {
|
|
230
|
-
if (!envsSeen.has(e.env)) {
|
|
231
|
-
envsSeen.add(e.env);
|
|
232
|
-
if (e.env === environment) {
|
|
233
|
-
rootEntries.push(`040000 tree ${envTreeSha}\t${environment}`);
|
|
234
|
-
} else {
|
|
235
|
-
rootEntries.push(`040000 tree ${e.envTreeSha!}\t${e.env}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
288
|
+
// 3. Replace the entries, listing every directory up to the root so the
|
|
289
|
+
// rebuild below has each parent in hand.
|
|
290
|
+
for (const b of blobs) {
|
|
291
|
+
const { dir, name } = splitPath(b.path);
|
|
292
|
+
(await listing(dir)).set(name, { mode: "100644", type: "blob", sha: b.sha });
|
|
293
|
+
for (let d = dir; d !== ""; d = parentDir(d)) await listing(parentDir(d));
|
|
238
294
|
}
|
|
239
295
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
296
|
+
// 4. Rebuild the touched trees, deepest first, then the root.
|
|
297
|
+
const touched = [...trees.keys()].sort((a, b) => depth(b) - depth(a) || (a < b ? -1 : 1));
|
|
298
|
+
let rootTreeSha = "";
|
|
299
|
+
for (const dir of touched) {
|
|
300
|
+
const lines = [...trees.get(dir)!].map(([name, e]) => `${e.mode} ${e.type} ${e.sha}\t${name}`).join("\n");
|
|
301
|
+
const result = await rt.spawn(["git", "mktree"], { cwd, stdin: `${lines}\n` });
|
|
302
|
+
if (result.exitCode !== 0) {
|
|
303
|
+
throw ledgerWriteError(`git mktree (${dir === "" ? "root" : dir})`, path, result.stderr);
|
|
304
|
+
}
|
|
305
|
+
const sha = result.stdout.trim();
|
|
306
|
+
if (dir === "") rootTreeSha = sha;
|
|
307
|
+
else trees.get(parentDir(dir))!.set(baseName(dir), { mode: "040000", type: "tree", sha });
|
|
246
308
|
}
|
|
247
|
-
const rootTreeSha = rootTreeResult.stdout.trim();
|
|
248
309
|
|
|
249
|
-
//
|
|
250
|
-
// read from (see
|
|
251
|
-
// the branch name.
|
|
310
|
+
// 5. Create the commit — parented on `tip`, the exact sha every listing
|
|
311
|
+
// was read from (see step 2), not a fresh re-resolution of the branch.
|
|
252
312
|
const parentRef = tip;
|
|
253
313
|
const parentArgs = parentRef ? ["-p", parentRef] : [];
|
|
254
314
|
const commitResult = await rt.spawn(
|
|
@@ -260,7 +320,7 @@ export async function writeBlobToPath(
|
|
|
260
320
|
}
|
|
261
321
|
const commitSha = commitResult.stdout.trim();
|
|
262
322
|
|
|
263
|
-
//
|
|
323
|
+
// 6. Update ref — CAS-guarded against `parentRef` (#1485): closes the
|
|
264
324
|
// local race two concurrent writers used to hit silently (whoever called
|
|
265
325
|
// update-ref last simply overwrote the other's tree, no error). A
|
|
266
326
|
// conflict here throws RefCASConflictError instead of clobbering.
|
|
@@ -290,8 +350,9 @@ export async function readBlobFromPath(
|
|
|
290
350
|
opts?: { cwd?: string },
|
|
291
351
|
): Promise<string | null> {
|
|
292
352
|
const rt = getRuntime();
|
|
353
|
+
const dir = await ledgerDir(environment, opts);
|
|
293
354
|
const result = await rt.spawn(
|
|
294
|
-
["git", "show", `${STATE_BRANCH}:${
|
|
355
|
+
["git", "show", `${STATE_BRANCH}:${dir}/${filename}`],
|
|
295
356
|
{ cwd: opts?.cwd },
|
|
296
357
|
);
|
|
297
358
|
if (result.exitCode !== 0) return null;
|
|
@@ -311,8 +372,9 @@ export async function readPathSha(
|
|
|
311
372
|
opts?: { cwd?: string },
|
|
312
373
|
): Promise<string | null> {
|
|
313
374
|
const rt = getRuntime();
|
|
375
|
+
const dir = await ledgerDir(environment, opts);
|
|
314
376
|
const result = await rt.spawn(
|
|
315
|
-
["git", "rev-parse", "--verify", `${STATE_BRANCH}:${
|
|
377
|
+
["git", "rev-parse", "--verify", `${STATE_BRANCH}:${dir}/${filename}`],
|
|
316
378
|
{ cwd: opts?.cwd },
|
|
317
379
|
);
|
|
318
380
|
if (result.exitCode !== 0) return null;
|
|
@@ -370,8 +432,9 @@ export async function readSnapshotAt(
|
|
|
370
432
|
opts?: { cwd?: string },
|
|
371
433
|
): Promise<string | null> {
|
|
372
434
|
const rt = getRuntime();
|
|
435
|
+
const dir = await ledgerDir(environment, opts);
|
|
373
436
|
const result = await rt.spawn(
|
|
374
|
-
["git", "show", `${ref}:${
|
|
437
|
+
["git", "show", `${ref}:${dir}/${lexicon}.json`],
|
|
375
438
|
{ cwd: opts?.cwd },
|
|
376
439
|
);
|
|
377
440
|
if (result.exitCode !== 0) return null;
|
|
@@ -460,7 +523,16 @@ export async function listLedgerEnvironments(opts?: { cwd?: string }): Promise<s
|
|
|
460
523
|
const tip = await getStateBranchTip(opts?.cwd);
|
|
461
524
|
if (!tip) return [];
|
|
462
525
|
const rt = getRuntime();
|
|
463
|
-
|
|
526
|
+
// A member lists the directories under its own `_members/<member>/`; the
|
|
527
|
+
// flat layout lists the root, where `_members` holds no releases.jsonl and
|
|
528
|
+
// so never reads as an environment (#2538).
|
|
529
|
+
const base = await ledgerDir("", opts);
|
|
530
|
+
// `--full-tree`: from a subdirectory, a bare `ls-tree` lists only that
|
|
531
|
+
// subdirectory's part of the tree (#2550).
|
|
532
|
+
const rootResult = await rt.spawn(
|
|
533
|
+
["git", "ls-tree", "--full-tree", base === "" ? STATE_BRANCH : `${STATE_BRANCH}:${base}`],
|
|
534
|
+
{ cwd: opts?.cwd },
|
|
535
|
+
);
|
|
464
536
|
if (rootResult.exitCode !== 0) return [];
|
|
465
537
|
|
|
466
538
|
const envs: string[] = [];
|
|
@@ -488,8 +560,9 @@ export async function listFilesInDir(dir: string, opts?: { cwd?: string }): Prom
|
|
|
488
560
|
const tip = await getStateBranchTip(opts?.cwd);
|
|
489
561
|
if (!tip) return [];
|
|
490
562
|
const rt = getRuntime();
|
|
563
|
+
const path = await ledgerDir(dir, opts);
|
|
491
564
|
const lsResult = await rt.spawn(
|
|
492
|
-
["git", "ls-tree", "--name-only", `${STATE_BRANCH}:${
|
|
565
|
+
["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${path}/`],
|
|
493
566
|
{ cwd: opts?.cwd },
|
|
494
567
|
);
|
|
495
568
|
if (lsResult.exitCode !== 0) return [];
|
|
@@ -507,8 +580,9 @@ export async function readEnvironmentSnapshots(
|
|
|
507
580
|
const snapshots = new Map<string, string>();
|
|
508
581
|
|
|
509
582
|
// List files in the environment directory
|
|
583
|
+
const dir = await ledgerDir(environment, opts);
|
|
510
584
|
const lsResult = await rt.spawn(
|
|
511
|
-
["git", "ls-tree", "--name-only", `${STATE_BRANCH}:${
|
|
585
|
+
["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${dir}/`],
|
|
512
586
|
{ cwd: opts?.cwd },
|
|
513
587
|
);
|
|
514
588
|
if (lsResult.exitCode !== 0) return snapshots;
|
|
@@ -532,8 +606,12 @@ export async function listSnapshots(
|
|
|
532
606
|
opts?: { cwd?: string; environment?: string },
|
|
533
607
|
): Promise<Array<{ commit: string; date: string; message: string }>> {
|
|
534
608
|
const rt = getRuntime();
|
|
609
|
+
// A member sees the commits that touched its own ledger (#2538); the flat
|
|
610
|
+
// layout keeps today's whole-branch history.
|
|
611
|
+
const base = await ledgerDir("", opts);
|
|
535
612
|
const result = await rt.spawn(
|
|
536
|
-
|
|
613
|
+
// `:(top)`: a pathspec is otherwise relative to the directory git runs in.
|
|
614
|
+
["git", "log", "--format=%H %aI %s", STATE_BRANCH, ...(base === "" ? [] : ["--", `:(top)${base}`])],
|
|
537
615
|
{ cwd: opts?.cwd },
|
|
538
616
|
);
|
|
539
617
|
if (result.exitCode !== 0) return [];
|
|
@@ -1106,13 +1184,11 @@ export async function getHeadCommit(opts?: { cwd?: string }): Promise<string> {
|
|
|
1106
1184
|
|
|
1107
1185
|
// ── Internal helpers ────────────────────────────────────────────
|
|
1108
1186
|
|
|
1109
|
-
|
|
1187
|
+
/** One entry of a tree listing, keyed by its name in the listing's map. */
|
|
1188
|
+
interface TreeLine {
|
|
1110
1189
|
mode: string;
|
|
1111
1190
|
type: string;
|
|
1112
1191
|
sha: string;
|
|
1113
|
-
name: string;
|
|
1114
|
-
env: string;
|
|
1115
|
-
envTreeSha?: string;
|
|
1116
1192
|
}
|
|
1117
1193
|
|
|
1118
1194
|
async function getStateBranchTip(cwd?: string): Promise<string | null> {
|
|
@@ -1126,76 +1202,45 @@ async function getStateBranchTip(cwd?: string): Promise<string | null> {
|
|
|
1126
1202
|
}
|
|
1127
1203
|
|
|
1128
1204
|
/**
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
* sha, never to `STATE_BRANCH`:
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1205
|
+
* The entries of directory `dir` (branch-relative, `""` for the root) in
|
|
1206
|
+
* commit `tip`, or an empty map when it does not exist. Always pinned to the
|
|
1207
|
+
* resolved `tip` sha, never to `STATE_BRANCH`: a write reads several
|
|
1208
|
+
* directories, and a branch name re-resolves on each read, so a concurrent
|
|
1209
|
+
* commit would splice entries from two commits into one tree undetectably.
|
|
1210
|
+
*
|
|
1211
|
+
* `--full-tree` (#2550): run from a project in a subdirectory of the
|
|
1212
|
+
* repository, a bare `ls-tree` lists only the part of the tree under that
|
|
1213
|
+
* subdirectory, which on the orphan branch is nothing. The write then
|
|
1214
|
+
* rebuilt the branch from an empty tree, and an append's CAS refused it as a
|
|
1215
|
+
* concurrent change.
|
|
1135
1216
|
*/
|
|
1136
|
-
async function
|
|
1217
|
+
async function listTreeAt(tip: string, dir: string, cwd?: string): Promise<Map<string, TreeLine>> {
|
|
1137
1218
|
const rt = getRuntime();
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const rootResult = await rt.spawn(
|
|
1143
|
-
["git", "ls-tree", tip],
|
|
1144
|
-
{ cwd },
|
|
1145
|
-
);
|
|
1146
|
-
if (rootResult.exitCode !== 0) return { tip, entries: [] };
|
|
1147
|
-
|
|
1148
|
-
const entries: TreeEntry[] = [];
|
|
1149
|
-
const lines = rootResult.stdout.trim().split("\n").filter(Boolean);
|
|
1150
|
-
|
|
1151
|
-
for (const line of lines) {
|
|
1219
|
+
const entries = new Map<string, TreeLine>();
|
|
1220
|
+
const result = await rt.spawn(["git", "ls-tree", "--full-tree", dir === "" ? tip : `${tip}:${dir}/`], { cwd });
|
|
1221
|
+
if (result.exitCode !== 0) return entries;
|
|
1222
|
+
for (const line of result.stdout.split("\n")) {
|
|
1152
1223
|
// Format: mode type sha\tname
|
|
1153
1224
|
const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
|
|
1154
|
-
if (
|
|
1155
|
-
const [, mode, type, sha, name] = match;
|
|
1156
|
-
|
|
1157
|
-
if (type === "tree") {
|
|
1158
|
-
// This is an env directory — list its contents, still pinned to `tip`.
|
|
1159
|
-
const envResult = await rt.spawn(
|
|
1160
|
-
["git", "ls-tree", `${tip}:${name}/`],
|
|
1161
|
-
{ cwd },
|
|
1162
|
-
);
|
|
1163
|
-
if (envResult.exitCode !== 0) continue;
|
|
1164
|
-
|
|
1165
|
-
const envLines = envResult.stdout.trim().split("\n").filter(Boolean);
|
|
1166
|
-
for (const envLine of envLines) {
|
|
1167
|
-
const envMatch = envLine.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
|
|
1168
|
-
if (!envMatch) continue;
|
|
1169
|
-
entries.push({
|
|
1170
|
-
mode: envMatch[1],
|
|
1171
|
-
type: envMatch[2],
|
|
1172
|
-
sha: envMatch[3],
|
|
1173
|
-
name: envMatch[4],
|
|
1174
|
-
env: name,
|
|
1175
|
-
envTreeSha: sha,
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1225
|
+
if (match) entries.set(match[4], { mode: match[1], type: match[2], sha: match[3] });
|
|
1179
1226
|
}
|
|
1227
|
+
return entries;
|
|
1228
|
+
}
|
|
1180
1229
|
|
|
1181
|
-
|
|
1230
|
+
function splitPath(path: string): { dir: string; name: string } {
|
|
1231
|
+
const slash = path.lastIndexOf("/");
|
|
1232
|
+
return { dir: path.slice(0, slash), name: path.slice(slash + 1) };
|
|
1182
1233
|
}
|
|
1183
1234
|
|
|
1184
|
-
function
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
type: "blob",
|
|
1196
|
-
sha: blobSha,
|
|
1197
|
-
name: filename,
|
|
1198
|
-
env,
|
|
1199
|
-
});
|
|
1200
|
-
return entries;
|
|
1235
|
+
function parentDir(dir: string): string {
|
|
1236
|
+
const slash = dir.lastIndexOf("/");
|
|
1237
|
+
return slash < 0 ? "" : dir.slice(0, slash);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function baseName(dir: string): string {
|
|
1241
|
+
return dir.slice(dir.lastIndexOf("/") + 1);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function depth(dir: string): number {
|
|
1245
|
+
return dir === "" ? 0 : dir.split("/").length;
|
|
1201
1246
|
}
|
package/src/lifecycle/lease.ts
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import { hostname } from "node:os";
|
|
35
35
|
import { randomUUID } from "node:crypto";
|
|
36
36
|
import { readRefSha, updateRefCAS, deleteRefCAS, writeBlob, readBlobBySha, pushRef, fetchRefInto, RefCASConflictError } from "./git";
|
|
37
|
+
import { resolveMemberLedger } from "./member-ledger";
|
|
37
38
|
|
|
38
39
|
export const LEASE_REF_PREFIX = "refs/chant/lease/";
|
|
39
40
|
|
|
@@ -59,12 +60,25 @@ export const LEASE_REMOTE_TRACKING_PREFIX = "refs/chant/lease-remote/";
|
|
|
59
60
|
*/
|
|
60
61
|
export const DEFAULT_LEASE_TTL_MS = 5 * 60_000;
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The lease ref for `opName`. `memberPrefix` is the project's ledger prefix
|
|
65
|
+
* from ./member-ledger.ts (#2538): empty at level 0 and for the root member
|
|
66
|
+
* `.`, which keeps `refs/chant/lease/<op>`, and `_members/<member>/` for any
|
|
67
|
+
* other workspace member, which gives `refs/chant/lease/_members/<member>/<op>`.
|
|
68
|
+
* Two members may then run Ops of the same name without sharing a lease.
|
|
69
|
+
*/
|
|
70
|
+
export function leaseRef(opName: string, memberPrefix = ""): string {
|
|
71
|
+
return `${LEASE_REF_PREFIX}${memberPrefix}${opName}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function leaseRemoteTrackingRef(opName: string, memberPrefix = ""): string {
|
|
75
|
+
return `${LEASE_REMOTE_TRACKING_PREFIX}${memberPrefix}${opName}`;
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
/** The lease refs of `opName` for the project at `opts.cwd` (or the process's directory). */
|
|
79
|
+
async function projectLeaseRefs(opName: string, opts?: { cwd?: string }): Promise<{ ref: string; trackingRef: string }> {
|
|
80
|
+
const { prefix } = await resolveMemberLedger(opts?.cwd ?? process.cwd());
|
|
81
|
+
return { ref: leaseRef(opName, prefix), trackingRef: leaseRemoteTrackingRef(opName, prefix) };
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
/**
|
|
@@ -147,8 +161,7 @@ export interface ReadLeaseResult {
|
|
|
147
161
|
* call against it, regardless of what the comparison decided about `record`.
|
|
148
162
|
*/
|
|
149
163
|
export async function readLease(opName: string, opts?: { cwd?: string }): Promise<ReadLeaseResult> {
|
|
150
|
-
const ref =
|
|
151
|
-
const trackingRef = leaseRemoteTrackingRef(opName);
|
|
164
|
+
const { ref, trackingRef } = await projectLeaseRefs(opName, opts);
|
|
152
165
|
await fetchRefInto(ref, trackingRef, opts).catch(() => undefined);
|
|
153
166
|
|
|
154
167
|
const sha = await readRefSha(ref, opts);
|
|
@@ -212,9 +225,10 @@ export async function acquireLease(
|
|
|
212
225
|
expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
|
|
213
226
|
};
|
|
214
227
|
|
|
228
|
+
const { ref } = await projectLeaseRefs(opName, opts);
|
|
215
229
|
const blobSha = await writeBlob(JSON.stringify(record), opts);
|
|
216
230
|
try {
|
|
217
|
-
await updateRefCAS(
|
|
231
|
+
await updateRefCAS(ref, blobSha, sha, opts);
|
|
218
232
|
} catch (err) {
|
|
219
233
|
if (err instanceof RefCASConflictError) {
|
|
220
234
|
const retry = await readLease(opName, opts);
|
|
@@ -225,7 +239,7 @@ export async function acquireLease(
|
|
|
225
239
|
// it into `heldBy`, per this function's doc.
|
|
226
240
|
throw err;
|
|
227
241
|
}
|
|
228
|
-
await pushRef(
|
|
242
|
+
await pushRef(ref, opts).catch(() => undefined);
|
|
229
243
|
return { acquired: true, lease: record };
|
|
230
244
|
}
|
|
231
245
|
|
|
@@ -244,12 +258,13 @@ export async function releaseLease(
|
|
|
244
258
|
): Promise<boolean> {
|
|
245
259
|
const { sha, record } = await readLease(opName, opts);
|
|
246
260
|
if (!sha || !record || record.holder !== holder || record.token !== token) return false;
|
|
261
|
+
const { ref } = await projectLeaseRefs(opName, opts);
|
|
247
262
|
try {
|
|
248
|
-
await deleteRefCAS(
|
|
263
|
+
await deleteRefCAS(ref, sha, opts);
|
|
249
264
|
} catch {
|
|
250
265
|
return false;
|
|
251
266
|
}
|
|
252
|
-
await pushRef(
|
|
267
|
+
await pushRef(ref, opts).catch(() => undefined);
|
|
253
268
|
return true;
|
|
254
269
|
}
|
|
255
270
|
|