@intentius/chant 0.124.28 → 0.124.29
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/build-params.d.ts +34 -1
- package/dist/build-params.d.ts.map +1 -1
- package/dist/build-params.js +31 -1
- package/dist/change-set-graph.d.ts +8 -1
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +4 -2
- package/dist/change-set.d.ts +10 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +4 -3
- package/dist/cli/build-params-cli.d.ts +7 -1
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/build-params-cli.js +17 -8
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -3
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +45 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +15 -4
- package/dist/cli/handlers/fan-out.d.ts +9 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +40 -3
- package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
- package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-gc.js +143 -0
- package/dist/cli/handlers/pr.d.ts +11 -3
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +134 -26
- package/dist/cli/handlers/promote.d.ts +7 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +210 -33
- package/dist/cli/handlers/release.d.ts +62 -5
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +414 -28
- package/dist/cli/handlers/run.d.ts +11 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +67 -6
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +61 -10
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +24 -2
- package/dist/cli/registry.d.ts +15 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +3 -2
- package/dist/cli/run-events.d.ts.map +1 -1
- package/dist/cli/run-events.js +5 -3
- package/dist/components/auto-release.d.ts +2 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +1 -0
- package/dist/components/capability.d.ts +9 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts +8 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +35 -0
- package/dist/components/fan-out-inputs.d.ts +69 -0
- package/dist/components/fan-out-inputs.d.ts.map +1 -0
- package/dist/components/fan-out-inputs.js +169 -0
- package/dist/components/fan-out-record.d.ts +13 -0
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +16 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +50 -0
- package/dist/components/fan-out.d.ts +4 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/pr-pipeline.d.ts +25 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +22 -4
- package/dist/components/promote.d.ts +23 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +60 -2
- package/dist/components/release-compile.d.ts +9 -0
- package/dist/components/release-compile.d.ts.map +1 -1
- package/dist/components/release-compile.js +9 -3
- package/dist/components/release-gate.d.ts +11 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +10 -4
- package/dist/components/wave-plan.d.ts +5 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +10 -2
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/flag-params.d.ts +54 -0
- package/dist/flag-params.d.ts.map +1 -0
- package/dist/flag-params.js +130 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
- package/dist/lifecycle/change-set-store.d.ts +2 -1
- package/dist/lifecycle/change-set-store.d.ts.map +1 -1
- package/dist/lifecycle/change-set-store.js +2 -1
- package/dist/lifecycle/delivery-metrics.d.ts +139 -0
- package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
- package/dist/lifecycle/delivery-metrics.js +145 -0
- package/dist/lifecycle/deploy-outcome.d.ts +81 -0
- package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
- package/dist/lifecycle/deploy-outcome.js +101 -0
- package/dist/lifecycle/gate-change-set.d.ts +10 -1
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
- package/dist/lifecycle/gate-change-set.js +35 -3
- package/dist/lifecycle/gate-ledger.d.ts +2 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +30 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +31 -5
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/mixed-environment.d.ts +95 -0
- package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
- package/dist/lifecycle/mixed-environment.js +154 -0
- package/dist/lifecycle/publish-attachments.d.ts +79 -0
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
- package/dist/lifecycle/publish-attachments.js +179 -0
- package/dist/lifecycle/publish-credentials.d.ts +37 -0
- package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
- package/dist/lifecycle/publish-credentials.js +69 -0
- package/dist/lifecycle/publish-declaration.d.ts +97 -0
- package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
- package/dist/lifecycle/publish-declaration.js +83 -0
- package/dist/lifecycle/publish-forge.d.ts +53 -0
- package/dist/lifecycle/publish-forge.d.ts.map +1 -0
- package/dist/lifecycle/publish-forge.js +298 -0
- package/dist/lifecycle/publish-npm.d.ts +61 -0
- package/dist/lifecycle/publish-npm.d.ts.map +1 -0
- package/dist/lifecycle/publish-npm.js +242 -0
- package/dist/lifecycle/publish-oci.d.ts +64 -0
- package/dist/lifecycle/publish-oci.d.ts.map +1 -0
- package/dist/lifecycle/publish-oci.js +383 -0
- package/dist/lifecycle/publish-record.d.ts +66 -0
- package/dist/lifecycle/publish-record.d.ts.map +1 -0
- package/dist/lifecycle/publish-record.js +84 -0
- package/dist/lifecycle/publish-targets.d.ts +45 -0
- package/dist/lifecycle/publish-targets.d.ts.map +1 -0
- package/dist/lifecycle/publish-targets.js +85 -0
- package/dist/lifecycle/release-cut.d.ts +48 -1
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +116 -23
- package/dist/lifecycle/release-join.d.ts +95 -0
- package/dist/lifecycle/release-join.d.ts.map +1 -0
- package/dist/lifecycle/release-join.js +139 -0
- package/dist/lifecycle/release-publish.d.ts +312 -0
- package/dist/lifecycle/release-publish.d.ts.map +1 -0
- package/dist/lifecycle/release-publish.js +335 -0
- package/dist/lifecycle/release-revocation.d.ts +80 -0
- package/dist/lifecycle/release-revocation.d.ts.map +1 -0
- package/dist/lifecycle/release-revocation.js +123 -0
- package/dist/lifecycle/release-store.d.ts +12 -2
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +4 -2
- package/dist/lifecycle/release-verify.d.ts +61 -0
- package/dist/lifecycle/release-verify.d.ts.map +1 -0
- package/dist/lifecycle/release-verify.js +92 -0
- package/dist/lifecycle/release-workstation.d.ts +91 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -0
- package/dist/lifecycle/release-workstation.js +180 -0
- package/dist/lifecycle/retention-policy.d.ts +45 -0
- package/dist/lifecycle/retention-policy.d.ts.map +1 -0
- package/dist/lifecycle/retention-policy.js +43 -0
- package/dist/lifecycle/retention.d.ts +185 -0
- package/dist/lifecycle/retention.d.ts.map +1 -0
- package/dist/lifecycle/retention.js +400 -0
- package/dist/plan-summary.d.ts.map +1 -1
- package/dist/plan-summary.js +8 -2
- package/dist/pr-loop.d.ts +7 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +23 -4
- package/dist/pr-report-url.d.ts +82 -0
- package/dist/pr-report-url.d.ts.map +1 -0
- package/dist/pr-report-url.js +174 -0
- package/dist/provenance.d.ts +2 -2
- package/dist/provenance.d.ts.map +1 -1
- package/dist/run-event-store.d.ts +29 -2
- package/dist/run-event-store.d.ts.map +1 -1
- package/dist/run-event-store.js +50 -3
- package/dist/run-event-stream.d.ts +2 -1
- package/dist/run-event-stream.d.ts.map +1 -1
- package/dist/run-event-stream.js +5 -1
- package/dist/run-event.d.ts +19 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/run-event.js +2 -0
- package/dist/wave-runtime.mjs +260 -229
- package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
- package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
- package/dist/workspace/change-set.schema.json +5 -1
- package/dist/workspace/check.schema.json +4 -2
- package/dist/workspace/checks/records.d.ts +9 -1
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks/records.js +35 -1
- package/dist/workspace/checks.d.ts +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +2 -1
- package/dist/workspace/ci-green.d.ts +8 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +10 -3
- package/dist/workspace/declaration.d.ts +16 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +26 -0
- package/dist/workspace/declaration.schema.json +64 -0
- package/dist/workspace/declared-kinds.d.ts +16 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/declared-kinds.js +25 -0
- package/dist/workspace/delivery-metrics.d.ts +91 -0
- package/dist/workspace/delivery-metrics.d.ts.map +1 -0
- package/dist/workspace/delivery-metrics.js +217 -0
- package/dist/workspace/delivery-metrics.schema.json +519 -0
- package/dist/workspace/flags-read.d.ts +37 -0
- package/dist/workspace/flags-read.d.ts.map +1 -0
- package/dist/workspace/flags-read.js +103 -0
- package/dist/workspace/flags.d.ts +100 -0
- package/dist/workspace/flags.d.ts.map +1 -0
- package/dist/workspace/flags.js +98 -0
- package/dist/workspace/graph-cli.js +1 -1
- package/dist/workspace/intent-cli.d.ts +11 -2
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.js +54 -5
- package/dist/workspace/intent-trace.d.ts +219 -0
- package/dist/workspace/intent-trace.d.ts.map +1 -0
- package/dist/workspace/intent-trace.js +314 -0
- package/dist/workspace/intent.d.ts +30 -6
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +141 -21
- package/dist/workspace/intent.schema.json +702 -6
- package/dist/workspace/overlap.d.ts +146 -0
- package/dist/workspace/overlap.d.ts.map +1 -0
- package/dist/workspace/overlap.js +276 -0
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +3 -0
- package/dist/workspace/records.d.ts +15 -2
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/records.js +32 -0
- package/dist/workspace/records.schema.json +21 -2
- package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/dist/workspace/releases.d.ts +9 -0
- package/dist/workspace/releases.d.ts.map +1 -1
- package/dist/workspace/releases.js +9 -0
- package/dist/workspace/releases.schema.json +123 -1
- package/dist/workspace/run-event.schema.json +10 -3
- package/dist/workspace/status-gates.d.ts +8 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +78 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +170 -3
- package/dist/workspace/status.schema.json +198 -1
- package/dist/workspace/wip.d.ts.map +1 -1
- package/dist/workspace/wip.js +11 -3
- package/package.json +1 -1
- package/src/build-params.test.ts +46 -0
- package/src/build-params.ts +55 -1
- package/src/change-set-graph.ts +10 -2
- package/src/change-set.ts +14 -3
- package/src/cli/build-params-cli.ts +22 -9
- package/src/cli/commands/build.ts +6 -3
- package/src/cli/handlers/approve-release.test.ts +5 -1
- package/src/cli/handlers/approve-revoked.test.ts +155 -0
- package/src/cli/handlers/approve.ts +47 -0
- package/src/cli/handlers/build.test.ts +15 -0
- package/src/cli/handlers/build.ts +17 -4
- package/src/cli/handlers/fan-out.test.ts +180 -1
- package/src/cli/handlers/fan-out.ts +38 -3
- package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
- package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
- package/src/cli/handlers/lifecycle-gc.ts +146 -0
- package/src/cli/handlers/pr.test.ts +148 -1
- package/src/cli/handlers/pr.ts +139 -28
- package/src/cli/handlers/promote.test.ts +280 -2
- package/src/cli/handlers/promote.ts +210 -34
- package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
- package/src/cli/handlers/release-join.e2e.test.ts +185 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
- package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
- package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
- package/src/cli/handlers/release.e2e.test.ts +146 -32
- package/src/cli/handlers/release.ts +415 -28
- package/src/cli/handlers/run-auto-release.test.ts +9 -0
- package/src/cli/handlers/run-release.test.ts +55 -0
- package/src/cli/handlers/run.ts +69 -5
- package/src/cli/main.test.ts +1 -0
- package/src/cli/main.ts +61 -10
- package/src/cli/parse-args.ts +19 -2
- package/src/cli/registry.ts +15 -3
- package/src/cli/run-events.ts +5 -2
- package/src/components/auto-release.test.ts +9 -0
- package/src/components/auto-release.ts +3 -0
- package/src/components/capability.ts +9 -0
- package/src/components/driver.test.ts +24 -0
- package/src/components/driver.ts +36 -0
- package/src/components/fan-out-inputs.test.ts +85 -0
- package/src/components/fan-out-inputs.ts +192 -0
- package/src/components/fan-out-record.test.ts +10 -0
- package/src/components/fan-out-record.ts +19 -0
- package/src/components/fan-out-run.ts +48 -0
- package/src/components/fan-out.ts +4 -0
- package/src/components/pr-pipeline.ts +35 -4
- package/src/components/promote.ts +68 -2
- package/src/components/release-compile.ts +17 -3
- package/src/components/release-gate.ts +18 -4
- package/src/components/wave-plan.ts +15 -2
- package/src/config.ts +8 -0
- package/src/flag-params.test.ts +144 -0
- package/src/flag-params.ts +134 -0
- package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
- package/src/lifecycle/change-set-store.ts +2 -1
- package/src/lifecycle/delivery-metrics.ts +263 -0
- package/src/lifecycle/deploy-outcome.ts +147 -0
- package/src/lifecycle/gate-change-set.ts +37 -6
- package/src/lifecycle/gate-ledger.ts +2 -2
- package/src/lifecycle/git.ts +61 -8
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/mixed-environment.test.ts +97 -0
- package/src/lifecycle/mixed-environment.ts +204 -0
- package/src/lifecycle/publish-attachments.ts +196 -0
- package/src/lifecycle/publish-credentials.ts +84 -0
- package/src/lifecycle/publish-declaration.ts +95 -0
- package/src/lifecycle/publish-forge.ts +325 -0
- package/src/lifecycle/publish-npm.ts +259 -0
- package/src/lifecycle/publish-oci.ts +396 -0
- package/src/lifecycle/publish-record.ts +113 -0
- package/src/lifecycle/publish-resume.test.ts +168 -0
- package/src/lifecycle/publish-targets.test.ts +494 -0
- package/src/lifecycle/publish-targets.ts +100 -0
- package/src/lifecycle/release-cut.test.ts +74 -2
- package/src/lifecycle/release-cut.ts +158 -22
- package/src/lifecycle/release-flag.test.ts +112 -0
- package/src/lifecycle/release-join.test.ts +68 -0
- package/src/lifecycle/release-join.ts +182 -0
- package/src/lifecycle/release-publish.test.ts +225 -0
- package/src/lifecycle/release-publish.ts +568 -0
- package/src/lifecycle/release-revocation.ts +147 -0
- package/src/lifecycle/release-store.ts +15 -4
- package/src/lifecycle/release-verify.test.ts +110 -0
- package/src/lifecycle/release-verify.ts +120 -0
- package/src/lifecycle/release-workstation.test.ts +204 -0
- package/src/lifecycle/release-workstation.ts +216 -0
- package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
- package/src/lifecycle/retention-policy.ts +63 -0
- package/src/lifecycle/retention.test.ts +363 -0
- package/src/lifecycle/retention.ts +508 -0
- package/src/plan-summary.test.ts +20 -0
- package/src/plan-summary.ts +8 -1
- package/src/pr-loop.ts +25 -4
- package/src/pr-report-url.test.ts +131 -0
- package/src/pr-report-url.ts +209 -0
- package/src/provenance.ts +2 -2
- package/src/run-event-store.ts +63 -3
- package/src/run-event-stream.ts +4 -1
- package/src/run-event.ts +21 -1
- package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
- package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
- package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
- package/src/workspace/change-set.schema.json +5 -1
- package/src/workspace/check.schema.json +4 -2
- package/src/workspace/checks/records.ts +35 -1
- package/src/workspace/checks.ts +2 -1
- package/src/workspace/ci-green.ts +14 -3
- package/src/workspace/declaration.schema.json +64 -0
- package/src/workspace/declaration.test.ts +28 -0
- package/src/workspace/declaration.ts +38 -0
- package/src/workspace/declared-kinds.ts +40 -1
- package/src/workspace/delivery-metrics.schema.json +519 -0
- package/src/workspace/delivery-metrics.test.ts +236 -0
- package/src/workspace/delivery-metrics.ts +286 -0
- package/src/workspace/flags-read.ts +121 -0
- package/src/workspace/flags.test.ts +141 -0
- package/src/workspace/flags.ts +157 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/intent-cli.ts +46 -5
- package/src/workspace/intent-trace.test.ts +173 -0
- package/src/workspace/intent-trace.ts +484 -0
- package/src/workspace/intent.schema.json +702 -6
- package/src/workspace/intent.ts +148 -25
- package/src/workspace/older-records.test.ts +412 -0
- package/src/workspace/overlap.test.ts +165 -0
- package/src/workspace/overlap.ts +340 -0
- package/src/workspace/read-contract.e2e.test.ts +2 -1
- package/src/workspace/reason-codes.test.ts +5 -0
- package/src/workspace/reason-codes.ts +3 -0
- package/src/workspace/record-assets.test.ts +2 -1
- package/src/workspace/records-contract.e2e.test.ts +2 -1
- package/src/workspace/records.schema.json +21 -2
- package/src/workspace/records.ts +36 -2
- package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/src/workspace/releases.schema.json +123 -1
- package/src/workspace/releases.ts +11 -0
- package/src/workspace/run-event.schema.json +10 -3
- package/src/workspace/status-contract.test.ts +29 -0
- package/src/workspace/status-gates.ts +8 -0
- package/src/workspace/status-mixed.test.ts +152 -0
- package/src/workspace/status-revoked.test.ts +153 -0
- package/src/workspace/status.schema.json +198 -1
- package/src/workspace/status.ts +254 -3
- package/src/workspace/wip.ts +11 -2
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlap between in-flight work items (#4025, policy from #4016).
|
|
3
|
+
*
|
|
4
|
+
* A work lease locks one item, and nothing compares two items with each
|
|
5
|
+
* other. This report does, after the fact: it says which in-flight work items
|
|
6
|
+
* changed the same files, and which entries of a planned change set more than
|
|
7
|
+
* one of them reaches. It never blocks anything. Two people or agents read it
|
|
8
|
+
* and sort the overlap out between them.
|
|
9
|
+
*
|
|
10
|
+
* ## In flight
|
|
11
|
+
*
|
|
12
|
+
* A work item is a current record of a declared work kind. It is in flight
|
|
13
|
+
* unless it implements at least one decision and every decision it implements
|
|
14
|
+
* is confirmed, as #4024 defines confirmed ({@link decisionConfirmed}: in one
|
|
15
|
+
* of the decision kind's closed states and superseded by nothing). The item's
|
|
16
|
+
* own state does not matter: work marked done whose decision is not settled
|
|
17
|
+
* can still collide.
|
|
18
|
+
*
|
|
19
|
+
* ## Linked commits
|
|
20
|
+
*
|
|
21
|
+
* The commits of a work item are the commits linked to it as #4029 defines
|
|
22
|
+
* linked ({@link judgeCommits}): a `Chant-Record: <work kind>:<id>` trailer,
|
|
23
|
+
* or a `Chant-Lease` trailer that resolves to the item. A window match does
|
|
24
|
+
* not link. The walk covers every local branch, every remote-tracking branch
|
|
25
|
+
* and HEAD, so work pushed to a branch and fetched shows before it is merged.
|
|
26
|
+
* The ledger branches under `chant/` hold no work and are left out. Merge
|
|
27
|
+
* commits carry no work of their own and are skipped. Nothing is fetched.
|
|
28
|
+
*
|
|
29
|
+
* ## Files
|
|
30
|
+
*
|
|
31
|
+
* An item's files are the paths its linked commits add, change or delete,
|
|
32
|
+
* from the repository root, with renames read as a delete and an add. Two
|
|
33
|
+
* items overlap by file when both touch one path.
|
|
34
|
+
*
|
|
35
|
+
* ## Change-set entries
|
|
36
|
+
*
|
|
37
|
+
* A change set is planned from many commits, and a release bundles many work
|
|
38
|
+
* items, so an entry belongs to no single item on its face. An entry is
|
|
39
|
+
* attributed to a work item when:
|
|
40
|
+
*
|
|
41
|
+
* 1. the entry has a `source`: the file that declares its graph node, from
|
|
42
|
+
* the repository root, recorded when the change set was stored
|
|
43
|
+
* (`../change-set-graph.ts`);
|
|
44
|
+
* 2. a commit linked to the item changed that file; and
|
|
45
|
+
* 3. that commit is in the history the change set was planned from: an
|
|
46
|
+
* ancestor of (or equal to) the commit its graph was read at, else the
|
|
47
|
+
* commit the gate was reached at. When neither is known, or the commit is
|
|
48
|
+
* not in this repository, the third test is skipped.
|
|
49
|
+
*
|
|
50
|
+
* An entry can be attributed to several items, and two items overlap by
|
|
51
|
+
* address when one entry is attributed to both. An entry with no `source`
|
|
52
|
+
* (stored by an older chant, mapped onto no node, or a node with no source
|
|
53
|
+
* location) is attributed to none and counted in `unattributed`.
|
|
54
|
+
*
|
|
55
|
+
* The change sets read are the ones `workspace status` lists on a gate that
|
|
56
|
+
* is pending or approved: the plans still waiting to apply.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
import { execFileSync } from "node:child_process";
|
|
60
|
+
import { dirname } from "node:path";
|
|
61
|
+
import type { ChangeSetDocument } from "../change-set";
|
|
62
|
+
import { judgeCommits, hasLeaseLedger, type WorkIndex } from "./commit-links";
|
|
63
|
+
import { declaredRecordKinds, type Declaration } from "./declaration";
|
|
64
|
+
import { declaredKindFile } from "./declared-kinds";
|
|
65
|
+
import { decisionConfirmed } from "./flags";
|
|
66
|
+
import { loadRecordKind } from "./records";
|
|
67
|
+
import { readRecordsFor } from "./records-cli";
|
|
68
|
+
import { LEASE_TRAILER, RECORD_TRAILER } from "./trailers";
|
|
69
|
+
|
|
70
|
+
/** One in-flight work item with at least one linked commit. */
|
|
71
|
+
export interface OverlapItem {
|
|
72
|
+
/** `<work kind>:<id>`, as a `Chant-Record` trailer names it. */
|
|
73
|
+
item: string;
|
|
74
|
+
/** The work record file, from the repository root. */
|
|
75
|
+
path: string;
|
|
76
|
+
state: string | null;
|
|
77
|
+
/** The commits linked to the item, newest first. */
|
|
78
|
+
commits: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** One change-set entry two items both reach. */
|
|
82
|
+
export interface OverlapAddress {
|
|
83
|
+
/** The change set's digest, as `chant change-set show` takes it. */
|
|
84
|
+
changeSet: string;
|
|
85
|
+
/** The workspace member whose ledger stores the change set, or null for the flat ledger. */
|
|
86
|
+
ledgerMember: string | null;
|
|
87
|
+
/** The entry's change-set member and planner address. */
|
|
88
|
+
member: string;
|
|
89
|
+
address: string;
|
|
90
|
+
/** The entry's graph node, or null. */
|
|
91
|
+
node: string | null;
|
|
92
|
+
/** The source file the attribution went through, from the repository root. */
|
|
93
|
+
source: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Two in-flight work items that changed the same thing. */
|
|
97
|
+
export interface OverlapPair {
|
|
98
|
+
/** The two items, `<work kind>:<id>`, in sorted order. */
|
|
99
|
+
items: [string, string];
|
|
100
|
+
/** The files both items' linked commits touch, sorted. */
|
|
101
|
+
files: string[];
|
|
102
|
+
/** The change-set entries attributed to both items, by change set then address. */
|
|
103
|
+
addresses: OverlapAddress[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** What `workspace status` reports as `overlap`. */
|
|
107
|
+
export interface OverlapReport {
|
|
108
|
+
/** The in-flight work items with a linked commit, by item. */
|
|
109
|
+
items: OverlapItem[];
|
|
110
|
+
/** Each pair of items that share a file or an entry, by the first item then the second. */
|
|
111
|
+
pairs: OverlapPair[];
|
|
112
|
+
/** The digests of the change sets read, sorted. */
|
|
113
|
+
changeSets: string[];
|
|
114
|
+
/** Entries of those change sets with no `source`, so attributed to no item. */
|
|
115
|
+
unattributed: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A change set to attribute: one a pending or approved gate binds. */
|
|
119
|
+
export interface OverlapChangeSetRef {
|
|
120
|
+
digest: string;
|
|
121
|
+
/** A directory whose ledger stores it: the member's directory. */
|
|
122
|
+
cwd: string;
|
|
123
|
+
ledgerMember: string | null;
|
|
124
|
+
/** The commit the gate was reached at, when the pending fact records one. */
|
|
125
|
+
gitSha: string | null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Reads a stored change set; the lifecycle store unless a test swaps it. */
|
|
129
|
+
export type StoredChangeSetReader = (digest: string, cwd: string) => Promise<ChangeSetDocument | null>;
|
|
130
|
+
|
|
131
|
+
const defaultReadChangeSet: StoredChangeSetReader = async (digest, cwd) => {
|
|
132
|
+
const { readStoredChangeSet } = await import("../lifecycle/change-set-store");
|
|
133
|
+
const read = await readStoredChangeSet(digest, { cwd });
|
|
134
|
+
return read.status === "found" ? read.document : null;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function git(top: string, args: string[], input?: string): string {
|
|
138
|
+
return execFileSync("git", ["-c", "core.quotepath=off", ...args], {
|
|
139
|
+
cwd: top,
|
|
140
|
+
encoding: "utf-8",
|
|
141
|
+
stdio: [input === undefined ? "ignore" : "pipe", "pipe", "ignore"],
|
|
142
|
+
maxBuffer: 512 * 1024 * 1024,
|
|
143
|
+
...(input !== undefined ? { input } : {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The work items of every declared work kind, with the index commits are judged against and the ones in flight. */
|
|
148
|
+
async function readWorkItems(declaration: Declaration, root: string, top: string): Promise<{ index: WorkIndex; inFlight: Map<string, { path: string; state: string | null }> } | null> {
|
|
149
|
+
const kinds = new Map<string, Set<string>>();
|
|
150
|
+
const inFlight = new Map<string, { path: string; state: string | null }>();
|
|
151
|
+
for (const d of declaredRecordKinds(declaration)) {
|
|
152
|
+
const file = declaredKindFile(d, root);
|
|
153
|
+
const loaded = await loadRecordKind(file, root);
|
|
154
|
+
if (!loaded.kind.work) continue;
|
|
155
|
+
const decisionKind = await loadRecordKind(loaded.kind.work.decisions, dirname(loaded.file));
|
|
156
|
+
const closed = new Set(decisionKind.kind.closedStates ?? []);
|
|
157
|
+
const read = await readRecordsFor({ kind: file, cwd: root });
|
|
158
|
+
const decisions = new Map((read.result.decisions ?? []).map((x) => [x.id, x]));
|
|
159
|
+
const ids = kinds.get(loaded.kind.name) ?? new Set<string>();
|
|
160
|
+
for (const r of read.result.records) {
|
|
161
|
+
if (r.id === null) continue;
|
|
162
|
+
ids.add(r.id);
|
|
163
|
+
if (r.supersededBy !== null) continue;
|
|
164
|
+
const implemented = r.implements ?? [];
|
|
165
|
+
const settled = implemented.length > 0 && implemented.every((x) => decisionConfirmed(decisions.get(x.id), closed));
|
|
166
|
+
const key = `${loaded.kind.name}:${r.id}`;
|
|
167
|
+
if (!settled && !inFlight.has(key)) inFlight.set(key, { path: r.path, state: r.state });
|
|
168
|
+
}
|
|
169
|
+
kinds.set(loaded.kind.name, ids);
|
|
170
|
+
}
|
|
171
|
+
if (kinds.size === 0) return null;
|
|
172
|
+
return { index: { kinds, ledger: hasLeaseLedger(top) }, inFlight };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The non-merge commits on every local and remote-tracking branch and HEAD
|
|
177
|
+
* that carry a `Chant-Record` or `Chant-Lease` trailer, newest first. The
|
|
178
|
+
* ledger branches under `chant/` are left out.
|
|
179
|
+
*/
|
|
180
|
+
export function trailerCommits(top: string): string[] {
|
|
181
|
+
let text: string;
|
|
182
|
+
try {
|
|
183
|
+
text = git(top, [
|
|
184
|
+
"log",
|
|
185
|
+
"--no-merges",
|
|
186
|
+
`--format=%H%x1f%(trailers:key=${RECORD_TRAILER},key=${LEASE_TRAILER},valueonly,separator=%x1e)%x1d`,
|
|
187
|
+
"--exclude=chant/*",
|
|
188
|
+
"--branches",
|
|
189
|
+
"--exclude=*/chant/*",
|
|
190
|
+
"--remotes",
|
|
191
|
+
"HEAD",
|
|
192
|
+
"--",
|
|
193
|
+
]);
|
|
194
|
+
} catch {
|
|
195
|
+
// An unborn HEAD has no history to read.
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const out: string[] = [];
|
|
199
|
+
for (const chunk of text.split("\x1d")) {
|
|
200
|
+
const [sha, trailers] = chunk.trim().split("\x1f");
|
|
201
|
+
if (sha && trailers && trailers.trim() !== "") out.push(sha);
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The paths each commit adds, changes or deletes, from the repository root, renames read as a delete and an add. */
|
|
207
|
+
export function commitFiles(top: string, shas: string[]): Map<string, string[]> {
|
|
208
|
+
const out = new Map<string, string[]>();
|
|
209
|
+
if (shas.length === 0) return out;
|
|
210
|
+
const text = git(top, ["log", "--no-walk=unsorted", "--stdin", "--no-renames", "--name-only", "--format=%x1e%H"], `${shas.join("\n")}\n`);
|
|
211
|
+
for (const block of text.split("\x1e")) {
|
|
212
|
+
const lines = block.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
213
|
+
if (lines.length === 0) continue;
|
|
214
|
+
out.set(lines[0], lines.slice(1));
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Every commit reachable from `rev`, or null when `rev` is not in the repository. */
|
|
220
|
+
function reachable(top: string, rev: string): Set<string> | null {
|
|
221
|
+
try {
|
|
222
|
+
return new Set(git(top, ["rev-list", rev, "--"]).split("\n").filter(Boolean));
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const byString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The work items a change-set entry is attributed to, sorted: each item with
|
|
232
|
+
* a linked commit that changed the entry's `source` and is in `within`, the
|
|
233
|
+
* history the change set was planned from (null when that is unknown, so
|
|
234
|
+
* every linked commit counts). An entry with no source has none.
|
|
235
|
+
*
|
|
236
|
+
* `commitsOf` maps each in-flight item to its linked commits, and `files`
|
|
237
|
+
* each commit to the paths it touches.
|
|
238
|
+
*/
|
|
239
|
+
export function attributeEntry(
|
|
240
|
+
source: string | undefined,
|
|
241
|
+
commitsOf: ReadonlyMap<string, readonly string[]>,
|
|
242
|
+
files: ReadonlyMap<string, readonly string[]>,
|
|
243
|
+
within: ReadonlySet<string> | null,
|
|
244
|
+
): string[] {
|
|
245
|
+
if (source === undefined) return [];
|
|
246
|
+
const out: string[] = [];
|
|
247
|
+
for (const [item, commits] of commitsOf) {
|
|
248
|
+
if (commits.some((sha) => (files.get(sha) ?? []).includes(source) && (within === null || within.has(sha)))) out.push(item);
|
|
249
|
+
}
|
|
250
|
+
return out.sort(byString);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The overlap report for the workspace declared at `root` in the repository
|
|
255
|
+
* at `top`. Null when the declaration names no work kind. Throws when a work
|
|
256
|
+
* kind or its records can't be read, or git fails; `workspace status` writes
|
|
257
|
+
* null then.
|
|
258
|
+
*/
|
|
259
|
+
export async function readOverlap(input: {
|
|
260
|
+
declaration: Declaration;
|
|
261
|
+
root: string;
|
|
262
|
+
top: string;
|
|
263
|
+
changeSets: OverlapChangeSetRef[];
|
|
264
|
+
readChangeSet?: StoredChangeSetReader;
|
|
265
|
+
}): Promise<OverlapReport | null> {
|
|
266
|
+
const { top } = input;
|
|
267
|
+
const work = await readWorkItems(input.declaration, input.root, top);
|
|
268
|
+
if (work === null) return null;
|
|
269
|
+
|
|
270
|
+
// The commits of each in-flight item, newest first.
|
|
271
|
+
const commitsOf = new Map<string, string[]>();
|
|
272
|
+
if (work.inFlight.size > 0) {
|
|
273
|
+
const candidates = trailerCommits(top);
|
|
274
|
+
const links = judgeCommits(top, candidates, work.index);
|
|
275
|
+
for (const sha of candidates) {
|
|
276
|
+
for (const id of links.get(sha)?.ids ?? []) {
|
|
277
|
+
if (!work.inFlight.has(id)) continue;
|
|
278
|
+
const list = commitsOf.get(id) ?? [];
|
|
279
|
+
if (!list.includes(sha)) list.push(sha);
|
|
280
|
+
commitsOf.set(id, list);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const items = [...commitsOf.keys()].sort(byString);
|
|
285
|
+
const files = commitFiles(top, [...new Set([...commitsOf.values()].flat())]);
|
|
286
|
+
const filesOf = new Map(items.map((id) => [id, new Set(commitsOf.get(id)!.flatMap((sha) => files.get(sha) ?? []))]));
|
|
287
|
+
|
|
288
|
+
// Each change-set entry with a source, attributed to the items whose linked commits changed it within the change set's history.
|
|
289
|
+
const read = input.readChangeSet ?? defaultReadChangeSet;
|
|
290
|
+
const seen = new Set<string>();
|
|
291
|
+
const changeSets: string[] = [];
|
|
292
|
+
let unattributed = 0;
|
|
293
|
+
const addressesOf = new Map<string, OverlapAddress[]>();
|
|
294
|
+
const history = new Map<string, Set<string> | null>();
|
|
295
|
+
for (const ref of input.changeSets) {
|
|
296
|
+
const key = `${ref.ledgerMember ?? ""}\0${ref.digest}`;
|
|
297
|
+
if (seen.has(key)) continue;
|
|
298
|
+
seen.add(key);
|
|
299
|
+
const doc = await read(ref.digest, ref.cwd);
|
|
300
|
+
if (doc === null) continue;
|
|
301
|
+
if (!changeSets.includes(doc.digest)) changeSets.push(doc.digest);
|
|
302
|
+
const at = doc.graph?.at ?? ref.gitSha;
|
|
303
|
+
for (const e of doc.entries) {
|
|
304
|
+
if (e.source === undefined) {
|
|
305
|
+
unattributed++;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// Only an entry two items could share needs the change set's history.
|
|
309
|
+
if (attributeEntry(e.source, commitsOf, files, null).length < 2) continue;
|
|
310
|
+
if (at && !history.has(at)) history.set(at, reachable(top, at));
|
|
311
|
+
const owners = attributeEntry(e.source, commitsOf, files, at ? history.get(at)! : null);
|
|
312
|
+
if (owners.length < 2) continue;
|
|
313
|
+
const address: OverlapAddress = { changeSet: doc.digest, ledgerMember: ref.ledgerMember, member: e.member, address: e.address, node: e.node ?? null, source: e.source };
|
|
314
|
+
for (const id of owners) addressesOf.set(id, [...(addressesOf.get(id) ?? []), address]);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
changeSets.sort(byString);
|
|
318
|
+
|
|
319
|
+
const pairs: OverlapPair[] = [];
|
|
320
|
+
for (let i = 0; i < items.length; i++) {
|
|
321
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
322
|
+
const a = items[i];
|
|
323
|
+
const b = items[j];
|
|
324
|
+
const fb = filesOf.get(b)!;
|
|
325
|
+
const shared = [...filesOf.get(a)!].filter((f) => fb.has(f)).sort(byString);
|
|
326
|
+
const ab = addressesOf.get(b) ?? [];
|
|
327
|
+
const addresses = (addressesOf.get(a) ?? [])
|
|
328
|
+
.filter((x) => ab.includes(x))
|
|
329
|
+
.sort((x, y) => byString(x.changeSet, y.changeSet) || byString(x.member, y.member) || byString(x.address, y.address));
|
|
330
|
+
if (shared.length > 0 || addresses.length > 0) pairs.push({ items: [a, b], files: shared, addresses });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
items: items.map((id) => ({ item: id, path: work.inFlight.get(id)!.path, state: work.inFlight.get(id)!.state, commits: commitsOf.get(id)! })),
|
|
336
|
+
pairs,
|
|
337
|
+
changeSets,
|
|
338
|
+
unattributed,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
@@ -46,13 +46,14 @@ import { workspacePoints } from "./points-cli";
|
|
|
46
46
|
import pointsSchema from "./points.schema.json";
|
|
47
47
|
import releasesSchema from "./releases.schema.json";
|
|
48
48
|
import changeSetShowSchema from "./change-set-show.schema.json";
|
|
49
|
+
import deliveryMetricsSchema from "./delivery-metrics.schema.json";
|
|
49
50
|
import { workspaceStatus } from "./status";
|
|
50
51
|
import statusSchema from "./status.schema.json";
|
|
51
52
|
|
|
52
53
|
const FIXTURE = join(REPO, "reference-workspace");
|
|
53
54
|
const TIMEOUT = 240_000;
|
|
54
55
|
|
|
55
|
-
const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, "records-since": recordsSinceSchema, intent: intentSchema, "intent-record": intentRecordSchema, composites: compositesSchema, points: pointsSchema, patch: patchSchema, "ci-last-green": ciLastGreenSchema, "ci-list": ciListSchema, releases: releasesSchema, "change-set-show": changeSetShowSchema };
|
|
56
|
+
const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema, "records-since": recordsSinceSchema, intent: intentSchema, "intent-record": intentRecordSchema, composites: compositesSchema, points: pointsSchema, patch: patchSchema, "ci-last-green": ciLastGreenSchema, "ci-list": ciListSchema, releases: releasesSchema, "change-set-show": changeSetShowSchema, "delivery-metrics": deliveryMetricsSchema };
|
|
56
57
|
|
|
57
58
|
/** This checkout's chant, started the way the CLI starts it, for members with no toolchain of their own. */
|
|
58
59
|
const reader: Toolchain = {
|
|
@@ -34,6 +34,7 @@ import { RUN_STATEMENT_FAILURE_CODES } from "./trust/run-statement";
|
|
|
34
34
|
import { WORK_EVIDENCE_ERROR_CODES } from "./work-evidence";
|
|
35
35
|
import { RECORD_FINDING_CODES } from "./checks/records";
|
|
36
36
|
import { ANSWER_WARNING_CODES } from "./points";
|
|
37
|
+
import { FLAG_WARNING_CODES } from "./flags";
|
|
37
38
|
import { POINTS_ERROR_CODES, POINTS_SOURCE_REASON_CODES } from "./points-cli";
|
|
38
39
|
import { POINTS_WRITE_ERROR_CODES } from "./decide";
|
|
39
40
|
import { PATCH_ERROR_CODES } from "./patch";
|
|
@@ -50,6 +51,7 @@ import { CI_GREEN_ERROR_CODES } from "./ci-green";
|
|
|
50
51
|
import { ROTATION_REFUSAL_CODES } from "./trust/rotation";
|
|
51
52
|
import { SIGNERS_ERROR_CODES } from "./trust/signers-cli";
|
|
52
53
|
import { EVIDENCE_ERROR_CODES } from "./trust/evidence";
|
|
54
|
+
import { METRICS_ERROR_CODES, METRICS_REASON_CODES } from "./delivery-metrics";
|
|
53
55
|
|
|
54
56
|
const HERE = import.meta.dirname;
|
|
55
57
|
|
|
@@ -101,6 +103,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
101
103
|
RUN_STATEMENT_FAILURE_CODES,
|
|
102
104
|
WORK_EVIDENCE_ERROR_CODES,
|
|
103
105
|
RECORD_FINDING_CODES,
|
|
106
|
+
FLAG_WARNING_CODES,
|
|
104
107
|
ANSWER_WARNING_CODES,
|
|
105
108
|
POINTS_ERROR_CODES,
|
|
106
109
|
POINTS_SOURCE_REASON_CODES,
|
|
@@ -121,6 +124,8 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
121
124
|
ROTATION_REFUSAL_CODES,
|
|
122
125
|
SIGNERS_ERROR_CODES,
|
|
123
126
|
EVIDENCE_ERROR_CODES,
|
|
127
|
+
METRICS_ERROR_CODES,
|
|
128
|
+
METRICS_REASON_CODES,
|
|
124
129
|
};
|
|
125
130
|
|
|
126
131
|
/** Every string in an `enum` under a property named `code`, anywhere in a schema. */
|
|
@@ -95,6 +95,9 @@ export const REASONS = {
|
|
|
95
95
|
"work-contract-unknown": "A work record names a contract that no record of its kind's contract kind has.",
|
|
96
96
|
"work-contract-undecided": "A work record names a contract whose state is not approved, such as a draft.",
|
|
97
97
|
"work-tier-unknown": "A work record names a builder tier that its kind's work.tier.tiers does not list.",
|
|
98
|
+
// A feature flag that is valid but warned about (records, check, #4024).
|
|
99
|
+
"flag-work-unknown": "A flag record names a work item that no record of its kind's work kind has.",
|
|
100
|
+
"flag-stale": "Every decision a flag's work item implements is confirmed, so the flag is due for removal. check reports it (WSP118).",
|
|
98
101
|
// An answer to a decision point that is valid but warned about (records, points, ws-058, #2739).
|
|
99
102
|
"answer-points-unreadable": "The points file the answer kind names can't be read, or is not valid.",
|
|
100
103
|
"answer-point-unknown": "The answer's point is not declared in the points file the answer kind names.",
|
|
@@ -19,6 +19,7 @@ import { pinFile, queryRecords, type RecordsDocument } from "./records-cli";
|
|
|
19
19
|
import { parseFrontMatter, RECORD_WARNING_CODES, SEAL_WARNING_CODES } from "./records";
|
|
20
20
|
import { WORK_WARNING_CODES } from "./work";
|
|
21
21
|
import { ANSWER_WARNING_CODES } from "./points";
|
|
22
|
+
import { FLAG_WARNING_CODES } from "./flags";
|
|
22
23
|
import { workspaceGraph } from "./graph-cli";
|
|
23
24
|
import graphSchema from "./graph.schema.json";
|
|
24
25
|
import recordsSchema from "./records.schema.json";
|
|
@@ -172,7 +173,7 @@ describe("chant workspace records checks each pin", () => {
|
|
|
172
173
|
test("the schema lists exactly the warning codes", () => {
|
|
173
174
|
// A work kind's records carry the work warnings too (#2683), work-done-gap-open included since records walks a done item's region (#2686),
|
|
174
175
|
// an answer kind's the answer warnings (ws-058), and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
175
|
-
expect(recordsSchema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
176
|
+
expect(recordsSchema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES, ...FLAG_WARNING_CODES]);
|
|
176
177
|
});
|
|
177
178
|
|
|
178
179
|
test("records pin <path> prints the entry's path from the workspace root and the file's hash", () => {
|
|
@@ -14,6 +14,7 @@ import { queryRecords, RECORDS_CONTRACT_VERSION, RECORDS_OUTPUT_SCHEMA_ID, type
|
|
|
14
14
|
import { READ_ERROR_CODES, RECORD_REASON_CODES, RECORD_WARNING_CODES, recordTextDigest, REVIEW_REASON_CODES, SEAL_WARNING_CODES } from "./records";
|
|
15
15
|
import { WORK_WARNING_CODES } from "./work";
|
|
16
16
|
import { ANSWER_WARNING_CODES } from "./points";
|
|
17
|
+
import { FLAG_WARNING_CODES } from "./flags";
|
|
17
18
|
import schema from "./records.schema.json";
|
|
18
19
|
import { PROVENANCE_LEVELS } from "./trust/attestor";
|
|
19
20
|
|
|
@@ -152,7 +153,7 @@ describe("records output schema", () => {
|
|
|
152
153
|
test("lists exactly the warning codes the code can return", () => {
|
|
153
154
|
// A work kind's records carry the work warnings too (#2683), work-done-gap-open included since records walks a done item's region (#2686),
|
|
154
155
|
// an answer kind's the answer warnings (ws-058), and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
155
|
-
expect(schema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
156
|
+
expect(schema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES, ...FLAG_WARNING_CODES]);
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
test("every failure validates with its code", async () => {
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
"items": { "$ref": "#/$defs/asset" }
|
|
255
255
|
},
|
|
256
256
|
"warnings": {
|
|
257
|
-
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base, or (added by #2772) a work record's acceptance criteria unmet when it is done, or met by a manual verdict from its implementer, or (added by #3147) a work record's contract that no record has or nobody approved, or a builder tier its kind does not list. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
257
|
+
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base, or (added by #2772) a work record's acceptance criteria unmet when it is done, or met by a manual verdict from its implementer, or (added by #3147) a work record's contract that no record has or nobody approved, or a builder tier its kind does not list, or (added by #4024) a flag record's work item that no record has, or a flag due for removal. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
258
258
|
"type": "array",
|
|
259
259
|
"items": { "$ref": "#/$defs/warning" }
|
|
260
260
|
},
|
|
@@ -322,6 +322,23 @@
|
|
|
322
322
|
"description": "Added in contract 1 by #3147, on every parsed record of a work kind whose work block names a contract kind. The contract record the item builds, by the id in the work block's contract field, with that record's state now; state is null when no record of the contract kind has the id. Null when the item names no contract.",
|
|
323
323
|
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLink" }]
|
|
324
324
|
},
|
|
325
|
+
"flag": {
|
|
326
|
+
"description": "Added in contract 1 by #4024, on every parsed record of a flag kind (a kind with a flag block). work is the work item the flag hides, with that item's state (null when no record of the flag kind's work kind has it), or null when the record names none. implements lists each decision the item implements, with its state and confirmed: true when it is in one of its kind's closed states and nothing supersedes it. stale is true when the item implements at least one decision and every one is confirmed: the flag is due for removal, warned flag-stale, and chant workspace check reports it as WSP118.",
|
|
327
|
+
"type": "object",
|
|
328
|
+
"required": ["work", "implements", "stale"],
|
|
329
|
+
"properties": {
|
|
330
|
+
"work": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLink" }] },
|
|
331
|
+
"implements": {
|
|
332
|
+
"type": "array",
|
|
333
|
+
"items": {
|
|
334
|
+
"type": "object",
|
|
335
|
+
"required": ["id", "state", "confirmed"],
|
|
336
|
+
"properties": { "id": { "type": "string" }, "state": { "type": ["string", "null"] }, "confirmed": { "type": "boolean" } }
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"stale": { "type": "boolean" }
|
|
340
|
+
}
|
|
341
|
+
},
|
|
325
342
|
"answers": {
|
|
326
343
|
"description": "Added in contract 1 by #3147, on every record with an id of a work kind whose work block names an answer kind. The decision-point answers about the item: each answer record whose constrains names the item's id, in path order, joined by id and never copied onto the item. Read at the same revision; in the working tree, with the answers a steward keeps on the lifecycle ledger laid over it. Absent when the answer kind can't be read.",
|
|
327
344
|
"type": "array",
|
|
@@ -414,7 +431,9 @@
|
|
|
414
431
|
"answer-point-unknown",
|
|
415
432
|
"answer-point-changed",
|
|
416
433
|
"record-unattested",
|
|
417
|
-
"record-ratified-below-quorum"
|
|
434
|
+
"record-ratified-below-quorum",
|
|
435
|
+
"flag-work-unknown",
|
|
436
|
+
"flag-stale"
|
|
418
437
|
]
|
|
419
438
|
},
|
|
420
439
|
"message": { "type": "string" }
|
package/src/workspace/records.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { sourceBlock, sourceBlockProblems, transcriptDrift } from "./source-bloc
|
|
|
38
38
|
import type { WorkspaceTree } from "./tree";
|
|
39
39
|
import type { DecisionWork, WorkAcceptance, WorkLink, WorkWarningCode } from "./work";
|
|
40
40
|
import type { AnswerWarningCode } from "./points";
|
|
41
|
+
import type { FlagState, FlagWarningCode } from "./flags";
|
|
41
42
|
|
|
42
43
|
// ── Reason codes ─────────────────────────────────────────────────────────────
|
|
43
44
|
|
|
@@ -209,8 +210,8 @@ export interface VerdictAttestation {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
export interface RecordWarning {
|
|
212
|
-
/** A work kind's records also carry the codes of `WORK_WARNING_CODES` (#2683), an answer kind's those of `ANSWER_WARNING_CODES` (#2739), and `records` adds `SEAL_WARNING_CODES` (#2688, #2878). */
|
|
213
|
-
code: RecordWarningCode | WorkWarningCode | SealWarningCode | AnswerWarningCode;
|
|
213
|
+
/** A work kind's records also carry the codes of `WORK_WARNING_CODES` (#2683), an answer kind's those of `ANSWER_WARNING_CODES` (#2739), a flag kind's those of `FLAG_WARNING_CODES` (#4024), and `records` adds `SEAL_WARNING_CODES` (#2688, #2878). */
|
|
214
|
+
code: RecordWarningCode | WorkWarningCode | SealWarningCode | AnswerWarningCode | FlagWarningCode;
|
|
214
215
|
message: string;
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -489,6 +490,31 @@ export const recordKindSchema = z
|
|
|
489
490
|
* Optional.
|
|
490
491
|
*/
|
|
491
492
|
answers: z.object({ points: z.string().min(1) }).strict().optional(),
|
|
493
|
+
/**
|
|
494
|
+
* A flag kind (#4024): each record is a feature flag, a workspace record
|
|
495
|
+
* a member's build reads as one build-time parameter. The block names the
|
|
496
|
+
* fields that hold the member whose parameter it sets, the parameter's
|
|
497
|
+
* name, the flag's owner and its value per environment, and the field
|
|
498
|
+
* naming the work item the flag hides, with that work kind's file
|
|
499
|
+
* (relative to this kind file). The record's id is the flag's name.
|
|
500
|
+
*
|
|
501
|
+
* With it, `chant build --env <env>` in that member resolves the
|
|
502
|
+
* parameter from the flag (`flag-params.ts`), `chant workspace status`
|
|
503
|
+
* lists the flag under the member's `featureFlags`, and `records` warns
|
|
504
|
+
* `flag-work-unknown` for a work item no record has and `flag-stale`
|
|
505
|
+
* when every decision the work item implements is confirmed, so the
|
|
506
|
+
* flag is due for removal (`check` WSP118). Optional.
|
|
507
|
+
*/
|
|
508
|
+
flag: z
|
|
509
|
+
.object({
|
|
510
|
+
member: z.string().min(1),
|
|
511
|
+
param: z.string().min(1),
|
|
512
|
+
owner: z.string().min(1),
|
|
513
|
+
values: z.string().min(1),
|
|
514
|
+
work: z.object({ field: z.string().min(1), kind: z.string().min(1) }).strict(),
|
|
515
|
+
})
|
|
516
|
+
.strict()
|
|
517
|
+
.optional(),
|
|
492
518
|
/**
|
|
493
519
|
* The top-level field that holds a closed record's seal (#2546, ws-003,
|
|
494
520
|
* ws-063): {@link recordSeal}, the whole-file JCS/SHA-256 of the record
|
|
@@ -559,6 +585,10 @@ export const recordKindSchema = z
|
|
|
559
585
|
message: "a work kind must have states, and its work open and done states must be listed in them",
|
|
560
586
|
path: ["work"],
|
|
561
587
|
})
|
|
588
|
+
.refine((k) => !k.flag || k.idField !== undefined, {
|
|
589
|
+
message: "a flag kind names its id with idField: a flag's id is its name, and a build finds it by that name",
|
|
590
|
+
path: ["flag"],
|
|
591
|
+
})
|
|
562
592
|
.refine((k) => !k.work?.acceptance || k.pins !== undefined, {
|
|
563
593
|
message: "a work kind with acceptance criteria must have pins: a criterion is met by the evidence in its pins field",
|
|
564
594
|
path: ["work", "acceptance"],
|
|
@@ -1229,6 +1259,8 @@ export interface RecordEntry {
|
|
|
1229
1259
|
acceptance?: WorkAcceptance | null;
|
|
1230
1260
|
/** For a work kind with a contract link (#3147): the contract record the item builds, with its state, or null when it names none. */
|
|
1231
1261
|
contract?: WorkLink | null;
|
|
1262
|
+
/** For a flag kind (#4024): the work item the flag hides, the decisions it implements, and whether the flag is due for removal. */
|
|
1263
|
+
flag?: FlagState;
|
|
1232
1264
|
}
|
|
1233
1265
|
|
|
1234
1266
|
export interface ReadRecordsOptions {
|
|
@@ -1575,6 +1607,8 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1575
1607
|
const work = kind.work ? await (await import("./work")).applyWork(loaded, entries, options) : undefined;
|
|
1576
1608
|
// An answer kind's points (ws-058): an answer whose point is gone or changed is warned about.
|
|
1577
1609
|
if (kind.answers) (await import("./points")).applyAnswers(loaded, entries, options);
|
|
1610
|
+
// A flag kind's work item and whether the flag is due for removal (#4024).
|
|
1611
|
+
if (kind.flag) await (await import("./flags")).applyFlags(loaded, entries, options);
|
|
1578
1612
|
|
|
1579
1613
|
for (const e of entries) e.valid = e.reasons.length === 0;
|
|
1580
1614
|
const records = options.current ? entries.filter((e) => e.supersededBy === null) : entries;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The feature flag record kind (#4024), data only: no imports, no code.
|
|
2
|
+
//
|
|
3
|
+
// chant workspace records --kind flags/flag.kind.mjs --json
|
|
4
|
+
//
|
|
5
|
+
// A flag is a workspace record a member's build reads as one build-time
|
|
6
|
+
// parameter: chant build --env <env> in that member takes the flag's value
|
|
7
|
+
// for the environment, and records it with source flag:<id>. Turning a flag
|
|
8
|
+
// on is a commit that changes this record, so it is reviewed, and the release
|
|
9
|
+
// cut from that commit carries the new value through the environment's gate.
|
|
10
|
+
//
|
|
11
|
+
// The canonical copy ships in @intentius/chant at
|
|
12
|
+
// src/workspace/reference-kinds/flag. A workspace copies this file and
|
|
13
|
+
// flag.schema.json into a directory of its own, such as flags/, names the
|
|
14
|
+
// kind file in chant.workspace.json's records, and points work.kind at its
|
|
15
|
+
// own work kind file.
|
|
16
|
+
export const recordKind = {
|
|
17
|
+
name: "flag",
|
|
18
|
+
location: { dir: ".", match: "^[a-z][a-z0-9-]{0,62}\\.flag\\.md$" },
|
|
19
|
+
format: "markdown-front-matter",
|
|
20
|
+
schema: { id: "urn:intentius:chant:flag:1", path: "flag.schema.json" },
|
|
21
|
+
idField: "id",
|
|
22
|
+
// The fields chant reads as the flag: the member and parameter it sets,
|
|
23
|
+
// its owner, its value per environment, and the work item it hides, a
|
|
24
|
+
// record of the work kind named here (relative to this file).
|
|
25
|
+
flag: {
|
|
26
|
+
member: "member",
|
|
27
|
+
param: "param",
|
|
28
|
+
owner: "owner",
|
|
29
|
+
values: "values",
|
|
30
|
+
work: { field: "work", kind: "../work/work.kind.mjs" },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "urn:intentius:chant:flag:1",
|
|
4
|
+
"title": "Feature flag front matter",
|
|
5
|
+
"description": "The front matter of one feature flag record (#4024): a workspace record one member's build reads as a build-time parameter. The file is named <id>.flag.md.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"patternProperties": {
|
|
9
|
+
"^x-": {}
|
|
10
|
+
},
|
|
11
|
+
"required": ["schema", "id", "owner", "work", "member", "param", "values"],
|
|
12
|
+
"properties": {
|
|
13
|
+
"schema": {
|
|
14
|
+
"description": "Format version of this file. Bumped only by a change to this schema.",
|
|
15
|
+
"const": 1
|
|
16
|
+
},
|
|
17
|
+
"id": {
|
|
18
|
+
"description": "The flag's name, which the build records as the source flag:<id> of the parameter it sets. Lowercase, with dashes.",
|
|
19
|
+
"type": "string",
|
|
20
|
+
"pattern": "^[a-z][a-z0-9-]{0,62}$"
|
|
21
|
+
},
|
|
22
|
+
"title": {
|
|
23
|
+
"description": "What the flag hides, in a few words. Optional.",
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"owner": {
|
|
27
|
+
"description": "Who owns the flag, and removes it once its work is settled.",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"minLength": 1
|
|
30
|
+
},
|
|
31
|
+
"work": {
|
|
32
|
+
"description": "The id of the work item the flag hides, a record of the work kind the flag kind names. A work item's implements names a decision, never a flag, so the link is kept here.",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
},
|
|
36
|
+
"member": {
|
|
37
|
+
"description": "The workspace member whose build parameter the flag sets, by its name in chant.workspace.json.",
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1
|
|
40
|
+
},
|
|
41
|
+
"param": {
|
|
42
|
+
"description": "The build parameter the flag sets: a key of buildParams in the member's chant.config.ts. Its value per environment must have the parameter's type and, when it declares one, be in its enum.",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"minLength": 1
|
|
45
|
+
},
|
|
46
|
+
"values": {
|
|
47
|
+
"description": "The flag's value per environment, keyed by the environment's name. An environment left out builds with the parameter's declared default.",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"propertyNames": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
50
|
+
"additionalProperties": { "type": ["string", "number", "boolean"] }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|