@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following a squash merge to the pull request's original commits (#3035,
|
|
3
|
+
* ws-092).
|
|
4
|
+
*
|
|
5
|
+
* A squash merge folds a pull request's commits into one new commit, and with
|
|
6
|
+
* them the trailers and signatures that joined each to its run and its work.
|
|
7
|
+
* The forge keeps the originals: GitHub and Forgejo both keep a pull
|
|
8
|
+
* request's head at `refs/pull/<n>/head` on the remote. A read that is asked
|
|
9
|
+
* to follow squashes (`--follow-squash`) looks for that ref locally, fetches
|
|
10
|
+
* the missing ones from `origin` in one `git fetch`, and keeps what it
|
|
11
|
+
* fetched under `refs/chant/pull/<n>/head`, so the next read needs no network.
|
|
12
|
+
* Without the flag nothing here runs, and a read never touches the network
|
|
13
|
+
* (studio-032 b: follow only when asked).
|
|
14
|
+
*
|
|
15
|
+
* A commit is followed as a squash when its subject ends with `(#<n>)`, as
|
|
16
|
+
* GitHub and Forgejo write a squash's subject, it has one parent, and the
|
|
17
|
+
* pull request's head is not already in its history (a merge commit or a
|
|
18
|
+
* rebase keeps the originals, so there is nothing to follow). Its original
|
|
19
|
+
* commits are the head's commits that the squash's parent does not have,
|
|
20
|
+
* oldest first. When the ref can't be read or fetched, the read says so and
|
|
21
|
+
* keeps its answer without them.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
|
|
26
|
+
/** A forge chant knows the pull request refs of. */
|
|
27
|
+
export type Forge = "github" | "forgejo";
|
|
28
|
+
|
|
29
|
+
/** Where a read keeps the pull request heads it fetched. */
|
|
30
|
+
export const PULL_CACHE_PREFIX = "refs/chant/pull/";
|
|
31
|
+
|
|
32
|
+
/** The remote a squash's pull request ref is fetched from. */
|
|
33
|
+
export const SQUASH_REMOTE = "origin";
|
|
34
|
+
|
|
35
|
+
/** How long one fetch of pull request refs may take. */
|
|
36
|
+
const FETCH_TIMEOUT_MS = 60_000;
|
|
37
|
+
|
|
38
|
+
function git(top: string, args: string[], opts: { input?: string; timeout?: number } = {}): string | undefined {
|
|
39
|
+
try {
|
|
40
|
+
return execFileSync("git", args, {
|
|
41
|
+
cwd: top,
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
input: opts.input,
|
|
44
|
+
timeout: opts.timeout,
|
|
45
|
+
stdio: [opts.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
46
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
47
|
+
// A fetch must never wait on a credential prompt.
|
|
48
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
49
|
+
});
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The pull request number a squash's subject ends with, `(#<n>)`, or null. */
|
|
56
|
+
export function pullRequestOf(subject: string): number | null {
|
|
57
|
+
const m = subject.match(/\(#([0-9]+)\)\s*$/);
|
|
58
|
+
return m ? Number(m[1]) : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The forge a remote URL points at: `github` for github.com and GitHub
|
|
63
|
+
* Enterprise hosts named for it, `forgejo` for codeberg.org and hosts named
|
|
64
|
+
* forgejo or gitea. Null for any other, which is still read at
|
|
65
|
+
* `refs/pull/<n>/head`, the ref both keep.
|
|
66
|
+
*/
|
|
67
|
+
export function forgeOf(url: string | null | undefined): Forge | null {
|
|
68
|
+
if (!url) return null;
|
|
69
|
+
const host = (url.match(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]*@)?([^/:]+)/i)?.[1] ?? url.match(/^(?:[^@/]*@)?([^/:]+):/)?.[1] ?? "").toLowerCase();
|
|
70
|
+
if (host === "github.com" || host.endsWith(".github.com") || /(^|[.-])github([.-]|$)/.test(host)) return "github";
|
|
71
|
+
if (host === "codeberg.org" || /(^|[.-])(forgejo|gitea)([.-]|$)/.test(host)) return "forgejo";
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The pull request ref a forge keeps for `n`. GitHub and Forgejo keep the same one. */
|
|
76
|
+
export function pullRef(_forge: Forge | null, n: number): string {
|
|
77
|
+
return `refs/pull/${n}/head`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What following one squash commit found. */
|
|
81
|
+
export interface SquashFollow {
|
|
82
|
+
/** The squash commit. */
|
|
83
|
+
sha: string;
|
|
84
|
+
pullRequest: number;
|
|
85
|
+
/** The forge `origin` points at, or null when it is none chant knows, or there is no `origin`. */
|
|
86
|
+
forge: Forge | null;
|
|
87
|
+
/** The local ref the pull request's head was read from, or null when none could be read. */
|
|
88
|
+
ref: string | null;
|
|
89
|
+
/** The pull request's head commit, or null. */
|
|
90
|
+
head: string | null;
|
|
91
|
+
/** Whether this read fetched the ref from the remote. */
|
|
92
|
+
fetched: boolean;
|
|
93
|
+
/** Whether the original commits were read. */
|
|
94
|
+
followed: boolean;
|
|
95
|
+
/** The original commits, oldest first. Empty when not followed. */
|
|
96
|
+
commits: string[];
|
|
97
|
+
/** Why it was not followed, when it wasn't. */
|
|
98
|
+
problem: string | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const commitOf = (top: string, rev: string): string | undefined => git(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`])?.trim() || undefined;
|
|
102
|
+
|
|
103
|
+
/** The local ref holding pull request `n`'s head: chant's cache, the forge's own ref, or a `refs/remotes/<remote>/pr/<n>` mirror. */
|
|
104
|
+
function localPullRef(top: string, forge: Forge | null, n: number): { ref: string; head: string } | undefined {
|
|
105
|
+
for (const ref of [`${PULL_CACHE_PREFIX}${n}/head`, pullRef(forge, n), `refs/remotes/${SQUASH_REMOTE}/pr/${n}`]) {
|
|
106
|
+
const head = commitOf(top, ref);
|
|
107
|
+
if (head) return { ref, head };
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Fetch each pull request's head into chant's cache, all in one fetch and then one by one if that fails. The numbers fetched. */
|
|
113
|
+
function fetchPullRefs(top: string, forge: Forge | null, numbers: number[]): Set<number> {
|
|
114
|
+
const spec = (n: number) => `+${pullRef(forge, n)}:${PULL_CACHE_PREFIX}${n}/head`;
|
|
115
|
+
const fetchRefs = (ns: number[]) => git(top, ["fetch", "--no-tags", "--quiet", "--no-write-fetch-head", SQUASH_REMOTE, ...ns.map(spec)], { timeout: FETCH_TIMEOUT_MS }) !== undefined;
|
|
116
|
+
if (numbers.length === 0) return new Set();
|
|
117
|
+
if (fetchRefs(numbers)) return new Set(numbers);
|
|
118
|
+
// One missing ref fails the whole fetch; the others may still be there.
|
|
119
|
+
return new Set(numbers.length === 1 ? [] : numbers.filter((n) => fetchRefs([n])));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Follow each squash commit among `commits` to its pull request's original
|
|
124
|
+
* commits (#3035). A commit that is not a squash (no `(#<n>)` subject, a
|
|
125
|
+
* merge, or a head already in its history) is left out of the result. With
|
|
126
|
+
* `fetchMissing`, a pull request ref missing locally is fetched from `origin`;
|
|
127
|
+
* without it, only refs already present are read.
|
|
128
|
+
*/
|
|
129
|
+
export function followSquashes(top: string, commits: { sha: string; subject: string }[], opts: { fetchMissing: boolean }): Map<string, SquashFollow> {
|
|
130
|
+
const out = new Map<string, SquashFollow>();
|
|
131
|
+
const candidates = commits.map((c) => ({ sha: c.sha, n: pullRequestOf(c.subject) })).filter((c): c is { sha: string; n: number } => c.n !== null);
|
|
132
|
+
if (candidates.length === 0) return out;
|
|
133
|
+
const parents = new Map<string, string[]>();
|
|
134
|
+
for (const line of (git(top, ["log", "--no-walk=unsorted", "--stdin", "--format=%H %P"], { input: `${candidates.map((c) => c.sha).join("\n")}\n` }) ?? "").split("\n")) {
|
|
135
|
+
const [sha, ...ps] = line.trim().split(/\s+/);
|
|
136
|
+
if (sha) parents.set(sha, ps);
|
|
137
|
+
}
|
|
138
|
+
const squashes = candidates.filter((c) => parents.get(c.sha)?.length === 1);
|
|
139
|
+
if (squashes.length === 0) return out;
|
|
140
|
+
const url = git(top, ["config", "--get", `remote.${SQUASH_REMOTE}.url`])?.trim() || null;
|
|
141
|
+
const forge = forgeOf(url);
|
|
142
|
+
const local = new Map<number, { ref: string; head: string }>();
|
|
143
|
+
for (const n of new Set(squashes.map((c) => c.n))) {
|
|
144
|
+
const hit = localPullRef(top, forge, n);
|
|
145
|
+
if (hit) local.set(n, hit);
|
|
146
|
+
}
|
|
147
|
+
const missing = [...new Set(squashes.map((c) => c.n))].filter((n) => !local.has(n));
|
|
148
|
+
const fetched = opts.fetchMissing && url ? fetchPullRefs(top, forge, missing) : new Set<number>();
|
|
149
|
+
for (const n of fetched) {
|
|
150
|
+
const head = commitOf(top, `${PULL_CACHE_PREFIX}${n}/head`);
|
|
151
|
+
if (head) local.set(n, { ref: `${PULL_CACHE_PREFIX}${n}/head`, head });
|
|
152
|
+
}
|
|
153
|
+
for (const c of squashes) {
|
|
154
|
+
const hit = local.get(c.n);
|
|
155
|
+
const base: SquashFollow = { sha: c.sha, pullRequest: c.n, forge, ref: null, head: null, fetched: false, followed: false, commits: [], problem: null };
|
|
156
|
+
if (!hit) {
|
|
157
|
+
const problem = !opts.fetchMissing
|
|
158
|
+
? `${pullRef(forge, c.n)} is not in this clone`
|
|
159
|
+
: !url
|
|
160
|
+
? `there is no ${SQUASH_REMOTE} remote to fetch ${pullRef(forge, c.n)} from`
|
|
161
|
+
: `${pullRef(forge, c.n)} could not be fetched from ${SQUASH_REMOTE}: the forge has no such ref, or it can't be reached`;
|
|
162
|
+
out.set(c.sha, { ...base, problem });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// A head already in the commit's history was merged or rebased, not squashed: nothing to follow.
|
|
166
|
+
if (git(top, ["merge-base", "--is-ancestor", hit.head, c.sha]) !== undefined) continue;
|
|
167
|
+
const parent = parents.get(c.sha)![0];
|
|
168
|
+
const list = git(top, ["rev-list", "--reverse", "--no-merges", hit.head, `^${parent}`]);
|
|
169
|
+
if (list === undefined) {
|
|
170
|
+
out.set(c.sha, { ...base, ref: hit.ref, head: hit.head, fetched: fetched.has(c.n), problem: `the commits of ${hit.ref} could not be listed` });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const originals = list.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
174
|
+
out.set(c.sha, { ...base, ref: hit.ref, head: hit.head, fetched: fetched.has(c.n), followed: true, commits: originals });
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* For the lines of `path` in a squash commit: which original commit last wrote
|
|
181
|
+
* each, by `git blame` at the pull request's head, when the file there is the
|
|
182
|
+
* same as in the squash (#3035). Undefined when it is not, since the lines
|
|
183
|
+
* then don't correspond one to one.
|
|
184
|
+
*/
|
|
185
|
+
export function squashLineOrigins(top: string, squash: string, head: string, path: string, originals: Set<string>): Map<number, string> | undefined {
|
|
186
|
+
const a = git(top, ["rev-parse", "--verify", "--quiet", `${squash}:${path}`])?.trim();
|
|
187
|
+
const b = git(top, ["rev-parse", "--verify", "--quiet", `${head}:${path}`])?.trim();
|
|
188
|
+
if (!a || a !== b) return undefined;
|
|
189
|
+
const out = git(top, ["blame", "--porcelain", head, "--", path]);
|
|
190
|
+
if (out === undefined) return undefined;
|
|
191
|
+
const lines = new Map<number, string>();
|
|
192
|
+
// Every line has a header, `<sha> <line in that commit> <line now>`, with a count after it on a group's first.
|
|
193
|
+
for (const l of out.split("\n")) {
|
|
194
|
+
const m = l.match(/^([0-9a-f]{40,64}) \d+ (\d+)(?: \d+)?$/);
|
|
195
|
+
if (m && originals.has(m[1])) lines.set(Number(m[2]), m[1]);
|
|
196
|
+
}
|
|
197
|
+
return lines;
|
|
198
|
+
}
|
|
@@ -318,6 +318,9 @@ describe("chant workspace status on built workspaces", () => {
|
|
|
318
318
|
services: [],
|
|
319
319
|
factory: null,
|
|
320
320
|
listing: null,
|
|
321
|
+
publisher: null,
|
|
322
|
+
ship: null,
|
|
323
|
+
replicate: null,
|
|
321
324
|
},
|
|
322
325
|
]);
|
|
323
326
|
});
|
|
@@ -451,7 +454,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
451
454
|
state: "approved",
|
|
452
455
|
recordedAt: "2026-09-20T00:00:00.000Z",
|
|
453
456
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
454
|
-
approvals: [{ principal: "alice", channel: "cli", at: "2026-09-20T01:00:00.000Z" }],
|
|
457
|
+
approvals: [{ principal: "alice", channel: "cli", relayedBy: null, at: "2026-09-20T01:00:00.000Z" }],
|
|
455
458
|
needed: 1,
|
|
456
459
|
signed: null,
|
|
457
460
|
approve: `chant approve web deploy --env staging --plan ${D("1")}`,
|
|
@@ -478,7 +481,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
478
481
|
recordedAt: "2026-09-20T00:00:00.000Z",
|
|
479
482
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
480
483
|
// The agent's approval doesn't count toward the quorum.
|
|
481
|
-
approvals: [{ principal: "bob", channel: "cli", at: "2026-09-20T02:00:00.000Z" }],
|
|
484
|
+
approvals: [{ principal: "bob", channel: "cli", relayedBy: null, at: "2026-09-20T02:00:00.000Z" }],
|
|
482
485
|
needed: 2,
|
|
483
486
|
signed: null,
|
|
484
487
|
approve: `chant approve web review --env staging --plan ${D("4")}`,
|
|
@@ -43,6 +43,8 @@ export interface StatusGateApproval {
|
|
|
43
43
|
principal: string;
|
|
44
44
|
/** The channel it was recorded on (`cli`, `mcp`, `acp`), or null for a record older than chant#2384. */
|
|
45
45
|
channel: string | null;
|
|
46
|
+
/** Who relayed it for `principal` (`chant approve --relayed-by`, #3402), or null when nobody did. */
|
|
47
|
+
relayedBy: string | null;
|
|
46
48
|
at: string;
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -124,7 +126,7 @@ export function approveCommand(component: string, gate: string, env: string | nu
|
|
|
124
126
|
return `chant approve ${component} ${gate}${env !== null ? ` --env ${env}` : ""}${planDigest ? ` --plan ${planDigest}` : ""}${signed ? " --sign" : ""}`;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, at: r.timestamp });
|
|
129
|
+
const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, relayedBy: r.relayedBy ?? null, at: r.timestamp });
|
|
128
130
|
|
|
129
131
|
/** One gate in one environment, decided against its newest pending fact. */
|
|
130
132
|
function decide(component: string, standing: PendingGateRecord, all: GateResolutionRecord[], now: string, rule: GateApprovalRule | null): StatusGate {
|
|
@@ -5,6 +5,50 @@
|
|
|
5
5
|
"description": "What `chant workspace status <env> --json` prints, version 1 of the read contract for releases across members (#2524 D15, D19, #2544). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`. Each member's gates, read from its gate ledger on the same branch, are in the JSON only (#2674), and so is each member's box block, from the declaration (#2726), with its isolation resolved (#2727). So are its stewards, their Ops and each Op's last run (#2731), the work lease each Op's turn holds (#2748), and the open decision points a steward waits on (#2749).",
|
|
6
6
|
"oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
|
|
7
7
|
"$defs": {
|
|
8
|
+
"boxShip": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"required": ["op", "gate", "env", "bookkeeping", "serving", "pending"],
|
|
12
|
+
"properties": {
|
|
13
|
+
"op": { "type": "string", "description": "The Op in the box member that commits the staged tree and releases it." },
|
|
14
|
+
"gate": { "type": "string", "description": "The gate the Op stops at for a person's approval." },
|
|
15
|
+
"env": { "type": "string", "description": "The environment whose release ledger, under the box member, the Op records each release in." },
|
|
16
|
+
"bookkeeping": { "type": "array", "items": { "type": "string" }, "description": "Path prefixes from the workspace root whose changes never count as waiting to ship." },
|
|
17
|
+
"serving": {
|
|
18
|
+
"description": "The latest release in env's ledger, the commit the box's site serves, or null when nothing has shipped or the ledger can't be read.",
|
|
19
|
+
"oneOf": [
|
|
20
|
+
{ "type": "null" },
|
|
21
|
+
{
|
|
22
|
+
"type": "object",
|
|
23
|
+
"additionalProperties": false,
|
|
24
|
+
"required": ["commit", "digest", "component", "at", "actor"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"commit": { "type": "string" },
|
|
27
|
+
"digest": { "type": "string" },
|
|
28
|
+
"component": { "type": "string" },
|
|
29
|
+
"at": { "type": "string" },
|
|
30
|
+
"actor": { "type": ["string", "null"] }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"pending": {
|
|
36
|
+
"description": "What is waiting to ship: the files that differ between serving.commit and the working tree, tracked or untracked and not ignored, bookkeeping left out. paths is sorted and lists at most 200. Null when nothing has shipped or the serving commit isn't in this checkout.",
|
|
37
|
+
"oneOf": [
|
|
38
|
+
{ "type": "null" },
|
|
39
|
+
{
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"required": ["files", "paths"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"files": { "type": "integer", "minimum": 0 },
|
|
45
|
+
"paths": { "type": "array", "items": { "type": "string" }, "maxItems": 200 }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
8
52
|
"result": {
|
|
9
53
|
"description": "The declaration was read. Exit code 0, even when some ledgers can't be read or members differ.",
|
|
10
54
|
"type": "object",
|
|
@@ -46,6 +90,10 @@
|
|
|
46
90
|
"baseFrom": { "enum": ["origin/HEAD", "main", "master", null], "description": "Where the target branch was found; null when there is none." }
|
|
47
91
|
}
|
|
48
92
|
},
|
|
93
|
+
"replication": {
|
|
94
|
+
"description": "Added in contract 1 by #3172. When a box block declares replicate (ws-085), where each ref it names (work branches, kept attempts, work-in-progress snapshots, the ledger branch) stands against the remote: replicated, or how many commits ahead. Read from the local refs and what chant recorded at its last push or fetch under refs/chant/replica/<remote>/; the command never fetches. chant workspace wip --json prints the same, with each branch's snapshots. null when no box declares replicate.",
|
|
95
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/replication" }]
|
|
96
|
+
},
|
|
49
97
|
"plantable": {
|
|
50
98
|
"description": "Added in contract 1 by #3146. Whether a host such as a studio's planter can plant the workspace as one box: exactly one member's box block declares services. Not a check: a records-only or infra workspace is valid and simply not plantable.",
|
|
51
99
|
"$ref": "#/$defs/plantable"
|
|
@@ -122,11 +170,24 @@
|
|
|
122
170
|
},
|
|
123
171
|
"member": {
|
|
124
172
|
"type": "object",
|
|
125
|
-
"required": ["name", "dir", "kind", "environments", "compare", "readable", "gateLedger", "gates", "box", "stewards", "stewardReasons"],
|
|
173
|
+
"required": ["name", "dir", "kind", "fields", "environments", "compare", "readable", "gateLedger", "gates", "box", "stewards", "stewardReasons"],
|
|
126
174
|
"properties": {
|
|
127
175
|
"name": { "type": "string" },
|
|
128
176
|
"dir": { "type": "string", "description": "Relative to the workspace root, with / separators; \".\" is the root member." },
|
|
129
177
|
"kind": { "type": "string" },
|
|
178
|
+
"fields": {
|
|
179
|
+
"description": "The fields the member's kind declares (#3151), each set from the entry or its default, null when it has neither. Null when the kind declares no fields or no pinned package supplies it. A value of the wrong type reads as the default, and chant workspace check reports it (WSP005). Added in contract 1 by chant 0.103.0.",
|
|
180
|
+
"type": ["object", "null"],
|
|
181
|
+
"additionalProperties": {
|
|
182
|
+
"oneOf": [
|
|
183
|
+
{ "type": "string" },
|
|
184
|
+
{ "type": "integer" },
|
|
185
|
+
{ "type": "boolean" },
|
|
186
|
+
{ "type": "null" },
|
|
187
|
+
{ "type": "object", "additionalProperties": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "boolean" }, { "type": "null" }] } }
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
},
|
|
130
191
|
"environments": {
|
|
131
192
|
"description": "env first, then compareTo when it is given and differs from env.",
|
|
132
193
|
"type": "array",
|
|
@@ -485,6 +546,41 @@
|
|
|
485
546
|
"decided_on": { "type": ["string", "null"], "description": "The day it was decided, or null while it is proposed." }
|
|
486
547
|
}
|
|
487
548
|
},
|
|
549
|
+
"replication": {
|
|
550
|
+
"type": "object",
|
|
551
|
+
"required": ["policy", "remoteConfigured", "replicated", "refs"],
|
|
552
|
+
"properties": {
|
|
553
|
+
"policy": {
|
|
554
|
+
"type": "object",
|
|
555
|
+
"required": ["box", "remote", "refs", "on", "every"],
|
|
556
|
+
"properties": {
|
|
557
|
+
"box": { "type": "string", "description": "The member whose box block declares the policy." },
|
|
558
|
+
"remote": { "type": "string" },
|
|
559
|
+
"refs": { "type": "array", "items": { "enum": ["work", "kept", "wip", "ledger"] } },
|
|
560
|
+
"on": { "type": "array", "items": { "enum": ["save", "release"] } },
|
|
561
|
+
"every": { "type": ["string", "null"] }
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
"remoteConfigured": { "type": "boolean", "description": "Whether the checkout has a git remote of the policy's name." },
|
|
565
|
+
"replicated": { "type": "boolean", "description": "True when every ref listed is on the remote as it is locally." },
|
|
566
|
+
"refs": {
|
|
567
|
+
"type": "array",
|
|
568
|
+
"description": "Every local ref the policy names, sorted by ref.",
|
|
569
|
+
"items": {
|
|
570
|
+
"type": "object",
|
|
571
|
+
"required": ["ref", "class", "commit", "replica", "replicated", "ahead"],
|
|
572
|
+
"properties": {
|
|
573
|
+
"ref": { "type": "string", "description": "Such as refs/heads/chant/work/W-001, refs/chant/kept/W-001/<token>, refs/chant/wip/chant/work/W-001 or refs/heads/chant/lifecycle." },
|
|
574
|
+
"class": { "enum": ["work", "kept", "wip", "ledger"] },
|
|
575
|
+
"commit": { "type": "string", "pattern": "^[0-9a-f]{40,64}$" },
|
|
576
|
+
"replica": { "type": ["string", "null"], "pattern": "^[0-9a-f]{40,64}$", "description": "What the remote held of the ref as chant last saw it (or, for a branch, git's remote-tracking ref), or null when unknown." },
|
|
577
|
+
"replicated": { "type": "boolean" },
|
|
578
|
+
"ahead": { "type": "integer", "minimum": 0, "description": "Commits reachable from commit that nothing known to be on the remote reaches; 0 when replicated." }
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
},
|
|
488
584
|
"box": {
|
|
489
585
|
"type": "object",
|
|
490
586
|
"required": ["capabilities", "isolation", "intent", "services"],
|
|
@@ -497,6 +593,30 @@
|
|
|
497
593
|
"description": "Added in contract 1 by #3146. What the box shows of itself on a home site, or null when the block declares no listing.",
|
|
498
594
|
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/listing" }]
|
|
499
595
|
},
|
|
596
|
+
"publisher": {
|
|
597
|
+
"description": "Added in contract 1 by #3165. The command that publishes the box's work, as the box block declares it (ws-088), or null when it names none. A surface offers a publish only when it is set, and asks for one with chant workspace box publish rather than running it.",
|
|
598
|
+
"type": ["string", "null"]
|
|
599
|
+
},
|
|
600
|
+
"ship": {
|
|
601
|
+
"description": "Added in contract 1 by ws-100. How the box ships its staged work to its own site, with defaults filled in, the release its site serves and what is waiting to ship, or null when the box block declares no ship. A surface offers Ship only when it is set: it runs chant run <op> in the member's directory with the person in CHANT_SHIP_BY and approves <gate> as for any Op.",
|
|
602
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/boxShip" }]
|
|
603
|
+
},
|
|
604
|
+
"replicate": {
|
|
605
|
+
"description": "Added in contract 1 by #3172. Where the box's work in progress is replicated (ws-085), with every default filled in, or null when the block declares no policy. At most one member's box has one; the top-level replication says how far each ref is from the remote.",
|
|
606
|
+
"oneOf": [
|
|
607
|
+
{ "type": "null" },
|
|
608
|
+
{
|
|
609
|
+
"type": "object",
|
|
610
|
+
"required": ["remote", "refs", "on", "every"],
|
|
611
|
+
"properties": {
|
|
612
|
+
"remote": { "type": "string", "description": "The git remote, by name; origin when the block names none." },
|
|
613
|
+
"refs": { "type": "array", "items": { "enum": ["work", "kept", "wip", "ledger"] }, "description": "What is replicated, in this order; all four when the block names none." },
|
|
614
|
+
"on": { "type": "array", "items": { "enum": ["save", "release"] }, "description": "When chant pushes on its own; both when the block names none." },
|
|
615
|
+
"every": { "type": ["string", "null"], "description": "The interval at which the host runs chant workspace wip push, or null." }
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
},
|
|
500
620
|
"services": {
|
|
501
621
|
"description": "The services the box block declares (#2880), in declaration order, or empty when it declares none. A reader has them before any converge tick; a steward's ConvergeOp's lastTick carries each one's verdict. cmd is as declared: its ${VAR} references are left for the process that applies it to expand. Added by chant 0.95.0.",
|
|
502
622
|
"type": "array",
|
|
@@ -561,7 +681,7 @@
|
|
|
561
681
|
},
|
|
562
682
|
"factory": {
|
|
563
683
|
"type": "object",
|
|
564
|
-
"required": ["builds", "check", "checks", "builders", "publish"],
|
|
684
|
+
"required": ["builds", "check", "checks", "builders", "tiers", "builderFor", "publish"],
|
|
565
685
|
"properties": {
|
|
566
686
|
"builds": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "The members the factory builds, in declaration order. A work item a person asks for constrains member:<the first>." },
|
|
567
687
|
"check": {
|
|
@@ -580,6 +700,8 @@
|
|
|
580
700
|
},
|
|
581
701
|
"checks": { "type": ["string", "null"], "description": "Where a work item's builder writes its check, from the workspace root, or null for the orchestrator's choice." },
|
|
582
702
|
"builders": { "type": ["string", "null"], "description": "The member that declares the builder agents, or null." },
|
|
703
|
+
"tiers": {"type": "array", "items": {"type": "object", "required": ["tier", "agent", "kinds", "session"], "properties": {"tier": {"type": "string"}, "agent": {"type": "string"}, "kinds": {"oneOf": [{"type": "null"}, {"type": "array", "items": {"type": "string"}}]}, "session": {"type": ["string", "null"]}}}, "description": "Which builder agent builds at which tier, as declared (#3152, ws-094), each { tier, agent, kinds, session } with kinds and session null when not declared; empty when none is. Added in contract 1 by chant 0.103.0."},
|
|
704
|
+
"builderFor": {"type": "object", "additionalProperties": {"type": "object", "additionalProperties": {"type": "object", "required": ["agent", "session"], "properties": {"agent": {"type": "string"}, "session": {"type": ["string", "null"]}}}}, "description": "The builder for each member the factory builds, by tier (#3152): member name, then tier, then { agent, session }. For a member the entry naming its kind is used, else the tier's entry with no kinds; a tier with neither is left out. Added in contract 1 by chant 0.103.0."},
|
|
583
705
|
"publish": {
|
|
584
706
|
"description": "Where a finished build is published, or null when the factory doesn't publish past the box.",
|
|
585
707
|
"oneOf": [
|
|
@@ -703,10 +825,11 @@
|
|
|
703
825
|
"type": "array",
|
|
704
826
|
"items": {
|
|
705
827
|
"type": "object",
|
|
706
|
-
"required": ["principal", "channel", "at"],
|
|
828
|
+
"required": ["principal", "channel", "relayedBy", "at"],
|
|
707
829
|
"properties": {
|
|
708
830
|
"principal": { "type": "string", "description": "Who recorded it." },
|
|
709
831
|
"channel": { "type": ["string", "null"], "description": "The channel it was recorded on, such as cli or mcp, or null for a record older than chant#2384." },
|
|
832
|
+
"relayedBy": { "type": ["string", "null"], "description": "Added in contract 1 by #3402. Who carried the approval to chant for principal (chant approve --relayed-by), such as a follower relaying a person's approval; a seal covers it. Null when nobody relayed it." },
|
|
710
833
|
"at": { "type": "string" }
|
|
711
834
|
}
|
|
712
835
|
}
|
package/src/workspace/status.ts
CHANGED
|
@@ -42,9 +42,11 @@ import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../li
|
|
|
42
42
|
import { readReleasePlan, type ReleasePlan } from "../lifecycle/plan-ledger";
|
|
43
43
|
import { listWorkLeases, type WorkLeaseState } from "../lifecycle/work-lease";
|
|
44
44
|
import { readCheckoutHead, type CheckoutHead } from "./records-checkout";
|
|
45
|
+
import { policyView, replicationState, type ReplicationState } from "./wip";
|
|
45
46
|
import { findWorkspaceRoot } from "../project-root";
|
|
46
47
|
import { resolveBoxes, type ResolvedIsolation } from "./box-isolation";
|
|
47
48
|
import { resolveBoxIntents, unresolvedIntent, type BoxIntent } from "./box-intent";
|
|
49
|
+
import { loadKindRegistry, resolveMemberFields, type MemberFields } from "./kinds";
|
|
48
50
|
import { factoryView, listingView, plantability, treeBytes, type FactoryView, type ListingView, type Plantable } from "./box-factory";
|
|
49
51
|
import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError, type Declaration, type ErrorLocation, type Member } from "./declaration";
|
|
50
52
|
import type { ReasonCode } from "./reason-codes";
|
|
@@ -160,6 +162,13 @@ export interface StatusMember {
|
|
|
160
162
|
name: string;
|
|
161
163
|
dir: string;
|
|
162
164
|
kind: string;
|
|
165
|
+
/**
|
|
166
|
+
* The fields the member's kind declares (#3151), each set from the entry or
|
|
167
|
+
* its default (null when it has none), or null when the kind declares no
|
|
168
|
+
* fields or no pinned package supplies it. A value of the wrong type reads
|
|
169
|
+
* as the default; `chant workspace check` reports it (WSP005).
|
|
170
|
+
*/
|
|
171
|
+
fields: MemberFields["fields"];
|
|
163
172
|
/** The environment asked for first, then the one compared to, if any. */
|
|
164
173
|
environments: StatusEnvironment[];
|
|
165
174
|
/** Null without `--compare-to`. */
|
|
@@ -178,6 +187,31 @@ export interface StatusMember {
|
|
|
178
187
|
stewardReasons: { code: StewardReasonCode; message: string }[];
|
|
179
188
|
}
|
|
180
189
|
|
|
190
|
+
/** The most paths `ship.pending.paths` lists; `files` still counts them all. */
|
|
191
|
+
export const SHIP_PENDING_PATHS_MAX = 200;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A box's ship (ws-100): the Op, gate and environment it declares, the
|
|
195
|
+
* release its site serves (the latest in that environment's release ledger
|
|
196
|
+
* under the box member), and what is waiting to ship.
|
|
197
|
+
*/
|
|
198
|
+
export interface StatusBoxShip {
|
|
199
|
+
op: string;
|
|
200
|
+
gate: string;
|
|
201
|
+
env: string;
|
|
202
|
+
bookkeeping: string[];
|
|
203
|
+
/** The latest release in `env`'s ledger: the commit the box's site serves. Null when nothing has shipped, or the ledger can't be read. */
|
|
204
|
+
serving: { commit: string; digest: string; component: string; at: string; actor: string | null } | null;
|
|
205
|
+
/**
|
|
206
|
+
* The files that differ between `serving.commit` and the working tree
|
|
207
|
+
* (tracked changes, committed or not, and untracked files git does not
|
|
208
|
+
* ignore), bookkeeping left out; `paths` sorted, at most
|
|
209
|
+
* SHIP_PENDING_PATHS_MAX of them. Null when nothing has shipped or the
|
|
210
|
+
* serving commit isn't in this checkout.
|
|
211
|
+
*/
|
|
212
|
+
pending: { files: number; paths: string[] } | null;
|
|
213
|
+
}
|
|
214
|
+
|
|
181
215
|
/** A box's brokered capabilities, from the declaration (#2726). */
|
|
182
216
|
export interface StatusBox {
|
|
183
217
|
capabilities: { name: string; broker: string | null; scope: string[] }[];
|
|
@@ -196,6 +230,12 @@ export interface StatusBox {
|
|
|
196
230
|
factory: FactoryView | null;
|
|
197
231
|
/** What the box shows of itself on a home site, with the cover's sha256 (#3146), or null when the block declares no listing. */
|
|
198
232
|
listing: ListingView | null;
|
|
233
|
+
/** The command that publishes the box's work, as declared (#3165, ws-088), or null when the block names none. */
|
|
234
|
+
publisher: string | null;
|
|
235
|
+
/** How the box ships its staged work, what its site serves and what is waiting (ws-100), or null when the block declares no ship. */
|
|
236
|
+
ship: StatusBoxShip | null;
|
|
237
|
+
/** Where the box's work in progress is replicated, with defaults filled in (#3172, ws-085), or null when the block declares no policy. */
|
|
238
|
+
replicate: { remote: string; refs: string[]; on: string[]; every: string | null } | null;
|
|
199
239
|
}
|
|
200
240
|
|
|
201
241
|
/** A declared box service as `status --json` prints it (#2880): every field present, null or false when not declared. */
|
|
@@ -252,6 +292,8 @@ export type StatusDocument =
|
|
|
252
292
|
leases: StatusLease[];
|
|
253
293
|
/** Each work item's acceptance criteria, met of total, by declared kind then item (#2772). Empty when no declared work kind has criteria. */
|
|
254
294
|
acceptance: StatusAcceptance[];
|
|
295
|
+
/** How far each ref the box's replicate policy names is from its remote (#3172), read locally; null when no box declares one. */
|
|
296
|
+
replication: ReplicationState | null;
|
|
255
297
|
summary: { members: number; released: number; unreadable: number; differing: number | null };
|
|
256
298
|
}
|
|
257
299
|
| {
|
|
@@ -321,6 +363,40 @@ async function ledgerFor(member: Member, env: string, cwd: string): Promise<Omit
|
|
|
321
363
|
return { layout: "flat", path: `${env}/releases.jsonl` };
|
|
322
364
|
}
|
|
323
365
|
|
|
366
|
+
/** A box's ship as status prints it (ws-100). Never throws: what can't be read is null. */
|
|
367
|
+
async function shipView(member: Member, cwd: string, read: LedgerReader): Promise<StatusBoxShip | null> {
|
|
368
|
+
const ship = member.box?.ship;
|
|
369
|
+
if (!ship) return null;
|
|
370
|
+
const view: StatusBoxShip = { op: ship.op, gate: ship.gate, env: ship.env, bookkeeping: [...ship.bookkeeping], serving: null, pending: null };
|
|
371
|
+
let records: ReleaseRecord[] = [];
|
|
372
|
+
try {
|
|
373
|
+
records = (await read((await ledgerFor(member, ship.env, cwd)).path, cwd)).records;
|
|
374
|
+
} catch {
|
|
375
|
+
return view;
|
|
376
|
+
}
|
|
377
|
+
const last = records[records.length - 1];
|
|
378
|
+
if (!last) return view;
|
|
379
|
+
view.serving = { commit: last.gitSha, digest: last.digest, component: last.component, at: last.timestamp, actor: last.actor ?? null };
|
|
380
|
+
const top = gitTop(cwd);
|
|
381
|
+
if (!top) return view;
|
|
382
|
+
const lines = (args: string[]): string[] | null => {
|
|
383
|
+
try {
|
|
384
|
+
return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 }).split("\0").filter(Boolean);
|
|
385
|
+
} catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
// The commit must be here: a release from a commit this checkout never had says nothing about it.
|
|
390
|
+
if (lines(["cat-file", "-e", `${last.gitSha}^{commit}`]) === null) return view;
|
|
391
|
+
const changed = lines(["diff", "--name-only", "-z", "--no-renames", last.gitSha, "--"]);
|
|
392
|
+
const untracked = lines(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
393
|
+
if (changed === null || untracked === null) return view;
|
|
394
|
+
const bookkeeping = (path: string) => ship.bookkeeping.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
|
|
395
|
+
const paths = [...new Set([...changed, ...untracked])].filter((path) => !bookkeeping(path)).sort();
|
|
396
|
+
view.pending = { files: paths.length, paths: paths.slice(0, SHIP_PENDING_PATHS_MAX) };
|
|
397
|
+
return view;
|
|
398
|
+
}
|
|
399
|
+
|
|
324
400
|
const defaultReader: LedgerReader = (path, cwd) => readReleaseLedger(path.slice(0, -"/releases.jsonl".length), { cwd });
|
|
325
401
|
|
|
326
402
|
async function readEnvironment(member: Member, env: string, cwd: string, read: LedgerReader): Promise<StatusEnvironment> {
|
|
@@ -412,6 +488,8 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
412
488
|
const isolation = new Map(resolveBoxes(declaration).map((b) => [b.member.name, b.isolation]));
|
|
413
489
|
const intents = new Map((await resolveBoxIntents(declaration, found.dir)).map((i) => [i.member, i.record?.intent ?? unresolvedIntent(i.id)]));
|
|
414
490
|
const coverBytes = treeBytes(workingTree(found.dir));
|
|
491
|
+
// The kinds the pins supply (#3151), for each member's fields. A pin that can't be read is check's to report (WSP002).
|
|
492
|
+
const kinds = loadKindRegistry(declaration.pins, found.dir).registry;
|
|
415
493
|
// #3163: the declaration's identity.gates at base, read once for every member's gates.
|
|
416
494
|
const ruleSource = scopeSource(found.dir);
|
|
417
495
|
const rules = (gate: string) => gateAdmissionFrom(ruleSource, gate);
|
|
@@ -426,6 +504,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
426
504
|
name: m.name,
|
|
427
505
|
dir: m.dir,
|
|
428
506
|
kind: m.kind,
|
|
507
|
+
fields: resolveMemberFields(kinds.get(m.kind), m.fields).fields,
|
|
429
508
|
environments,
|
|
430
509
|
compare: query.compareTo === undefined ? null : compareEnvironments(environments[0], environments[environments.length - 1]),
|
|
431
510
|
readable: environments.every((e) => e.reason === null),
|
|
@@ -447,8 +526,11 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
447
526
|
health: s.health,
|
|
448
527
|
optional: s.optional,
|
|
449
528
|
})),
|
|
450
|
-
factory: factoryView(m.box.factory),
|
|
529
|
+
factory: factoryView(m.box.factory, declaration.members),
|
|
451
530
|
listing: listingView(m.box.listing, coverBytes),
|
|
531
|
+
publisher: m.box.publisher,
|
|
532
|
+
ship: await shipView(m, found.dir, read),
|
|
533
|
+
replicate: m.box.replicate === null ? null : (({ box: _box, ...rest }) => rest)(policyView(m.name, m.box.replicate)),
|
|
452
534
|
},
|
|
453
535
|
stewards: stewards.stewards,
|
|
454
536
|
stewardReasons: stewards.reasons,
|
|
@@ -475,6 +557,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
475
557
|
members,
|
|
476
558
|
leases,
|
|
477
559
|
acceptance,
|
|
560
|
+
replication: replicationOf(declaration, top),
|
|
478
561
|
summary: {
|
|
479
562
|
members: members.length,
|
|
480
563
|
released: members.filter((m) => m.environments[0].releases.length > 0).length,
|
|
@@ -631,3 +714,9 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
|
|
|
631
714
|
);
|
|
632
715
|
return lines.join("\n");
|
|
633
716
|
}
|
|
717
|
+
|
|
718
|
+
/** Where the box's work in progress stands against its remote (#3172), or null when no box block declares replicate. */
|
|
719
|
+
function replicationOf(declaration: Declaration, top: string): ReplicationState | null {
|
|
720
|
+
const m = declaration.members.find((x) => x.box?.replicate);
|
|
721
|
+
return m ? replicationState(top, policyView(m.name, m.box!.replicate!)) : null;
|
|
722
|
+
}
|