@intentius/chant 0.102.0 → 0.104.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 +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.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/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +3 -2
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +63 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +48 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +7 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +126 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/index.d.ts +4 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +27 -3
- package/dist/workspace/box-factory.d.ts.map +1 -1
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +2 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +9 -2
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +108 -14
- package/dist/workspace/conformance/writer.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +65 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +153 -4
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +1 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +28 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +102 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +48 -0
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +33 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +7 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +66 -6
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +28 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +138 -6
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs.d.ts +132 -11
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +2 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +54 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +16 -5
- package/dist/workspace/symbols.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +6 -1
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +6 -2
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +34 -3
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/main.test.ts +20 -0
- package/src/cli/main.ts +185 -23
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +34 -6
- package/src/cli/registry.ts +63 -1
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +51 -0
- package/src/lifecycle/gate-ledger.ts +7 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/index.ts +9 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/types.ts +8 -0
- package/src/op/work-lease-run.ts +3 -0
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +4 -1
- package/src/workspace/box-factory.ts +37 -4
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +3 -0
- package/src/workspace/checks.ts +10 -3
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.mjs +22 -1
- package/src/workspace/conformance/index.ts +5 -1
- package/src/workspace/conformance/vitest.ts +33 -1
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
- package/src/workspace/conformance/writer.test.ts +32 -2
- package/src/workspace/conformance/writer.ts +339 -28
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +427 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +153 -4
- package/src/workspace/declaration.ts +141 -0
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +2 -2
- package/src/workspace/graph.schema.json +67 -0
- package/src/workspace/identity.test.ts +23 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +26 -9
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +130 -3
- package/src/workspace/intent-record.ts +74 -28
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +5 -5
- package/src/workspace/intent.schema.json +234 -4
- package/src/workspace/intent.ts +260 -66
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +157 -2
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +162 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +38 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +78 -0
- package/src/workspace/records-cli.ts +20 -6
- package/src/workspace/records-close.schema.json +78 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +7 -0
- package/src/workspace/records-review.schema.json +78 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-write.ts +192 -25
- package/src/workspace/records.schema.json +16 -0
- package/src/workspace/records.ts +50 -9
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +343 -14
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +416 -5
- package/src/workspace/runs.schema.json +451 -4
- package/src/workspace/runs.test.ts +39 -0
- package/src/workspace/runs.ts +296 -35
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +5 -2
- package/src/workspace/status-gates.ts +3 -1
- package/src/workspace/status.schema.json +126 -3
- package/src/workspace/status.ts +90 -1
- package/src/workspace/symbols.ts +20 -9
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.ts +3 -0
- package/src/workspace/work-evidence.schema.json +3 -0
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +16 -1
- package/src/workspace/write-scope.ts +33 -11
|
@@ -33,19 +33,28 @@
|
|
|
33
33
|
* `--resume` carries the earlier attempt's progress, `remainingFanOut` narrows
|
|
34
34
|
* the plan before the gate is decided, and the digest is carried rather than
|
|
35
35
|
* recomputed, so the approval still stands.
|
|
36
|
+
*
|
|
37
|
+
* `--wave-gate <name>` (#3049) swaps the one gate for one per wave: the same
|
|
38
|
+
* repeated command stops at each wave's gate in turn, each planned after the
|
|
39
|
+
* wave before it applied, and `chant approve fan-out <name>-wave-<n>` answers
|
|
40
|
+
* it. `--wave <n>` runs one wave, for a CI job per wave.
|
|
36
41
|
*/
|
|
37
42
|
|
|
38
43
|
import { resolve, dirname } from "node:path";
|
|
39
|
-
import {
|
|
44
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
40
45
|
import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
|
|
41
46
|
import { samePlanDigest } from "../../lifecycle/plan-digest";
|
|
42
47
|
import { affectedStacks } from "../../lifecycle/affected";
|
|
48
|
+
import { changedFilesBetween, lexiconChangedUnits } from "../../components/changed-files";
|
|
43
49
|
import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support";
|
|
44
|
-
import { runFanOut } from "../../components/fan-out-run";
|
|
50
|
+
import { EarlierWaveNotAppliedError, runFanOut } from "../../components/fan-out-run";
|
|
51
|
+
import { readFanOutAttempt, writeFanOutAttempt, type FanOutAttempt } from "../../components/fan-out-record";
|
|
52
|
+
import { describeChangedWave, withWaveRecord, type WaveRecord } from "../../gated-waves";
|
|
45
53
|
import { renderFanOutHuman, renderFanOutJson, renderFanOutPlan } from "../../components/fan-out-output";
|
|
46
54
|
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
47
55
|
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary";
|
|
48
56
|
import { approveCommand } from "../../op/gate";
|
|
57
|
+
import { getHeadCommit } from "../../lifecycle/git";
|
|
49
58
|
import { remainingFanOut, type ChangedUnits, type FanOutProgress } from "../../components/fan-out";
|
|
50
59
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
51
60
|
import { formatError, formatInfo, formatWarning } from "../format";
|
|
@@ -54,37 +63,6 @@ import { resolveBuildRoot } from "./lifecycle";
|
|
|
54
63
|
import { GATED_EXIT_CODE, recordAutoReleasesForRun } from "./run";
|
|
55
64
|
import type { CommandContext } from "../registry";
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* What an attempt left behind, and what the next one reads (`--resume`).
|
|
61
|
-
*
|
|
62
|
-
* The digest is stored alongside the progress because progress is only
|
|
63
|
-
* meaningful for the plan it was made against. A fan-out derived from different
|
|
64
|
-
* source is a different fan-out, and carrying "cluster-a already applied" into
|
|
65
|
-
* it would be a claim about work nobody did.
|
|
66
|
-
*/
|
|
67
|
-
interface FanOutAttempt {
|
|
68
|
-
digest: string;
|
|
69
|
-
completed: string[];
|
|
70
|
-
failed: string[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function readAttempt(path: string): FanOutAttempt | undefined {
|
|
74
|
-
if (!existsSync(path)) return undefined;
|
|
75
|
-
const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<FanOutAttempt>;
|
|
76
|
-
return {
|
|
77
|
-
digest: typeof parsed.digest === "string" ? parsed.digest : "",
|
|
78
|
-
completed: Array.isArray(parsed.completed) ? parsed.completed.filter((n): n is string => typeof n === "string") : [],
|
|
79
|
-
failed: Array.isArray(parsed.failed) ? parsed.failed.filter((n): n is string => typeof n === "string") : [],
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function writeAttempt(path: string, attempt: FanOutAttempt): void {
|
|
84
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
85
|
-
writeFileSync(path, JSON.stringify(attempt, null, 2) + "\n");
|
|
86
|
-
}
|
|
87
|
-
|
|
88
66
|
/**
|
|
89
67
|
* The stack-level change signal, from whichever end the invocation supplied.
|
|
90
68
|
*
|
|
@@ -100,13 +78,20 @@ function writeAttempt(path: string, attempt: FanOutAttempt): void {
|
|
|
100
78
|
* consumes a changed export is affected at stack granularity in the same way a
|
|
101
79
|
* changed component is at component granularity.
|
|
102
80
|
*/
|
|
103
|
-
async function resolveChangedUnits(
|
|
81
|
+
export async function resolveChangedUnits(
|
|
104
82
|
ctx: CommandContext,
|
|
105
83
|
config: ChantConfig,
|
|
84
|
+
opts: {
|
|
85
|
+
/** Measure from this commit instead of `--base` (a pull request's merge base, #3183). */
|
|
86
|
+
base?: string;
|
|
87
|
+
/** Build both refs for `lifecycle affected` only when the project declares `stacks`. */
|
|
88
|
+
stacksOnly?: boolean;
|
|
89
|
+
} = {},
|
|
106
90
|
): Promise<{ units: ChangedUnits } | { error: string; hint?: string }> {
|
|
107
91
|
const { args } = ctx;
|
|
92
|
+
const base = opts.base ?? args.base;
|
|
108
93
|
const fromFile = args.fromAffected;
|
|
109
|
-
if (
|
|
94
|
+
if (base && fromFile) {
|
|
110
95
|
return {
|
|
111
96
|
error: "--base and --from-affected both name a change signal",
|
|
112
97
|
hint: "Pass --base <ref> to derive it here, or --from-affected <file> to read one `chant lifecycle affected --json` already wrote.",
|
|
@@ -136,7 +121,7 @@ async function resolveChangedUnits(
|
|
|
136
121
|
};
|
|
137
122
|
}
|
|
138
123
|
|
|
139
|
-
if (!
|
|
124
|
+
if (!base) {
|
|
140
125
|
return {
|
|
141
126
|
error: "A change signal is required: chant components fan-out --base <ref> [--head <ref>]",
|
|
142
127
|
hint: "Or pass --from-affected <file> with the JSON `chant lifecycle affected --base <ref> --json` wrote.",
|
|
@@ -144,23 +129,37 @@ async function resolveChangedUnits(
|
|
|
144
129
|
}
|
|
145
130
|
|
|
146
131
|
try {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
132
|
+
// A unit that is not chant source, such as a Terraform root, never shows
|
|
133
|
+
// in the artifact diff below; the lexicon that owns it answers from the
|
|
134
|
+
// changed paths (#3183).
|
|
135
|
+
const projectRoot = resolve(".");
|
|
136
|
+
const lexiconUnits = ctx.plugins.some((p) => p.changedUnits)
|
|
137
|
+
? await lexiconChangedUnits(ctx.plugins, {
|
|
138
|
+
projectRoot,
|
|
139
|
+
config: config as Record<string, unknown>,
|
|
140
|
+
changedFiles: await changedFilesBetween(projectRoot, base, args.head ?? "HEAD"),
|
|
141
|
+
})
|
|
142
|
+
: [];
|
|
143
|
+
const result =
|
|
144
|
+
opts.stacksOnly && !config.stacks?.length
|
|
145
|
+
? { changed: [], dependents: [], indeterminate: [] }
|
|
146
|
+
: await affectedStacks({
|
|
147
|
+
// Components are discovered from the current directory, the convention
|
|
148
|
+
// every component command follows. In a multi-stack project the diff
|
|
149
|
+
// reads the same root, because `stacks[].src` is written relative to it.
|
|
150
|
+
projectPath: config.stacks?.length ? resolve(".") : resolveBuildRoot(args, config),
|
|
151
|
+
serializers: ctx.plugins.map((p) => p.serializer),
|
|
152
|
+
baseRef: base,
|
|
153
|
+
headRef: args.head,
|
|
154
|
+
includeDependents: args.includeDependents,
|
|
155
|
+
// A project that declares its stacks gets an answer keyed by stack name,
|
|
156
|
+
// which is the name a component's deploy step uses. Without this the diff
|
|
157
|
+
// answers by lexicon partition and the join below claims none of it.
|
|
158
|
+
...(config.stacks ? { stacks: config.stacks } : {}),
|
|
159
|
+
});
|
|
161
160
|
return {
|
|
162
161
|
units: {
|
|
163
|
-
changed: [...new Set([...result.changed, ...result.dependents])].sort(),
|
|
162
|
+
changed: [...new Set([...result.changed, ...result.dependents, ...lexiconUnits])].sort(),
|
|
164
163
|
indeterminate: result.indeterminate,
|
|
165
164
|
},
|
|
166
165
|
};
|
|
@@ -190,6 +189,21 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
190
189
|
return 1;
|
|
191
190
|
}
|
|
192
191
|
|
|
192
|
+
// The two gate modes (#3049). `--gate` approves the whole set once, as
|
|
193
|
+
// #2417 decided; `--wave-gate` approves each wave once the waves before it
|
|
194
|
+
// applied. They answer different questions, so one run takes one of them.
|
|
195
|
+
if (args.gate && args.waveGate) {
|
|
196
|
+
console.error(formatError({
|
|
197
|
+
message: "--gate and --wave-gate both name a gate",
|
|
198
|
+
hint: "Pass --gate <name> for one approval over the whole set, or --wave-gate <name> for one per wave.",
|
|
199
|
+
}));
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
if (args.wave !== undefined && !args.waveGate) {
|
|
203
|
+
console.error(formatError({ message: "--wave runs one wave of a gated-wave fan-out, so it needs --wave-gate <name>" }));
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
193
207
|
const signal = await resolveChangedUnits(ctx, config);
|
|
194
208
|
if ("error" in signal) {
|
|
195
209
|
console.error(formatError({ message: signal.error, ...(signal.hint ? { hint: signal.hint } : {}) }));
|
|
@@ -204,6 +218,7 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
204
218
|
sandbox: args.sandbox,
|
|
205
219
|
buildParams: paramsResolution.provenance,
|
|
206
220
|
config,
|
|
221
|
+
...(args.canary?.length ? { canary: args.canary } : {}),
|
|
207
222
|
});
|
|
208
223
|
} catch (err) {
|
|
209
224
|
// A cycle or an unknown `dependsOn` refuses here, over the whole graph and
|
|
@@ -226,6 +241,9 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
226
241
|
}
|
|
227
242
|
|
|
228
243
|
const gate = args.gate ? { op: FAN_OUT_GATE_OP, gate: args.gate } : undefined;
|
|
244
|
+
const waveGate = args.waveGate
|
|
245
|
+
? { op: FAN_OUT_GATE_OP, gate: args.waveGate, ...(args.wave !== undefined ? { only: args.wave } : {}) }
|
|
246
|
+
: undefined;
|
|
229
247
|
|
|
230
248
|
// `--resume`: what an earlier attempt at this same plan finished. Read before
|
|
231
249
|
// the dry-run branch so a plan-only invocation shows what is actually left
|
|
@@ -234,11 +252,13 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
234
252
|
// approval survives the resume rather than being re-asked for.
|
|
235
253
|
const resumePath = args.resume ? resolve(args.resume) : undefined;
|
|
236
254
|
let priorCompleted: string[] = [];
|
|
255
|
+
let priorOutputs: Record<string, Record<string, unknown>> = {};
|
|
256
|
+
let priorWaves: WaveRecord[] = [];
|
|
237
257
|
let progress: FanOutProgress | undefined;
|
|
238
258
|
if (resumePath) {
|
|
239
259
|
let attempt: FanOutAttempt | undefined;
|
|
240
260
|
try {
|
|
241
|
-
attempt =
|
|
261
|
+
attempt = readFanOutAttempt(resumePath);
|
|
242
262
|
} catch (err) {
|
|
243
263
|
console.error(formatError({ message: `--resume: could not read "${args.resume}": ${err instanceof Error ? err.message : String(err)}` }));
|
|
244
264
|
return 1;
|
|
@@ -255,16 +275,22 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
255
275
|
// exists to retry them — the operator repeated the command precisely
|
|
256
276
|
// because the thing that failed is now expected to work.
|
|
257
277
|
priorCompleted = attempt.completed;
|
|
278
|
+
priorOutputs = Object.fromEntries(
|
|
279
|
+
Object.entries(attempt.outputs).filter(([name]) => attempt.completed.includes(name)),
|
|
280
|
+
);
|
|
258
281
|
progress = { completed: attempt.completed };
|
|
282
|
+
priorWaves = attempt.waves ?? [];
|
|
259
283
|
}
|
|
260
284
|
}
|
|
261
285
|
|
|
262
286
|
if (args.dryRun) {
|
|
263
287
|
// `remainingFanOut` carries the digest rather than minting a new one, so
|
|
264
288
|
// the digest printed here is the same string that approves the real run.
|
|
265
|
-
|
|
289
|
+
// A gated-wave fan-out keeps the derivation's wave numbers, since they
|
|
290
|
+
// name the gates, so its plan is printed whole.
|
|
291
|
+
const plan = progress && !waveGate ? remainingFanOut(derived.plan, derived.components, progress) : derived.plan;
|
|
266
292
|
if (args.json) renderFanOutJson(plan);
|
|
267
|
-
else renderFanOutPlan(plan, { ...(gate ? { gate } : {}) });
|
|
293
|
+
else renderFanOutPlan(plan, { ...(gate ? { gate } : {}), ...(waveGate ? { waveGate } : {}) });
|
|
268
294
|
return 0;
|
|
269
295
|
}
|
|
270
296
|
|
|
@@ -279,13 +305,72 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
279
305
|
}
|
|
280
306
|
|
|
281
307
|
const registry = await fanOutRegistry(projectPath, config);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
308
|
+
// #3061: the commit this run deploys, for the workloads' vcs.ref.head.revision.
|
|
309
|
+
const revision = await getHeadCommit().catch(() => undefined);
|
|
310
|
+
|
|
311
|
+
// The record is rewritten as each component settles, not only once the run
|
|
312
|
+
// returns. A fan-out killed in its third wave (a cancelled CI job, a lost
|
|
313
|
+
// runner, ctrl-c) then still knows that the first two applied, and the
|
|
314
|
+
// repeated command picks up from there instead of redoing them.
|
|
315
|
+
const completedSoFar = new Set(priorCompleted);
|
|
316
|
+
const failedSoFar: string[] = [];
|
|
317
|
+
const outputsSoFar = { ...priorOutputs };
|
|
318
|
+
let wavesSoFar = priorWaves;
|
|
319
|
+
const recordAttempt = (): void => {
|
|
320
|
+
if (!resumePath) return;
|
|
321
|
+
const attempt: FanOutAttempt = {
|
|
322
|
+
digest: derived.plan.digest,
|
|
323
|
+
completed: [...completedSoFar].sort(),
|
|
324
|
+
failed: [...failedSoFar].sort(),
|
|
325
|
+
outputs: outputsSoFar,
|
|
326
|
+
...(wavesSoFar.length > 0 ? { waves: wavesSoFar } : {}),
|
|
327
|
+
};
|
|
328
|
+
writeFanOutAttempt(resumePath, attempt);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
let result;
|
|
332
|
+
try {
|
|
333
|
+
result = await runFanOut(derived.plan, derived.components, registry, {
|
|
334
|
+
env: args.env ?? "local",
|
|
335
|
+
releaseIdentity: () => (revision ? { revision } : {}),
|
|
336
|
+
// A completed component's recorded outputs win over a `--seed-outputs`
|
|
337
|
+
// entry for the same name, as a fresh apply's outputs merge over a seed in
|
|
338
|
+
// the driver: the record holds what this fan-out itself applied.
|
|
339
|
+
componentOutputs: { ...seededOutputs, ...priorOutputs },
|
|
340
|
+
...(gate ? { gate } : {}),
|
|
341
|
+
...(waveGate ? { waveGate } : {}),
|
|
342
|
+
...(waveGate
|
|
343
|
+
? {
|
|
344
|
+
onWaveSettled: (record: WaveRecord) => {
|
|
345
|
+
wavesSoFar = withWaveRecord(wavesSoFar, record);
|
|
346
|
+
recordAttempt();
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
: {}),
|
|
350
|
+
...(progress ? { progress } : {}),
|
|
351
|
+
...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
|
|
352
|
+
...(resumePath
|
|
353
|
+
? {
|
|
354
|
+
onComponentSettled: (settled, outputs) => {
|
|
355
|
+
if (settled.status === "ok") {
|
|
356
|
+
completedSoFar.add(settled.component);
|
|
357
|
+
if (outputs) outputsSoFar[settled.component] = outputs;
|
|
358
|
+
} else if (settled.status === "fail") {
|
|
359
|
+
failedSoFar.push(settled.component);
|
|
360
|
+
}
|
|
361
|
+
recordAttempt();
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
: {}),
|
|
365
|
+
});
|
|
366
|
+
} catch (err) {
|
|
367
|
+
// `--wave <n>` reached before an earlier wave applied: nothing ran.
|
|
368
|
+
if (err instanceof EarlierWaveNotAppliedError) {
|
|
369
|
+
console.error(formatError({ message: err.message }));
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
throw err;
|
|
373
|
+
}
|
|
289
374
|
|
|
290
375
|
if (args.dumpOutputs) {
|
|
291
376
|
const dumpPath = resolve(args.dumpOutputs);
|
|
@@ -293,25 +378,28 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
293
378
|
writeFileSync(dumpPath, JSON.stringify(result.componentOutputs, null, 2));
|
|
294
379
|
}
|
|
295
380
|
|
|
296
|
-
// Written even on a failure and even on a gate:
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
writeAttempt(resumePath, {
|
|
301
|
-
digest: result.plan.digest,
|
|
302
|
-
completed: [...new Set([...priorCompleted, ...result.completed])].sort(),
|
|
303
|
-
failed: result.failed,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
381
|
+
// Written again at the end, even on a failure and even on a gate: a gated
|
|
382
|
+
// attempt got through nothing, which is itself worth recording against this
|
|
383
|
+
// plan's digest.
|
|
384
|
+
recordAttempt();
|
|
306
385
|
|
|
307
386
|
if (args.json) renderFanOutJson(result);
|
|
308
|
-
else renderFanOutHuman(result, { ...(gate ? { gate } : {}) });
|
|
387
|
+
else renderFanOutHuman(result, { ...(gate ? { gate } : {}), ...(waveGate ? { waveGate } : {}) });
|
|
388
|
+
|
|
389
|
+
// A wave whose set moved after its approval stops with both digests named
|
|
390
|
+
// (#3049), the #2300 refusal on a set. Printed on stderr even under --json,
|
|
391
|
+
// since it is the line a CI log reader is looking for.
|
|
392
|
+
for (const wave of result.waves ?? []) {
|
|
393
|
+
if (wave.status === "gated" && wave.approved) console.error(formatWarning({ message: describeChangedWave(wave) }));
|
|
394
|
+
}
|
|
309
395
|
|
|
310
396
|
// The same durable trace `chant run --components` leaves (#597), for the
|
|
311
397
|
// components this fan-out actually applied. A partial fan-out records the
|
|
312
398
|
// branches that finished and nothing else, which is the whole reason the
|
|
313
399
|
// runner reports `completed` separately from `failed`.
|
|
314
|
-
|
|
400
|
+
// A gated-wave run can stop at a later wave's gate after earlier waves
|
|
401
|
+
// applied, and those applies are recorded like any other.
|
|
402
|
+
if (result.status !== "gated" || result.completed.length > 0) {
|
|
315
403
|
await recordAutoReleasesForRun(
|
|
316
404
|
result.results,
|
|
317
405
|
args.env ?? "local",
|
|
@@ -320,7 +408,9 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
320
408
|
);
|
|
321
409
|
}
|
|
322
410
|
|
|
323
|
-
|
|
411
|
+
// A failed root outranks a gate in the status, but a gated-wave run that
|
|
412
|
+
// stopped at a wave's gate still prints how to approve it.
|
|
413
|
+
if (result.gate && (result.status === "gated" || waveGate)) {
|
|
324
414
|
const pending = result.gate;
|
|
325
415
|
// The pending fact's own plan: the fan-out's digest for the gate over the
|
|
326
416
|
// set, or a component's plan and environment for a gate inside one (#2574).
|
|
@@ -337,7 +427,7 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
337
427
|
...(await summaryLedgerPrefix()),
|
|
338
428
|
...(pending.environment ? { environment: pending.environment } : {}),
|
|
339
429
|
});
|
|
340
|
-
return GATED_EXIT_CODE;
|
|
430
|
+
return result.status === "gated" ? GATED_EXIT_CODE : 1;
|
|
341
431
|
}
|
|
342
432
|
|
|
343
433
|
return result.status === "ok" ? 0 : 1;
|
|
@@ -637,6 +637,9 @@ async function runComponentGraphView(
|
|
|
637
637
|
// `chant workspace graph --composites` labels a component without
|
|
638
638
|
// importing its source.
|
|
639
639
|
...(graph.archetypes?.[name] ? { archetype: graph.archetypes[name] } : {}),
|
|
640
|
+
// The environments the component declares (#3153), where each release goes, so
|
|
641
|
+
// `chant workspace graph --composites` lists them without importing its source.
|
|
642
|
+
...(graph.environments?.[name] ? { environments: graph.environments[name] } : {}),
|
|
640
643
|
},
|
|
641
644
|
// Deep-link the node to its `*.component.ts` (behold's inspect panel).
|
|
642
645
|
...(graph.files?.[name] ? { sourceLoc: { file: graph.files[name] } } : {}),
|
|
@@ -455,6 +455,27 @@ describe("runApprove", () => {
|
|
|
455
455
|
vi.unstubAllEnvs();
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
+
// #3402 — a relayed approval names who relayed it, typed, not in --note.
|
|
459
|
+
test("--relayed-by is recorded on the resolution line, and a blank one is refused", async () => {
|
|
460
|
+
seedPending("fountain-apply", "rollout-gate", PLAN_A);
|
|
461
|
+
appendGateResolutionMock.mockResolvedValue({
|
|
462
|
+
commit: "sha",
|
|
463
|
+
record: { version: 1, op: "fountain-apply", gate: "rollout-gate", resolvedBy: "github:alice", relayedBy: "github:hud-follower", timestamp: "2026-01-01T00:00:00.000Z" },
|
|
464
|
+
});
|
|
465
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
466
|
+
|
|
467
|
+
expect(await runApprove(ctx({ path: "fountain-apply", extraPositional: "rollout-gate", actor: "github:alice", relayedBy: "github:hud-follower" }))).toBe(0);
|
|
468
|
+
expect(appendGateResolutionMock).toHaveBeenCalledWith(
|
|
469
|
+
expect.objectContaining({ resolvedBy: "github:alice", relayedBy: "github:hud-follower" }),
|
|
470
|
+
);
|
|
471
|
+
expect(errSpy.mock.calls.flat().join("\n")).toMatch(/resolved by github:alice, relayed by github:hud-follower,/);
|
|
472
|
+
|
|
473
|
+
appendGateResolutionMock.mockClear();
|
|
474
|
+
expect(await runApprove(ctx({ path: "fountain-apply", extraPositional: "rollout-gate", actor: "github:alice", relayedBy: "a\nb" }))).toBe(1);
|
|
475
|
+
expect(appendGateResolutionMock).not.toHaveBeenCalled();
|
|
476
|
+
errSpy.mockRestore();
|
|
477
|
+
});
|
|
478
|
+
|
|
458
479
|
test("without --url, the surrounding PR/MR job is itself the address", async () => {
|
|
459
480
|
vi.stubEnv("GITHUB_SERVER_URL", "https://github.com");
|
|
460
481
|
vi.stubEnv("GITHUB_REPOSITORY", "INTENTIUS/chant");
|
|
@@ -134,6 +134,9 @@ async function collectOperatorSubscribers(
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/** A pull request's gate op, `pr-<number>` (#3183, `prOp` in ../../pr-loop.ts). */
|
|
138
|
+
const PR_GATE_OP = /^pr-\d+$/;
|
|
139
|
+
|
|
137
140
|
// ── chant operator ──────────────────────────────────────────────────────────
|
|
138
141
|
|
|
139
142
|
/**
|
|
@@ -882,7 +885,9 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
882
885
|
console.error(formatInfo(
|
|
883
886
|
opName === FAN_OUT_GATE_OP
|
|
884
887
|
? `The next \`chant components fan-out\` decides this gate from scratch and records a fresh pending fact.`
|
|
885
|
-
: opName
|
|
888
|
+
: PR_GATE_OP.test(opName)
|
|
889
|
+
? `The next \`chant components pr-apply\` decides this gate from scratch and records a fresh pending fact.`
|
|
890
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
886
891
|
? `The next \`chant workspace upgrade ${gate}\` decides this gate from scratch and records a fresh pending fact.`
|
|
887
892
|
: `The next \`chant run ${opName}\` decides this gate from scratch and records a fresh pending fact.`,
|
|
888
893
|
));
|
|
@@ -903,6 +908,7 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
903
908
|
...(ctx.args.roles ? { roles: ctx.args.roles } : {}),
|
|
904
909
|
...(ctx.args.agent ? { agent: true } : {}),
|
|
905
910
|
...(ctx.args.sign !== undefined ? { sign: ctx.args.sign } : {}),
|
|
911
|
+
...(ctx.args.relayedBy !== undefined ? { relayedBy: ctx.args.relayedBy } : {}),
|
|
906
912
|
});
|
|
907
913
|
if (!outcome.ok) return 1;
|
|
908
914
|
|
|
@@ -910,7 +916,9 @@ export async function runApprove(ctx: CommandContext): Promise<number> {
|
|
|
910
916
|
`This records the resolution as a fact; it does not itself re-run anything. ` +
|
|
911
917
|
(opName === FAN_OUT_GATE_OP
|
|
912
918
|
? `Repeat the \`chant components fan-out\` command and it walks through gate "${gate}".`
|
|
913
|
-
: opName
|
|
919
|
+
: PR_GATE_OP.test(opName)
|
|
920
|
+
? `The pull request's apply (\`chant components pr-apply\`) walks through gate "${gate}" when it plans this same digest.`
|
|
921
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
914
922
|
? `Repeat \`chant workspace upgrade ${gate}\` with the same target, and it applies the patch this approval names.`
|
|
915
923
|
: `Run \`chant run ${opName}\` and it walks through gate "${gate}".`),
|
|
916
924
|
));
|
|
@@ -938,6 +946,13 @@ export interface GateApprovalOptions {
|
|
|
938
946
|
actor?: string;
|
|
939
947
|
/** `--note` — free-text prose. */
|
|
940
948
|
note?: string;
|
|
949
|
+
/**
|
|
950
|
+
* `--relayed-by <principal>` (#3402): who carried this approval to chant for
|
|
951
|
+
* the approver, such as a follower program relaying a person's approval.
|
|
952
|
+
* Recorded as `relayedBy` on the resolution line and covered by `--sign`.
|
|
953
|
+
* The approver is still `--actor`; the relay is not an approver.
|
|
954
|
+
*/
|
|
955
|
+
relayedBy?: string;
|
|
941
956
|
/** `--url` — the approval surface, validated as absolute http/https. */
|
|
942
957
|
url?: string;
|
|
943
958
|
/**
|
|
@@ -1101,6 +1116,16 @@ export async function recordGateApproval(
|
|
|
1101
1116
|
return { ok: false };
|
|
1102
1117
|
}
|
|
1103
1118
|
|
|
1119
|
+
// #3402: the relay is a principal, on one line (the seal's payload is LF-joined).
|
|
1120
|
+
const relayedBy = opts.relayedBy?.trim();
|
|
1121
|
+
if (opts.relayedBy !== undefined && (!relayedBy || /[\r\n]/.test(relayedBy))) {
|
|
1122
|
+
console.error(formatError({
|
|
1123
|
+
message: `--relayed-by needs one principal on one line (got ${JSON.stringify(opts.relayedBy)})`,
|
|
1124
|
+
hint: "Name who relayed the approval, such as --relayed-by github:<login> or a principal the signers file lists.",
|
|
1125
|
+
}));
|
|
1126
|
+
return { ok: false };
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1104
1129
|
const resolvedBy = opts.actor
|
|
1105
1130
|
?? (isModelAuthored(origin)
|
|
1106
1131
|
// A channel that cannot attest to a name does not get to write one down.
|
|
@@ -1126,6 +1151,7 @@ export async function recordGateApproval(
|
|
|
1126
1151
|
const scope = scopeSource(cwd);
|
|
1127
1152
|
try {
|
|
1128
1153
|
identity.refuseUnidentified(scope, [resolvedBy], opts.actor !== undefined ? "--actor" : "the approver");
|
|
1154
|
+
if (relayedBy) identity.refuseUnidentified(scope, [relayedBy], "--relayed-by");
|
|
1129
1155
|
} catch (err) {
|
|
1130
1156
|
if (!(err instanceof identity.IdentityError)) throw err;
|
|
1131
1157
|
console.error(formatError({
|
|
@@ -1158,6 +1184,7 @@ export async function recordGateApproval(
|
|
|
1158
1184
|
...(environment !== undefined ? { environment } : {}),
|
|
1159
1185
|
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1160
1186
|
resolvedBy,
|
|
1187
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1161
1188
|
timestamp,
|
|
1162
1189
|
});
|
|
1163
1190
|
} finally {
|
|
@@ -1176,6 +1203,7 @@ export async function recordGateApproval(
|
|
|
1176
1203
|
...(environment !== undefined ? { environment } : {}),
|
|
1177
1204
|
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1178
1205
|
resolvedBy,
|
|
1206
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1179
1207
|
timestamp,
|
|
1180
1208
|
seal,
|
|
1181
1209
|
});
|
|
@@ -1230,6 +1258,7 @@ export async function recordGateApproval(
|
|
|
1230
1258
|
op: opName,
|
|
1231
1259
|
gate,
|
|
1232
1260
|
resolvedBy,
|
|
1261
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1233
1262
|
timestamp,
|
|
1234
1263
|
...(seal ? { seal } : {}),
|
|
1235
1264
|
approver,
|
|
@@ -1247,7 +1276,9 @@ export async function recordGateApproval(
|
|
|
1247
1276
|
);
|
|
1248
1277
|
|
|
1249
1278
|
console.error(formatSuccess(
|
|
1250
|
-
`Gate "${gate}" on "${opName}" resolved by ${record.resolvedBy}
|
|
1279
|
+
`Gate "${gate}" on "${opName}" resolved by ${record.resolvedBy}` +
|
|
1280
|
+
(record.relayedBy ? `, relayed by ${record.relayedBy},` : "") +
|
|
1281
|
+
` at ${record.timestamp}` +
|
|
1251
1282
|
(record.url ? ` (${record.url})` : "") +
|
|
1252
1283
|
(record.seal ? `, signed with ${record.seal.key}` : "") +
|
|
1253
1284
|
(pushed ? "" : " (local only — the push did not land)"),
|