@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
package/src/lifecycle/status.ts
CHANGED
|
@@ -15,10 +15,15 @@
|
|
|
15
15
|
* for it. Someone (or some pipeline) deployed outside the recorded path.
|
|
16
16
|
* - **stale** — a release record exists but nothing live corresponds to it
|
|
17
17
|
* anymore (the component disappeared from observation).
|
|
18
|
-
* - **drifted** —
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* - **drifted** — a release record exists and the component is observed
|
|
19
|
+
* live, but a positive live observation contradicts the record (#2513):
|
|
20
|
+
* the live identity a lexicon reports (`attributes.digest` /
|
|
21
|
+
* `attributes.gitSha`) differs from the recorded release's, the ownership
|
|
22
|
+
* marker went from chant's to someone else's, or the live configuration
|
|
23
|
+
* changed against a lifecycle snapshot taken at or after the release (the
|
|
24
|
+
* `update` verdict `lifecycle plan` gives). Only things chant saw count:
|
|
25
|
+
* a resource it could not read, or one that reports no identity, never
|
|
26
|
+
* makes a component drifted.
|
|
22
27
|
* - **reconciled** — a release record exists and live evidence (via
|
|
23
28
|
* ownership) confirms the component is present and owned by chant.
|
|
24
29
|
* - **unknown** — live evidence was requested and could not be read (#1089),
|
|
@@ -37,7 +42,9 @@
|
|
|
37
42
|
*/
|
|
38
43
|
|
|
39
44
|
import type { ChangeSet, ChangeAction } from "./change-set";
|
|
45
|
+
import type { ResourceMetadata } from "../lexicon";
|
|
40
46
|
import { latestPerComponent, type ReleaseRecord } from "./release-ledger";
|
|
47
|
+
import { isLegacyContentDigest } from "./legacy-digest";
|
|
41
48
|
import type { BuildLedgerEntry, ComponentBomSummary } from "./build-ledger";
|
|
42
49
|
import { unobservedReasonText, type UnobservedReason } from "../observation";
|
|
43
50
|
|
|
@@ -166,6 +173,99 @@ export interface LiveComponentEvidence {
|
|
|
166
173
|
* configured.
|
|
167
174
|
*/
|
|
168
175
|
rollup?: ComponentResourceRollup;
|
|
176
|
+
/**
|
|
177
|
+
* What the component's live resources say they are running (#2513), one
|
|
178
|
+
* entry per present resource that reported an identity attribute
|
|
179
|
+
* ({@link LIVE_IDENTITY_ATTRIBUTES}). A resource that reports none has no
|
|
180
|
+
* entry: no identity is no evidence, never a mismatch.
|
|
181
|
+
*/
|
|
182
|
+
identities?: LiveIdentity[];
|
|
183
|
+
/**
|
|
184
|
+
* Positive live observations that changed against the last lifecycle
|
|
185
|
+
* snapshot (#2513), each stamped with that snapshot's timestamp so the
|
|
186
|
+
* status join can drop a change the release itself made (a snapshot taken
|
|
187
|
+
* before the release). When present, `reconcileStatus` reads these instead
|
|
188
|
+
* of the bare `action === "update"`.
|
|
189
|
+
*/
|
|
190
|
+
driftSignals?: LiveDriftSignal[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The `ResourceMetadata.attributes` keys a lexicon sets to report which build
|
|
195
|
+
* a live resource is running (#2513), and the release-record field each one is
|
|
196
|
+
* compared with. Opt-in: a lexicon that sets neither is reconciled on presence,
|
|
197
|
+
* ownership and snapshot drift alone, exactly as before.
|
|
198
|
+
*
|
|
199
|
+
* - `digest` — the artifact digest serving now (`sha256:...`). Matches the
|
|
200
|
+
* record's `digest`, or its `inputDigest` when the record has one.
|
|
201
|
+
* - `gitSha` — the commit the live build came from. Matches the record's
|
|
202
|
+
* `gitSha`; an abbreviated sha (7+ characters) matches by prefix.
|
|
203
|
+
*/
|
|
204
|
+
export const LIVE_IDENTITY_ATTRIBUTES = { digest: "digest", gitSha: "gitSha" } as const;
|
|
205
|
+
|
|
206
|
+
/** One live resource's reported identity (#2513). */
|
|
207
|
+
export interface LiveIdentity {
|
|
208
|
+
/** The live entity/resource name that reported it. */
|
|
209
|
+
entity: string;
|
|
210
|
+
digest?: string;
|
|
211
|
+
gitSha?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** One live change against the last snapshot (#2513). */
|
|
215
|
+
export interface LiveDriftSignal {
|
|
216
|
+
entity: string;
|
|
217
|
+
/**
|
|
218
|
+
* `attributes` — the change set's `update` (status, physicalId, lastUpdated
|
|
219
|
+
* or an attribute changed). `ownership` — the snapshot read chant's marker
|
|
220
|
+
* and the live read now reads none (`owned` -> `foreign`), or the marker now
|
|
221
|
+
* names a different stack/env. `unknown` on either side is never a signal.
|
|
222
|
+
*/
|
|
223
|
+
kind: "attributes" | "ownership";
|
|
224
|
+
/** The snapshot's timestamp. Absent when the caller did not say. */
|
|
225
|
+
since?: string;
|
|
226
|
+
/** Human-readable backing (the changed paths, the marker transition). */
|
|
227
|
+
detail?: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The per-entity observation `liveEvidenceFromChangeSet` reads identity and
|
|
232
|
+
* marker transitions from (#2513). `now` is the live read; `then`/`thenAt`
|
|
233
|
+
* are the last lifecycle snapshot's record for the same entity, when one
|
|
234
|
+
* exists.
|
|
235
|
+
*/
|
|
236
|
+
export interface EntityLiveRead {
|
|
237
|
+
now?: ResourceMetadata;
|
|
238
|
+
then?: ResourceMetadata;
|
|
239
|
+
thenAt?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function identityOf(entity: string, meta: ResourceMetadata | undefined): LiveIdentity | undefined {
|
|
243
|
+
const attrs = meta?.attributes;
|
|
244
|
+
if (!attrs) return undefined;
|
|
245
|
+
const digest = attrs[LIVE_IDENTITY_ATTRIBUTES.digest];
|
|
246
|
+
const gitSha = attrs[LIVE_IDENTITY_ATTRIBUTES.gitSha];
|
|
247
|
+
const d = typeof digest === "string" && digest.length > 0 ? digest : undefined;
|
|
248
|
+
const g = typeof gitSha === "string" && gitSha.length > 0 ? gitSha : undefined;
|
|
249
|
+
if (!d && !g) return undefined;
|
|
250
|
+
return { entity, ...(d ? { digest: d } : {}), ...(g ? { gitSha: g } : {}) };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* An ownership transition between the snapshot and now that contradicts a
|
|
255
|
+
* chant release. `unknown` on either side answers nothing and is skipped, the
|
|
256
|
+
* same way the change set never escalates `unknown` (#1089, #1168).
|
|
257
|
+
*/
|
|
258
|
+
function ownershipChange(then: ResourceMetadata | undefined, now: ResourceMetadata | undefined): string | undefined {
|
|
259
|
+
if (!then || !now) return undefined;
|
|
260
|
+
if (then.ownership === "owned" && now.ownership === "foreign") {
|
|
261
|
+
return "ownership marker was chant's at the last snapshot and is gone now";
|
|
262
|
+
}
|
|
263
|
+
const a = then.marker;
|
|
264
|
+
const b = now.marker;
|
|
265
|
+
if (a && b && (a.stack !== b.stack || (a.env !== undefined && b.env !== undefined && a.env !== b.env))) {
|
|
266
|
+
return `ownership marker changed from ${a.stack}${a.env ? `/${a.env}` : ""} to ${b.stack}${b.env ? `/${b.env}` : ""}`;
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
169
269
|
}
|
|
170
270
|
|
|
171
271
|
/**
|
|
@@ -211,6 +311,11 @@ export function mergeLiveEvidence(
|
|
|
211
311
|
// Base first: the counts come from the change set, and a stack
|
|
212
312
|
// observation has none to offer.
|
|
213
313
|
...(b?.rollup ?? sup.rollup ? { rollup: b?.rollup ?? sup.rollup } : {}),
|
|
314
|
+
// Identity and snapshot drift come from the per-resource reads; a stack
|
|
315
|
+
// observation carries neither, so dropping them here would make
|
|
316
|
+
// `drifted` unreachable again on every lexicon with a stack observer.
|
|
317
|
+
...(b?.identities ? { identities: b.identities } : {}),
|
|
318
|
+
...(b?.driftSignals ? { driftSignals: b.driftSignals } : {}),
|
|
214
319
|
});
|
|
215
320
|
}
|
|
216
321
|
return merged;
|
|
@@ -299,7 +404,18 @@ function mergeEvidence(entries: LiveComponentEvidence[]): LiveComponentEvidence
|
|
|
299
404
|
// actually seen live, which already answers "is this deployed".
|
|
300
405
|
const unobserved = live ? undefined : entries.find((e) => e.unobserved)?.unobserved;
|
|
301
406
|
|
|
302
|
-
|
|
407
|
+
const identities = entries.flatMap((e) => e.identities ?? []);
|
|
408
|
+
const driftSignals = entries.flatMap((e) => e.driftSignals ?? []);
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
live,
|
|
412
|
+
ownership,
|
|
413
|
+
action,
|
|
414
|
+
...(unobserved ? { unobserved } : {}),
|
|
415
|
+
rollup: rollUp(entries),
|
|
416
|
+
...(identities.length ? { identities } : {}),
|
|
417
|
+
...(driftSignals.length ? { driftSignals } : {}),
|
|
418
|
+
};
|
|
303
419
|
}
|
|
304
420
|
|
|
305
421
|
/**
|
|
@@ -317,9 +433,41 @@ function mergeEvidence(entries: LiveComponentEvidence[]): LiveComponentEvidence
|
|
|
317
433
|
export function liveEvidenceFromChangeSet(
|
|
318
434
|
cs: ChangeSet,
|
|
319
435
|
nameMapping?: LiveNameMapping,
|
|
436
|
+
opts?: {
|
|
437
|
+
/**
|
|
438
|
+
* Per-entity live read and snapshot record (#2513). With it, the evidence
|
|
439
|
+
* carries the identity each live resource reports and the drift signals
|
|
440
|
+
* (stamped with the snapshot's time) `reconcileStatus` reads for
|
|
441
|
+
* `drifted`. Without it, the evidence is what it was before: the change
|
|
442
|
+
* set's `action` alone.
|
|
443
|
+
*/
|
|
444
|
+
observations?: Map<string, EntityLiveRead>;
|
|
445
|
+
},
|
|
320
446
|
): Map<string, LiveComponentEvidence> {
|
|
321
447
|
const evidenceByName = new Map<string, LiveComponentEvidence>();
|
|
322
448
|
for (const entry of cs.entries) {
|
|
449
|
+
const obs = opts?.observations?.get(entry.name);
|
|
450
|
+
// Identity and marker transitions only from a resource that was actually
|
|
451
|
+
// read present. An unobserved or absent entity contributes nothing.
|
|
452
|
+
const identity = entry.evidence.live ? identityOf(entry.name, obs?.now) : undefined;
|
|
453
|
+
const driftSignals: LiveDriftSignal[] = [];
|
|
454
|
+
if (obs && entry.action === "update") {
|
|
455
|
+
driftSignals.push({
|
|
456
|
+
entity: entry.name,
|
|
457
|
+
kind: "attributes",
|
|
458
|
+
...(obs.thenAt ? { since: obs.thenAt } : {}),
|
|
459
|
+
...(entry.deltas?.length ? { detail: entry.deltas.map((d) => d.path).join(", ") } : {}),
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const ownershipDetail = obs && entry.evidence.live ? ownershipChange(obs.then, obs.now) : undefined;
|
|
463
|
+
if (ownershipDetail) {
|
|
464
|
+
driftSignals.push({
|
|
465
|
+
entity: entry.name,
|
|
466
|
+
kind: "ownership",
|
|
467
|
+
...(obs?.thenAt ? { since: obs.thenAt } : {}),
|
|
468
|
+
detail: ownershipDetail,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
323
471
|
evidenceByName.set(entry.name, {
|
|
324
472
|
rollup: rollUp([
|
|
325
473
|
{
|
|
@@ -342,6 +490,8 @@ export function liveEvidenceFromChangeSet(
|
|
|
342
490
|
},
|
|
343
491
|
}
|
|
344
492
|
: {}),
|
|
493
|
+
...(identity ? { identities: [identity] } : {}),
|
|
494
|
+
...(driftSignals.length ? { driftSignals } : {}),
|
|
345
495
|
});
|
|
346
496
|
}
|
|
347
497
|
|
|
@@ -404,6 +554,7 @@ export function reconcileStatus(
|
|
|
404
554
|
|
|
405
555
|
let reconciliation: ComponentStatusRow["reconciliation"];
|
|
406
556
|
let detail: string;
|
|
557
|
+
let contradiction: string | undefined;
|
|
407
558
|
|
|
408
559
|
if (!liveEvidence) {
|
|
409
560
|
// No live evidence requested at all (digest-only / offline mode) — the
|
|
@@ -437,9 +588,9 @@ export function reconcileStatus(
|
|
|
437
588
|
detail = evidence?.partial
|
|
438
589
|
? `recorded ${recorded.timestamp} (digest ${recorded.digest}), but only ${evidence.partial.present} of ${evidence.partial.total} deploy units observed live now (missing: ${evidence.partial.missing.join(", ")})`
|
|
439
590
|
: `recorded ${recorded.timestamp} (digest ${recorded.digest}), but nothing observed live now`;
|
|
440
|
-
} else if (recorded &&
|
|
591
|
+
} else if (recorded && (contradiction = contradictsRecord(recorded, evidence!))) {
|
|
441
592
|
reconciliation = "drifted";
|
|
442
|
-
detail = `recorded ${recorded.timestamp} (digest ${recorded.digest}), but
|
|
593
|
+
detail = `recorded ${recorded.timestamp} (digest ${recorded.digest}), but ${contradiction}`;
|
|
443
594
|
} else {
|
|
444
595
|
reconciliation = "reconciled";
|
|
445
596
|
detail = `recorded ${recorded!.timestamp} (digest ${recorded!.digest}), live and consistent`;
|
|
@@ -467,6 +618,66 @@ export function reconcileStatus(
|
|
|
467
618
|
return rows;
|
|
468
619
|
}
|
|
469
620
|
|
|
621
|
+
/** `sha256:abc` and `abc` name the same digest; anything else compares exactly. */
|
|
622
|
+
function sameDigest(a: string, b: string): boolean {
|
|
623
|
+
const bare = (d: string) => d.replace(/^sha256:/, "");
|
|
624
|
+
return a === b || bare(a) === bare(b);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Full shas compare exactly; an abbreviated one (7+ chars) matches by prefix. */
|
|
628
|
+
function sameGitSha(a: string, b: string): boolean {
|
|
629
|
+
if (a === b) return true;
|
|
630
|
+
const [short, long] = a.length <= b.length ? [a, b] : [b, a];
|
|
631
|
+
return short.length >= 7 && long.startsWith(short);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function notBefore(since: string, recordedAt: string): boolean {
|
|
635
|
+
const s = Date.parse(since);
|
|
636
|
+
const r = Date.parse(recordedAt);
|
|
637
|
+
// An unparseable timestamp cannot place the change after the release, so
|
|
638
|
+
// it is not attributed to drift since it.
|
|
639
|
+
return !Number.isNaN(s) && !Number.isNaN(r) && s >= r;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Does live evidence for a present, recorded component contradict its latest
|
|
644
|
+
* release record (#2513)? Returns why, or `undefined` when nothing seen says
|
|
645
|
+
* so. Every rule reads a positive observation; a missing identity, an
|
|
646
|
+
* `unknown` ownership or a resource nobody read is not a contradiction.
|
|
647
|
+
*/
|
|
648
|
+
function contradictsRecord(recorded: ReleaseRecord, evidence: LiveComponentEvidence): string | undefined {
|
|
649
|
+
for (const id of evidence.identities ?? []) {
|
|
650
|
+
if (id.digest !== undefined) {
|
|
651
|
+
const matches =
|
|
652
|
+
sameDigest(id.digest, recorded.digest) ||
|
|
653
|
+
(recorded.inputDigest !== undefined && sameDigest(id.digest, recorded.inputDigest));
|
|
654
|
+
if (!matches) return `live ${id.entity} reports digest ${id.digest}`;
|
|
655
|
+
}
|
|
656
|
+
if (id.gitSha !== undefined && !sameGitSha(id.gitSha, recorded.gitSha)) {
|
|
657
|
+
return `live ${id.entity} reports git sha ${id.gitSha}, recorded ${recorded.gitSha}`;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (evidence.driftSignals) {
|
|
662
|
+
// A signal against a snapshot taken before the release may be the release
|
|
663
|
+
// itself, so only a change seen since the release counts. A signal with no
|
|
664
|
+
// `since` was produced by a caller that did not say, and counts.
|
|
665
|
+
const since = evidence.driftSignals.filter((d) => d.since === undefined || notBefore(d.since, recorded.timestamp));
|
|
666
|
+
const first = since[0];
|
|
667
|
+
if (first) {
|
|
668
|
+
const what = first.kind === "ownership" ? first.detail ?? "ownership marker changed" : `live configuration of ${first.entity} changed${first.detail ? ` (${first.detail})` : ""}`;
|
|
669
|
+
const more = since.length > 1 ? ` (+${since.length - 1} more)` : "";
|
|
670
|
+
return `${what} since the snapshot of ${first.since ?? "an earlier read"}${more}`;
|
|
671
|
+
}
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Evidence assembled without per-entity observations: the change set's own
|
|
676
|
+
// `update` verdict is the only drift signal there is.
|
|
677
|
+
if (evidence.action === "update") return "live configuration has drifted since";
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
680
|
+
|
|
470
681
|
/**
|
|
471
682
|
* Answer "which build is in `envA`, and is it the one tested in `envB`" —
|
|
472
683
|
* the single-query comparison the epic names explicitly — for one component
|
|
@@ -491,6 +702,13 @@ export interface CrossEnvComparison {
|
|
|
491
702
|
same: boolean;
|
|
492
703
|
/** The identities `same` was decided on (`inputDigest ?? digest` per side), when both sides had one and they differ from the digests shown. */
|
|
493
704
|
comparedOn?: { a: string; b: string };
|
|
705
|
+
/**
|
|
706
|
+
* Set when the two identities differ and exactly one of them is a
|
|
707
|
+
* `legacy-digest` value from before real SHA-256 (#2514). The same content
|
|
708
|
+
* hashes differently in the two forms, so `same: false` then says nothing
|
|
709
|
+
* about whether the builds match.
|
|
710
|
+
*/
|
|
711
|
+
mixedDigestForms?: true;
|
|
494
712
|
}
|
|
495
713
|
|
|
496
714
|
/** Compare the latest recorded digest for `component` between two environments' release records. */
|
|
@@ -512,5 +730,8 @@ export function compareAcrossEnvironments(
|
|
|
512
730
|
digestB: latestB?.digest,
|
|
513
731
|
same: !!idA && !!idB && idA === idB,
|
|
514
732
|
...(comparedOnInputs && idA && idB ? { comparedOn: { a: idA, b: idB } } : {}),
|
|
733
|
+
...(idA && idB && idA !== idB && isLegacyContentDigest(idA) !== isLegacyContentDigest(idB)
|
|
734
|
+
? { mixedDigestForms: true as const }
|
|
735
|
+
: {}),
|
|
515
736
|
};
|
|
516
737
|
}
|
|
@@ -24,9 +24,8 @@
|
|
|
24
24
|
* component.schema.json), a component may attach a `noRollback: "<reason>"`
|
|
25
25
|
* string property directly on the step to declare the compensation gap is
|
|
26
26
|
* intentional and understood, or supply its own explicit compensation via a
|
|
27
|
-
* component-level `rollback` field
|
|
28
|
-
* `rollback-previous
|
|
29
|
-
* in the same phase.
|
|
27
|
+
* component-level `rollback` field or a sibling
|
|
28
|
+
* `rollback-previous`/`snapshot-before` step in the same phase.
|
|
30
29
|
*
|
|
31
30
|
* Triggers on: a bare `{ kind: "run-migration", ... }` step with no
|
|
32
31
|
* `noRollback` reason, no component-level `rollback`, and no sibling
|
|
@@ -129,3 +129,40 @@ export const behaviourFindingContract = activityContract(
|
|
|
129
129
|
mergeRequest: z.string().optional(),
|
|
130
130
|
}),
|
|
131
131
|
);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The propose-only template upgrade (#2550, ws-032). `args` mirrors
|
|
135
|
+
* `ProposeWorkspaceUpgradeArgs` without its test hooks. `returns` names what
|
|
136
|
+
* a step reads: whether anything changed, whether it was proposed, the patch
|
|
137
|
+
* digest the command's gate would bind, and the branch and pull request.
|
|
138
|
+
*/
|
|
139
|
+
export const proposeWorkspaceUpgradeContract = activityContract(
|
|
140
|
+
"proposeWorkspaceUpgrade",
|
|
141
|
+
z.strictObject({
|
|
142
|
+
scope: z.string().optional(),
|
|
143
|
+
to: z.string().optional(),
|
|
144
|
+
mode: z.enum(["report", "branch", "pull-request"]).optional(),
|
|
145
|
+
branch: z.string().optional(),
|
|
146
|
+
base: z.string().optional(),
|
|
147
|
+
remote: z.string().optional(),
|
|
148
|
+
allowCode: z.boolean().optional(),
|
|
149
|
+
cwd: z.string().optional(),
|
|
150
|
+
}),
|
|
151
|
+
z.object({
|
|
152
|
+
scope: z.string(),
|
|
153
|
+
mode: z.enum(["report", "branch", "pull-request"]),
|
|
154
|
+
changed: z.boolean(),
|
|
155
|
+
proposed: z.boolean(),
|
|
156
|
+
checksOk: z.boolean(),
|
|
157
|
+
from: z.string().nullable(),
|
|
158
|
+
to: z.string().nullable(),
|
|
159
|
+
digest: z.string(),
|
|
160
|
+
manualSteps: z.number(),
|
|
161
|
+
governance: z.boolean(),
|
|
162
|
+
branch: z.string().optional(),
|
|
163
|
+
commit: z.string().optional(),
|
|
164
|
+
pushed: z.boolean().optional(),
|
|
165
|
+
prUrl: z.string().optional(),
|
|
166
|
+
summary: z.string(),
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
@@ -147,3 +147,13 @@ export type {
|
|
|
147
147
|
BehaviourFindingDeps,
|
|
148
148
|
BaseCheckout,
|
|
149
149
|
} from "./predict-behaviour";
|
|
150
|
+
|
|
151
|
+
// The propose-only template upgrade (#2550). Loads the workspace code on first
|
|
152
|
+
// call only, so importing this module loads nothing under `workspace/`.
|
|
153
|
+
export { proposeWorkspaceUpgrade } from "./propose-upgrade";
|
|
154
|
+
export type {
|
|
155
|
+
ProposeWorkspaceUpgradeArgs,
|
|
156
|
+
ProposeWorkspaceUpgradeResult,
|
|
157
|
+
ProposeUpgradeMode,
|
|
158
|
+
CommandRunner,
|
|
159
|
+
} from "./propose-upgrade";
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proposeWorkspaceUpgrade — the propose-only activity behind scheduled
|
|
3
|
+
* template upgrades (#2550, D9, ws-032).
|
|
4
|
+
*
|
|
5
|
+
* `chant workspace upgrade` is a core command. An Op, including a workspace Op
|
|
6
|
+
* later (#2554), schedules it through this activity, which stages the same
|
|
7
|
+
* upgrade (fetch, migrate, merge per file, build, lint and `workspace check`
|
|
8
|
+
* in a worktree) and then only proposes it:
|
|
9
|
+
*
|
|
10
|
+
* - `report` (default): stage and check, and return the summary.
|
|
11
|
+
* - `branch`: commit the patch on a proposal branch, by default
|
|
12
|
+
* `chant/upgrade/<scope>`, and push it when the repository has the remote.
|
|
13
|
+
* - `pull-request`: the branch, plus an open pull request against the default
|
|
14
|
+
* branch, created once and edited on later runs.
|
|
15
|
+
*
|
|
16
|
+
* It never writes the default branch or the checked-out branch, and it never
|
|
17
|
+
* applies the patch to the working tree, so an Op cannot rewrite itself or
|
|
18
|
+
* the gates that judge it. Review and merge of the pull request are the
|
|
19
|
+
* approval. The PR body names the patch digest, the value `chant workspace
|
|
20
|
+
* upgrade` binds its gate to, so the two paths approve the same thing.
|
|
21
|
+
*
|
|
22
|
+
* The workspace code loads on first call, so importing core's activities
|
|
23
|
+
* loads nothing under `workspace/` (#2525 rule 5).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { execFile } from "node:child_process";
|
|
27
|
+
import { promisify } from "node:util";
|
|
28
|
+
import type { StagedUpgrade } from "../../workspace/lineage-upgrade";
|
|
29
|
+
|
|
30
|
+
const execFileAsync = promisify(execFile);
|
|
31
|
+
|
|
32
|
+
export type ProposeUpgradeMode = "report" | "branch" | "pull-request";
|
|
33
|
+
|
|
34
|
+
export interface ProposeWorkspaceUpgradeArgs {
|
|
35
|
+
/** The lineage scope: `"."` (default) or a vendor scope's directory. */
|
|
36
|
+
scope?: string;
|
|
37
|
+
/** The target ref. Defaults to the ref the scope is pinned at, so a moving ref such as a branch is followed. */
|
|
38
|
+
to?: string;
|
|
39
|
+
mode?: ProposeUpgradeMode;
|
|
40
|
+
/** The proposal branch. Default `chant/upgrade/<scope>`, with `root` for `"."`. */
|
|
41
|
+
branch?: string;
|
|
42
|
+
/** The branch the pull request targets. Default: the remote's default branch. */
|
|
43
|
+
base?: string;
|
|
44
|
+
/** The remote to push to. Default `origin`. */
|
|
45
|
+
remote?: string;
|
|
46
|
+
/** Run code migrations. Default false. */
|
|
47
|
+
allowCode?: boolean;
|
|
48
|
+
/** The directory holding the lineage lock. Default: the working directory. */
|
|
49
|
+
cwd?: string;
|
|
50
|
+
/** Replaces the git and gh child processes. For tests. */
|
|
51
|
+
_run?: CommandRunner;
|
|
52
|
+
/** Replaces build and lint in the worktree. For tests. */
|
|
53
|
+
_runChant?: import("../../workspace/lineage-upgrade").ChantRunner;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Runs `git` or `gh` with arguments, returning stdout. Rejects on a non-zero exit. */
|
|
57
|
+
export type CommandRunner = (bin: "git" | "gh", args: string[], cwd: string) => Promise<string>;
|
|
58
|
+
|
|
59
|
+
export interface ProposeWorkspaceUpgradeResult {
|
|
60
|
+
scope: string;
|
|
61
|
+
mode: ProposeUpgradeMode;
|
|
62
|
+
/** Whether the template has anything new for the scope. */
|
|
63
|
+
changed: boolean;
|
|
64
|
+
/** Whether a branch or pull request was written. */
|
|
65
|
+
proposed: boolean;
|
|
66
|
+
checksOk: boolean;
|
|
67
|
+
from: string | null;
|
|
68
|
+
to: string | null;
|
|
69
|
+
/** The patch digest `chant workspace upgrade` binds its gate to. */
|
|
70
|
+
digest: string;
|
|
71
|
+
manualSteps: number;
|
|
72
|
+
governance: boolean;
|
|
73
|
+
branch?: string;
|
|
74
|
+
commit?: string;
|
|
75
|
+
pushed?: boolean;
|
|
76
|
+
prUrl?: string;
|
|
77
|
+
summary: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const defaultRun: CommandRunner = async (bin, args, cwd) => (await execFileAsync(bin, args, { cwd, maxBuffer: 64 * 1024 * 1024 })).stdout;
|
|
81
|
+
|
|
82
|
+
/** The proposal branch for a scope. */
|
|
83
|
+
export function proposalBranch(scope: string): string {
|
|
84
|
+
return `chant/upgrade/${scope === "." ? "root" : scope.replace(/[^A-Za-z0-9._/-]/g, "-")}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The marker that finds this scope's pull request again. */
|
|
88
|
+
export function proposalMarker(scope: string): string {
|
|
89
|
+
return `<!-- chant-workspace-upgrade:${scope} -->`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function body(staged: StagedUpgrade, lines: string[]): string {
|
|
93
|
+
return [
|
|
94
|
+
proposalMarker(staged.scope),
|
|
95
|
+
`## Template upgrade: \`${staged.scope}\``,
|
|
96
|
+
"",
|
|
97
|
+
`\`${staged.template}\` from \`${staged.from ?? "(no ref)"}\` to \`${staged.to ?? "(no ref)"}\`.`,
|
|
98
|
+
"",
|
|
99
|
+
`Patch digest: \`${staged.digest}\``,
|
|
100
|
+
"",
|
|
101
|
+
"```",
|
|
102
|
+
...lines,
|
|
103
|
+
"```",
|
|
104
|
+
"",
|
|
105
|
+
staged.manualSteps.length > 0
|
|
106
|
+
? `${staged.manualSteps.length} file(s) did not merge cleanly and are kept as they were, each with a manual step. \`chant workspace check\` fails until each is merged and resolved with \`chant workspace lineage resolve <path>\`.`
|
|
107
|
+
: "Every file merged cleanly.",
|
|
108
|
+
staged.governance ? "\nThis upgrade changes governance files. Review it under the rules on the default branch, not the ones it proposes." : "",
|
|
109
|
+
"",
|
|
110
|
+
"Proposed by the `proposeWorkspaceUpgrade` activity. It never writes the default branch.",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function defaultBranch(run: CommandRunner, repo: string, remote: string): Promise<string | null> {
|
|
115
|
+
try {
|
|
116
|
+
const ref = (await run("git", ["symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`], repo)).trim();
|
|
117
|
+
return ref.startsWith(`${remote}/`) ? ref.slice(remote.length + 1) : ref;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Stage a workspace upgrade and propose it as a branch or pull request. Never writes the default branch. */
|
|
124
|
+
export async function proposeWorkspaceUpgrade(args: ProposeWorkspaceUpgradeArgs): Promise<ProposeWorkspaceUpgradeResult> {
|
|
125
|
+
const mode = args.mode ?? "report";
|
|
126
|
+
const run = args._run ?? defaultRun;
|
|
127
|
+
const remote = args.remote ?? "origin";
|
|
128
|
+
const { stageUpgrade, describeStaged, commitStagedUpgrade } = await import("../../workspace/lineage-upgrade");
|
|
129
|
+
const staged = await stageUpgrade({
|
|
130
|
+
root: args.cwd ?? process.cwd(),
|
|
131
|
+
scope: args.scope,
|
|
132
|
+
to: args.to,
|
|
133
|
+
allowCode: args.allowCode,
|
|
134
|
+
...(args._runChant ? { runChant: args._runChant } : {}),
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
const lines = describeStaged(staged);
|
|
138
|
+
const result: ProposeWorkspaceUpgradeResult = {
|
|
139
|
+
scope: staged.scope,
|
|
140
|
+
mode,
|
|
141
|
+
changed: staged.changed,
|
|
142
|
+
proposed: false,
|
|
143
|
+
checksOk: staged.checksOk,
|
|
144
|
+
from: staged.from,
|
|
145
|
+
to: staged.to,
|
|
146
|
+
digest: staged.digest,
|
|
147
|
+
manualSteps: staged.manualSteps.length,
|
|
148
|
+
governance: staged.governance !== null,
|
|
149
|
+
summary: lines.join("\n"),
|
|
150
|
+
};
|
|
151
|
+
if (!result.changed || !result.checksOk || mode === "report") return result;
|
|
152
|
+
|
|
153
|
+
const branch = args.branch ?? proposalBranch(staged.scope);
|
|
154
|
+
const remoteDefault = await defaultBranch(run, staged.repo, remote);
|
|
155
|
+
const base = args.base ?? remoteDefault;
|
|
156
|
+
let current: string | null = null;
|
|
157
|
+
try {
|
|
158
|
+
current = (await run("git", ["symbolic-ref", "--short", "HEAD"], staged.repo)).trim();
|
|
159
|
+
} catch {
|
|
160
|
+
// Detached HEAD: nothing checked out to protect.
|
|
161
|
+
}
|
|
162
|
+
for (const [what, name] of [["default branch", remoteDefault], ["base branch", base], ["checked-out branch", current]] as const) {
|
|
163
|
+
if (name && branch === name) {
|
|
164
|
+
throw new Error(`proposeWorkspaceUpgrade only proposes: it will not write the ${what} "${name}". Choose another branch.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (mode === "pull-request" && !base) {
|
|
168
|
+
throw new Error(`proposeWorkspaceUpgrade: cannot tell the default branch of "${remote}"; pass base`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const title = `chore(upgrade): ${staged.template} ${staged.to ?? ""} (${staged.scope})`.replace(/\s+/g, " ").replace(" )", ")");
|
|
172
|
+
result.commit = commitStagedUpgrade(staged, branch, `${title}\n\nPatch digest: ${staged.digest}\n`);
|
|
173
|
+
result.branch = branch;
|
|
174
|
+
result.proposed = true;
|
|
175
|
+
|
|
176
|
+
let hasRemote = false;
|
|
177
|
+
try {
|
|
178
|
+
await run("git", ["remote", "get-url", remote], staged.repo);
|
|
179
|
+
hasRemote = true;
|
|
180
|
+
} catch {
|
|
181
|
+
hasRemote = false;
|
|
182
|
+
}
|
|
183
|
+
if (hasRemote) {
|
|
184
|
+
// The proposal branch is rebuilt from the default branch on every run, so it is replaced.
|
|
185
|
+
await run("git", ["push", "--force", remote, `${result.commit}:refs/heads/${branch}`], staged.repo);
|
|
186
|
+
result.pushed = true;
|
|
187
|
+
} else {
|
|
188
|
+
result.pushed = false;
|
|
189
|
+
}
|
|
190
|
+
if (mode !== "pull-request") return result;
|
|
191
|
+
if (!hasRemote) throw new Error(`proposeWorkspaceUpgrade: no remote "${remote}" to open a pull request on`);
|
|
192
|
+
|
|
193
|
+
const text = body(staged, lines);
|
|
194
|
+
const existing = (await run("gh", ["pr", "list", "--head", branch, "--state", "open", "--json", "url", "--jq", ".[0].url // empty"], staged.repo)).trim();
|
|
195
|
+
if (existing) {
|
|
196
|
+
await run("gh", ["pr", "edit", existing, "--title", title, "--body", text], staged.repo);
|
|
197
|
+
result.prUrl = existing;
|
|
198
|
+
} else {
|
|
199
|
+
const out = await run("gh", ["pr", "create", "--base", base!, "--head", branch, "--title", title, "--body", text], staged.repo);
|
|
200
|
+
result.prUrl = out.trim().split("\n").pop();
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
} finally {
|
|
204
|
+
staged.dispose();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
import * as coreContracts from "./activities/activity-contracts";
|
|
38
38
|
import { collectActivityContracts, type ActivityContract } from "./activity-contract";
|
|
39
|
+
import { importLexiconModule } from "../lexicon-module";
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* The shape {@link loadActivityContracts} reads off a plugin: an optional
|
|
@@ -74,16 +75,29 @@ export async function loadActivityContracts(
|
|
|
74
75
|
|
|
75
76
|
for (const entry of lexicons) {
|
|
76
77
|
const name = typeof entry === "string" ? entry : entry.name;
|
|
78
|
+
let contributor: LexiconActivityContractContributor | undefined = typeof entry === "string" ? undefined : entry;
|
|
77
79
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// chant #2520 — a lexicon declared by module path has no subpath to
|
|
81
|
+
// import. Its plugin's `activityContracts()` member stands in for it;
|
|
82
|
+
// given only the name, the plugin is read from the declared module.
|
|
83
|
+
const local = await importLexiconModule(name);
|
|
84
|
+
if (local !== undefined) {
|
|
85
|
+
contributor ??= Object.values(local).find(
|
|
86
|
+
(v): v is LexiconActivityContractContributor =>
|
|
87
|
+
typeof v === "object" && v !== null && (v as { name?: unknown }).name === name &&
|
|
88
|
+
typeof (v as { activityContracts?: unknown }).activityContracts === "function",
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
const spec = `@intentius/chant-lexicon-${name}/op/activity-contracts`;
|
|
92
|
+
collectActivityContracts((await import(spec)) as Record<string, unknown>, contracts);
|
|
93
|
+
}
|
|
80
94
|
} catch {
|
|
81
95
|
// Lexicon absent or declares no contracts at the conventional subpath.
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
if (
|
|
98
|
+
if (contributor !== undefined && typeof contributor.activityContracts === "function") {
|
|
85
99
|
try {
|
|
86
|
-
for (const contract of
|
|
100
|
+
for (const contract of contributor.activityContracts()) contracts.set(contract.name, contract);
|
|
87
101
|
} catch {
|
|
88
102
|
// A plugin member that throws contributes nothing, the same as one that is absent.
|
|
89
103
|
}
|