@intentius/chant 0.102.0 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/core.d.ts +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +3 -2
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +63 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +48 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +7 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +126 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/index.d.ts +4 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +27 -3
- package/dist/workspace/box-factory.d.ts.map +1 -1
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +2 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +9 -2
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +108 -14
- package/dist/workspace/conformance/writer.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +65 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +153 -4
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +1 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +28 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +102 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +48 -0
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +33 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +7 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +66 -6
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +28 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +138 -6
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs.d.ts +132 -11
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +2 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +54 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +16 -5
- package/dist/workspace/symbols.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +6 -1
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +6 -2
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +34 -3
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/main.test.ts +20 -0
- package/src/cli/main.ts +185 -23
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +34 -6
- package/src/cli/registry.ts +63 -1
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +51 -0
- package/src/lifecycle/gate-ledger.ts +7 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/index.ts +9 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/types.ts +8 -0
- package/src/op/work-lease-run.ts +3 -0
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +4 -1
- package/src/workspace/box-factory.ts +37 -4
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +3 -0
- package/src/workspace/checks.ts +10 -3
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +7 -2
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +96 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +14 -8
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.mjs +22 -1
- package/src/workspace/conformance/index.ts +5 -1
- package/src/workspace/conformance/vitest.ts +33 -1
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
- package/src/workspace/conformance/writer.test.ts +32 -2
- package/src/workspace/conformance/writer.ts +339 -28
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +427 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +153 -4
- package/src/workspace/declaration.ts +141 -0
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +2 -2
- package/src/workspace/graph.schema.json +67 -0
- package/src/workspace/identity.test.ts +23 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +26 -9
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +130 -3
- package/src/workspace/intent-record.ts +74 -28
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +5 -5
- package/src/workspace/intent.schema.json +234 -4
- package/src/workspace/intent.ts +260 -66
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +157 -2
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +162 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +38 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +78 -0
- package/src/workspace/records-cli.ts +20 -6
- package/src/workspace/records-close.schema.json +78 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +7 -0
- package/src/workspace/records-review.schema.json +78 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-write.ts +192 -25
- package/src/workspace/records.schema.json +16 -0
- package/src/workspace/records.ts +50 -9
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +343 -14
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +416 -5
- package/src/workspace/runs.schema.json +451 -4
- package/src/workspace/runs.test.ts +39 -0
- package/src/workspace/runs.ts +296 -35
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +5 -2
- package/src/workspace/status-gates.ts +3 -1
- package/src/workspace/status.schema.json +126 -3
- package/src/workspace/status.ts +90 -1
- package/src/workspace/symbols.ts +20 -9
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.ts +3 -0
- package/src/workspace/work-evidence.schema.json +3 -0
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +16 -1
- package/src/workspace/write-scope.ts +33 -11
|
@@ -33,13 +33,25 @@
|
|
|
33
33
|
* repository does not have, unless it names itself one of the four
|
|
34
34
|
* exceptions.
|
|
35
35
|
*
|
|
36
|
+
* 5. Concurrent writes (#3173, ws-089). When the writer performs `records
|
|
37
|
+
* amend`, the suite has it make three amendments of the script's
|
|
38
|
+
* decision at once, each from the digest the suite read
|
|
39
|
+
* (`--expect <digest>`), as two people and an agent would. Exactly one
|
|
40
|
+
* must be written; the other two must come back as chant's
|
|
41
|
+
* record-conflict refusal, naming the winner's digest, returned to the
|
|
42
|
+
* suite unchanged rather than retried blindly or hidden. The suite then
|
|
43
|
+
* has the writer retry each from the digest its refusal named, and each
|
|
44
|
+
* retry must be written. See {@link CONCURRENT_AMENDS}.
|
|
45
|
+
*
|
|
36
46
|
* `facts()` may only read: every chant call it makes must be a read-contract
|
|
37
47
|
* command, and it must leave the workspace as it was.
|
|
38
48
|
*
|
|
39
49
|
* The workspace is generated for each run, from the reader suite's fixture
|
|
40
50
|
* and the `__writer_fixture__/` overlay this package ships beside this module:
|
|
41
|
-
* the reference workspace's work, answer and session kinds
|
|
42
|
-
*
|
|
51
|
+
* the reference workspace's work, answer and session kinds (with the
|
|
52
|
+
* contract and driver kinds a session's verdicts may also name), its
|
|
53
|
+
* decision points, and one open work item, W-001. Nothing is written
|
|
54
|
+
* anywhere else.
|
|
43
55
|
*
|
|
44
56
|
* Actions a writer does not list in `actions` are not applicable to it. The
|
|
45
57
|
* suite still performs those steps itself, directly through chant, so a later
|
|
@@ -68,6 +80,7 @@ export const WRITE_CONTRACT_ACTIONS = [
|
|
|
68
80
|
"records close",
|
|
69
81
|
"points ask",
|
|
70
82
|
"points answer",
|
|
83
|
+
"points retract",
|
|
71
84
|
"work claim",
|
|
72
85
|
"work renew",
|
|
73
86
|
"work evidence",
|
|
@@ -75,6 +88,9 @@ export const WRITE_CONTRACT_ACTIONS = [
|
|
|
75
88
|
"runs start",
|
|
76
89
|
"runs end",
|
|
77
90
|
"runs record",
|
|
91
|
+
"box listing set",
|
|
92
|
+
"wip save",
|
|
93
|
+
"wip restore",
|
|
78
94
|
] as const;
|
|
79
95
|
export type WriteContractAction = (typeof WRITE_CONTRACT_ACTIONS)[number];
|
|
80
96
|
|
|
@@ -86,6 +102,7 @@ export const WRITE_CONTRACT_SCHEMAS: Record<WriteContractAction, string> = {
|
|
|
86
102
|
"records close": "records-close.schema.json",
|
|
87
103
|
"points ask": "points-write.schema.json",
|
|
88
104
|
"points answer": "points-write.schema.json",
|
|
105
|
+
"points retract": "points-write.schema.json",
|
|
89
106
|
"work claim": "work-lease.schema.json",
|
|
90
107
|
"work renew": "work-lease.schema.json",
|
|
91
108
|
"work evidence": "work-evidence.schema.json",
|
|
@@ -93,12 +110,15 @@ export const WRITE_CONTRACT_SCHEMAS: Record<WriteContractAction, string> = {
|
|
|
93
110
|
"runs start": "runs-write.schema.json",
|
|
94
111
|
"runs end": "runs-write.schema.json",
|
|
95
112
|
"runs record": "runs-write.schema.json",
|
|
113
|
+
"box listing set": "box-listing-write.schema.json",
|
|
114
|
+
"wip save": "wip-write.schema.json",
|
|
115
|
+
"wip restore": "wip-write.schema.json",
|
|
96
116
|
};
|
|
97
117
|
|
|
98
118
|
/**
|
|
99
|
-
* The flags a writer may add to each command. The record, points, evidence
|
|
100
|
-
* and
|
|
101
|
-
* `--json`; a lease prints its document only with `--json`.
|
|
119
|
+
* The flags a writer may add to each command. The record, points, evidence,
|
|
120
|
+
* run and listing writes always print their document, so a writer adds
|
|
121
|
+
* nothing or `--json`; a lease prints its document only with `--json`.
|
|
102
122
|
*/
|
|
103
123
|
export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (readonly string[])[]> = {
|
|
104
124
|
"records new": [[], ["--json"]],
|
|
@@ -107,6 +127,7 @@ export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (re
|
|
|
107
127
|
"records close": [[], ["--json"]],
|
|
108
128
|
"points ask": [[], ["--json"]],
|
|
109
129
|
"points answer": [[], ["--json"]],
|
|
130
|
+
"points retract": [[], ["--json"]],
|
|
110
131
|
"work claim": [["--json"]],
|
|
111
132
|
"work renew": [["--json"]],
|
|
112
133
|
"work evidence": [[], ["--json"]],
|
|
@@ -114,16 +135,22 @@ export const WRITE_CONTRACT_JSON_FLAGS: Record<WriteContractAction, readonly (re
|
|
|
114
135
|
"runs start": [[], ["--json"]],
|
|
115
136
|
"runs end": [[], ["--json"]],
|
|
116
137
|
"runs record": [[], ["--json"]],
|
|
138
|
+
"box listing set": [[], ["--json"]],
|
|
139
|
+
"wip save": [[], ["--json"]],
|
|
140
|
+
"wip restore": [[], ["--json"]],
|
|
117
141
|
};
|
|
118
142
|
|
|
119
143
|
/** What each action is given. Field documents are JSON values; the command reads them from stdin. */
|
|
120
144
|
export interface WriteParams {
|
|
121
145
|
"records new": { kind: string; fields: Record<string, unknown> };
|
|
122
|
-
|
|
146
|
+
/** `expect` (#3173): the record's digest the write builds on, passed as `--expect`. */
|
|
147
|
+
"records amend": { id: string; kind: string; fields: Record<string, unknown>; expect?: string };
|
|
123
148
|
"records review": { id: string; kind: string; verdict: "agree" | "dissent" | "abstain"; by: string; note?: string };
|
|
124
149
|
"records close": { id: string; kind: string };
|
|
125
|
-
|
|
126
|
-
"points
|
|
150
|
+
/** `candidates` is an ad-hoc point's question and candidates (#3403), `{ question, criteria }`: they go on stdin, and the inputs as JSON on the command line. */
|
|
151
|
+
"points ask": { point: string; kind: string; inputs: Record<string, unknown>; subject?: string; candidates?: { question: string; criteria: Record<string, string> | string[] } };
|
|
152
|
+
"points answer": { id: string; kind: string; answer: string; by: string; note?: string };
|
|
153
|
+
"points retract": { id: string; kind: string; by: string; note?: string };
|
|
127
154
|
"work claim": { id: string; kind: string; holder: string };
|
|
128
155
|
"work renew": { id: string; kind: string; holder: string; token: string };
|
|
129
156
|
"work evidence": { id: string; kind: string; holder: string; token: string; entry: Record<string, unknown> };
|
|
@@ -131,6 +158,12 @@ export interface WriteParams {
|
|
|
131
158
|
"runs start": { run: Record<string, unknown> };
|
|
132
159
|
"runs end": { id: string; fields: Record<string, unknown> };
|
|
133
160
|
"runs record": { run: Record<string, unknown> };
|
|
161
|
+
/** A box's listing (#3308): the member, the listing fields, and an image to copy in as its cover. */
|
|
162
|
+
"box listing set": { member: string; fields: Record<string, unknown>; cover?: string };
|
|
163
|
+
/** A snapshot of the working tree, uncommitted records included, onto refs/chant/wip/<branch> (#3172): what the caller calls it, and who took it. */
|
|
164
|
+
"wip save": { label?: string; by?: string };
|
|
165
|
+
/** Put the working tree back as a snapshot holds it (#3172). */
|
|
166
|
+
"wip restore": { snapshot: string; by?: string };
|
|
134
167
|
}
|
|
135
168
|
|
|
136
169
|
/** One write the suite asks of the writer: the action, what it is given, and the command that performs it. */
|
|
@@ -142,7 +175,7 @@ export type WriteStep = {
|
|
|
142
175
|
params: WriteParams[A];
|
|
143
176
|
/** The arguments after `chant workspace <action>`, as {@link writeArgv} builds them from `params`. */
|
|
144
177
|
args: string[];
|
|
145
|
-
/** What the command reads on stdin (`--from -`, `--set -`, `--inputs -`), when it reads anything. */
|
|
178
|
+
/** What the command reads on stdin (`--from -`, `--set -`, `--inputs -`, `--candidates -`), when it reads anything. */
|
|
146
179
|
input?: string;
|
|
147
180
|
};
|
|
148
181
|
}[WriteContractAction];
|
|
@@ -162,7 +195,7 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
|
|
|
162
195
|
}
|
|
163
196
|
case "records amend": {
|
|
164
197
|
const q = p as WriteParams["records amend"];
|
|
165
|
-
return { args: [q.id, "--kind", q.kind, "--set", "-"], input: json(q.fields) };
|
|
198
|
+
return { args: [q.id, "--kind", q.kind, "--set", "-", ...(q.expect !== undefined ? ["--expect", q.expect] : [])], input: json(q.fields) };
|
|
166
199
|
}
|
|
167
200
|
case "records review": {
|
|
168
201
|
const q = p as WriteParams["records review"];
|
|
@@ -174,11 +207,18 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
|
|
|
174
207
|
}
|
|
175
208
|
case "points ask": {
|
|
176
209
|
const q = p as WriteParams["points ask"];
|
|
177
|
-
|
|
210
|
+
const subject = q.subject !== undefined ? ["--subject", q.subject] : [];
|
|
211
|
+
// An ad-hoc ask (#3403): the question and candidates read from stdin, and the inputs given as JSON.
|
|
212
|
+
if (q.candidates !== undefined) return { args: [q.point, "--inputs", json(q.inputs), "--candidates", "-", ...subject, "--kind", q.kind], input: json(q.candidates) };
|
|
213
|
+
return { args: [q.point, "--inputs", "-", ...subject, "--kind", q.kind], input: json(q.inputs) };
|
|
178
214
|
}
|
|
179
215
|
case "points answer": {
|
|
180
216
|
const q = p as WriteParams["points answer"];
|
|
181
|
-
return { args: [q.id, "--answer", q.answer, "--by", q.by, "--kind", q.kind] };
|
|
217
|
+
return { args: [q.id, "--answer", q.answer, "--by", q.by, ...(q.note !== undefined ? ["--note", q.note] : []), "--kind", q.kind] };
|
|
218
|
+
}
|
|
219
|
+
case "points retract": {
|
|
220
|
+
const q = p as WriteParams["points retract"];
|
|
221
|
+
return { args: [q.id, "--by", q.by, ...(q.note !== undefined ? ["--note", q.note] : []), "--kind", q.kind] };
|
|
182
222
|
}
|
|
183
223
|
case "work claim": {
|
|
184
224
|
const q = p as WriteParams["work claim"];
|
|
@@ -205,6 +245,18 @@ export function writeArgv<A extends WriteContractAction>(action: A, params: Writ
|
|
|
205
245
|
const q = p as WriteParams["runs end"];
|
|
206
246
|
return { args: [q.id, "--from", "-"], input: json(q.fields) };
|
|
207
247
|
}
|
|
248
|
+
case "box listing set": {
|
|
249
|
+
const q = p as WriteParams["box listing set"];
|
|
250
|
+
return { args: [q.member, "--from", "-", ...(q.cover !== undefined ? ["--cover", q.cover] : [])], input: json(q.fields) };
|
|
251
|
+
}
|
|
252
|
+
case "wip save": {
|
|
253
|
+
const q = p as WriteParams["wip save"];
|
|
254
|
+
return { args: [...(q.label !== undefined ? ["--label", q.label] : []), ...(q.by !== undefined ? ["--by", q.by] : [])] };
|
|
255
|
+
}
|
|
256
|
+
case "wip restore": {
|
|
257
|
+
const q = p as WriteParams["wip restore"];
|
|
258
|
+
return { args: [q.snapshot, ...(q.by !== undefined ? ["--by", q.by] : [])] };
|
|
259
|
+
}
|
|
208
260
|
default:
|
|
209
261
|
throw new Error(`not a write-contract action: ${String(action)}`);
|
|
210
262
|
}
|
|
@@ -218,6 +270,32 @@ export const WRITER_KINDS = {
|
|
|
218
270
|
work: "work/work.kind.mjs",
|
|
219
271
|
} as const;
|
|
220
272
|
|
|
273
|
+
/** The member of the writer workspace whose box block the script lists (#3308). */
|
|
274
|
+
export const WRITER_BOX = "app";
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The files the suite makes for a run outside the workspace, for a step to
|
|
278
|
+
* hand chant: the cover image the listing step copies in (#3308), a PNG.
|
|
279
|
+
*/
|
|
280
|
+
export const WRITER_INPUTS = {
|
|
281
|
+
cover: {
|
|
282
|
+
name: "cover.png",
|
|
283
|
+
bytes: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "base64"),
|
|
284
|
+
},
|
|
285
|
+
} as const;
|
|
286
|
+
|
|
287
|
+
/** Where the suite put {@link WRITER_INPUTS} for this run. */
|
|
288
|
+
export interface WriterInputs {
|
|
289
|
+
dir: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Write {@link WRITER_INPUTS} into `dir`. */
|
|
293
|
+
export function writeWriterInputs(dir: string): WriterInputs {
|
|
294
|
+
mkdirSync(dir, { recursive: true });
|
|
295
|
+
for (const f of Object.values(WRITER_INPUTS)) writeFileSync(join(dir, f.name), f.bytes);
|
|
296
|
+
return { dir };
|
|
297
|
+
}
|
|
298
|
+
|
|
221
299
|
/** The principals the script writes as. */
|
|
222
300
|
export const WRITER_PRINCIPALS = { holder: "conformance-writer", reviewer: "conformance-reviewer", by: "conformance" } as const;
|
|
223
301
|
|
|
@@ -225,10 +303,11 @@ export const WRITER_PRINCIPALS = { holder: "conformance-writer", reviewer: "conf
|
|
|
225
303
|
export interface WriterScriptStep<A extends WriteContractAction = WriteContractAction> {
|
|
226
304
|
id: string;
|
|
227
305
|
action: A;
|
|
228
|
-
|
|
306
|
+
/** `inputs` is where the suite put {@link WRITER_INPUTS}; a step that hands chant one of them leaves it out without. */
|
|
307
|
+
params(done: Record<string, Record<string, unknown>>, inputs?: WriterInputs): WriteParams[A];
|
|
229
308
|
}
|
|
230
309
|
|
|
231
|
-
const step = <A extends WriteContractAction>(id: string, action: A, params: (done: Record<string, Record<string, unknown
|
|
310
|
+
const step = <A extends WriteContractAction>(id: string, action: A, params: (done: Record<string, Record<string, unknown>>, inputs?: WriterInputs) => WriteParams[A]): WriterScriptStep => ({ id, action, params }) as WriterScriptStep;
|
|
232
311
|
|
|
233
312
|
const leaseToken = (done: Record<string, Record<string, unknown>>): string => String((done.claim?.lease as { token?: unknown } | undefined)?.token ?? "");
|
|
234
313
|
const RUN_STARTED = { harness: { name: "conformance", version: "1" }, model: "none", provider: "none", by: WRITER_PRINCIPALS.by, unit: "W-001" };
|
|
@@ -236,9 +315,12 @@ const RUN_STARTED = { harness: { name: "conformance", version: "1" }, model: "no
|
|
|
236
315
|
/**
|
|
237
316
|
* The writes the suite drives, in order, each action at least once: a
|
|
238
317
|
* decision made, amended and reviewed; a review session opened and closed; a
|
|
239
|
-
* decision point asked
|
|
318
|
+
* decision point asked, answered with a note, the answer retracted and the
|
|
319
|
+
* question answered again (#3351); an ad-hoc question asked with its own
|
|
320
|
+
* question and candidates, as an agent asks the room, and answered (#3403); the work item's lease claimed, renewed,
|
|
240
321
|
* evidence attached under it and released; one run started and ended, and one
|
|
241
|
-
* recorded whole
|
|
322
|
+
* recorded whole; the box's listing set, with a cover image copied in; and a
|
|
323
|
+
* snapshot of the working tree taken and restored (#3172).
|
|
242
324
|
*/
|
|
243
325
|
export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
|
|
244
326
|
step("decision", "records new", () => ({
|
|
@@ -275,6 +357,16 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
|
|
|
275
357
|
opened: "2026-01-01T00:00:00Z",
|
|
276
358
|
closed: null,
|
|
277
359
|
verdicts: [],
|
|
360
|
+
// A review comment anchored in the app, as a UI review batch keeps it (#3350).
|
|
361
|
+
comments: [
|
|
362
|
+
{
|
|
363
|
+
text: "The status line should say which build is running.",
|
|
364
|
+
by: WRITER_PRINCIPALS.reviewer,
|
|
365
|
+
anchor: { route: "/", elements: [{ selector: "#status", tag: "p", text: "Running.", state: "anchored", basis: "id" }] },
|
|
366
|
+
answers: [{ round: 0, disposition: "needs-discussion", note: "Which build: the commit or the release?", by: WRITER_PRINCIPALS.holder, at: "2026-01-01T00:02:00Z" }],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
rounds: [{ round: 0, note: null, by: WRITER_PRINCIPALS.reviewer, at: "2026-01-01T00:01:00Z", ended: "2026-01-01T00:03:00Z" }],
|
|
278
370
|
},
|
|
279
371
|
})),
|
|
280
372
|
step("close", "records close", (d) => ({ id: String(d.session.id), kind: WRITER_KINDS.session })),
|
|
@@ -284,7 +376,21 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
|
|
|
284
376
|
subject: "W-001",
|
|
285
377
|
inputs: { "work-item.criteria": 1, "work-item.files": 1, "work-item.words": 40, "work-item.fits_small": false, "work-item.fits_medium": false },
|
|
286
378
|
})),
|
|
287
|
-
step("answer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "medium", by: WRITER_PRINCIPALS.reviewer })),
|
|
379
|
+
step("answer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "medium", by: WRITER_PRINCIPALS.reviewer, note: "Two criteria and one file, but past the small limits." })),
|
|
380
|
+
step("retract", "points retract", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, by: WRITER_PRINCIPALS.reviewer, note: "Answered for the wrong work item." })),
|
|
381
|
+
step("reanswer", "points answer", (d) => ({ id: String(d.ask.id), kind: WRITER_KINDS.answer, answer: "large", by: WRITER_PRINCIPALS.reviewer })),
|
|
382
|
+
// #3403: an agent's question to the room, its text and options arriving with the ask.
|
|
383
|
+
step("adhoc", "points ask", () => ({
|
|
384
|
+
point: "agent-question",
|
|
385
|
+
kind: WRITER_KINDS.answer,
|
|
386
|
+
subject: "W-001",
|
|
387
|
+
inputs: { "ask.id": "conformance-ask-1", "ask.by": WRITER_PRINCIPALS.holder },
|
|
388
|
+
candidates: {
|
|
389
|
+
question: "Should the writer suite's item ship behind a flag?",
|
|
390
|
+
criteria: { flag: "Behind a flag, off by default.", now: "On for everyone at once." },
|
|
391
|
+
},
|
|
392
|
+
})),
|
|
393
|
+
step("adhoc-answer", "points answer", (d) => ({ id: String(d.adhoc.id), kind: WRITER_KINDS.answer, answer: "flag", by: WRITER_PRINCIPALS.reviewer })),
|
|
288
394
|
step("claim", "work claim", () => ({ id: "W-001", kind: WRITER_KINDS.work, holder: WRITER_PRINCIPALS.holder })),
|
|
289
395
|
step("renew", "work renew", (d) => ({ id: "W-001", kind: WRITER_KINDS.work, holder: WRITER_PRINCIPALS.holder, token: leaseToken(d) })),
|
|
290
396
|
step("evidence", "work evidence", (d) => ({
|
|
@@ -312,8 +418,34 @@ export const WRITER_SCRIPT: readonly WriterScriptStep[] = [
|
|
|
312
418
|
...RUN_STARTED,
|
|
313
419
|
},
|
|
314
420
|
})),
|
|
421
|
+
step("listing", "box listing set", (_d, inputs) => ({
|
|
422
|
+
member: WRITER_BOX,
|
|
423
|
+
fields: { published: true, title: "The writer suite's box", line: "Listed through chant, read back after amnesia." },
|
|
424
|
+
...(inputs ? { cover: join(inputs.dir, WRITER_INPUTS.cover.name) } : {}),
|
|
425
|
+
})),
|
|
426
|
+
// #3172: a checkpoint of everything the script left uncommitted, and putting it back.
|
|
427
|
+
step("checkpoint", "wip save", () => ({ label: "turn:1", by: WRITER_PRINCIPALS.by })),
|
|
428
|
+
step("undo", "wip restore", (done) => ({ snapshot: String((done.checkpoint?.snapshot as { commit?: unknown } | undefined)?.commit ?? ""), by: WRITER_PRINCIPALS.by })),
|
|
315
429
|
];
|
|
316
430
|
|
|
431
|
+
/**
|
|
432
|
+
* The concurrent case (#3173): three amendments of the script's decision the
|
|
433
|
+
* writer makes at once, each setting its question, as two people and an
|
|
434
|
+
* agent answering the same record would. The suite gives each the digest it
|
|
435
|
+
* read as `expect`.
|
|
436
|
+
*/
|
|
437
|
+
export const CONCURRENT_AMENDS: readonly { id: string; fields: Record<string, unknown> }[] = [
|
|
438
|
+
{ id: "concurrent-alice", fields: { question: "How does a tool write a fact about this workspace, as alice reads it?" } },
|
|
439
|
+
{ id: "concurrent-bob", fields: { question: "How does a tool write a fact about this workspace, as bob reads it?" } },
|
|
440
|
+
{ id: "concurrent-agent", fields: { question: "How does a tool write a fact about this workspace, as the agent reads it?" } },
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
/** The amend step of {@link CONCURRENT_AMENDS} entry `c`, on record `id`, from digest `expect`. */
|
|
444
|
+
export function concurrentAmendStep(c: { id: string; fields: Record<string, unknown> }, id: string, expect: string): WriteStep {
|
|
445
|
+
const params: WriteParams["records amend"] = { id, kind: WRITER_KINDS.decision, fields: c.fields, expect };
|
|
446
|
+
return { id: c.id, action: "records amend", params, ...writeArgv("records amend", params) } as WriteStep;
|
|
447
|
+
}
|
|
448
|
+
|
|
317
449
|
/** The four things ws-074 lets a tool keep outside the repo. */
|
|
318
450
|
export const PRIVATE_STATE_CATEGORIES = ["cache", "telemetry", "secret", "runtime"] as const;
|
|
319
451
|
export type PrivateStateCategory = (typeof PRIVATE_STATE_CATEGORIES)[number];
|
|
@@ -395,8 +527,8 @@ export interface WorkspaceWriterConformanceReport {
|
|
|
395
527
|
/** The actions not in `actions`, so not applicable to this writer. */
|
|
396
528
|
skipped: WriteContractAction[];
|
|
397
529
|
results: WriterStepResult[];
|
|
398
|
-
/** The problems of the checks after the script, by check. */
|
|
399
|
-
after: { facts: string[]; state: string[]; amnesia: string[]; holds: string[]; readBack: string[] };
|
|
530
|
+
/** The problems of the checks after the script, by check. `concurrent` is empty, and not run, when the writer does not perform records amend. */
|
|
531
|
+
after: { facts: string[]; state: string[]; amnesia: string[]; holds: string[]; readBack: string[]; concurrent: string[] };
|
|
400
532
|
/** What `facts()` returned before amnesia and after. */
|
|
401
533
|
facts: { before: unknown; after: unknown };
|
|
402
534
|
/** The workspace written. Removed before the report is returned. */
|
|
@@ -482,14 +614,31 @@ export function isReadCall(argv: readonly string[]): boolean {
|
|
|
482
614
|
case "runs":
|
|
483
615
|
return !["start", "end", "record"].includes(sub ?? "");
|
|
484
616
|
case "points":
|
|
485
|
-
return !["ask", "answer"].includes(sub ?? "");
|
|
617
|
+
return !["ask", "answer", "retract"].includes(sub ?? "");
|
|
486
618
|
case "work":
|
|
487
619
|
return sub === "history";
|
|
620
|
+
case "wip":
|
|
621
|
+
return !["save", "restore", "push", "fetch"].includes(sub ?? "");
|
|
488
622
|
default:
|
|
489
623
|
return false;
|
|
490
624
|
}
|
|
491
625
|
}
|
|
492
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Whether an argument a writer passed is the step's: the same text, or for a
|
|
629
|
+
* step argument that is JSON (an ad-hoc ask's inputs, #3403) the same value,
|
|
630
|
+
* whatever its key order or spacing.
|
|
631
|
+
*/
|
|
632
|
+
function sameArg(given: string | undefined, want: string): boolean {
|
|
633
|
+
if (given === want) return true;
|
|
634
|
+
if (given === undefined || !/^[[{]/.test(want)) return false;
|
|
635
|
+
try {
|
|
636
|
+
return isDeepStrictEqual(JSON.parse(given), JSON.parse(want));
|
|
637
|
+
} catch {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
493
642
|
/**
|
|
494
643
|
* What is wrong with the chant calls one step made: anything but exactly
|
|
495
644
|
* one call, of `workspace <action>` with the step's arguments in order and
|
|
@@ -503,7 +652,7 @@ export function writerCallProblems(step: WriteStep, calls: readonly ChantRun[]):
|
|
|
503
652
|
const prefix = ["workspace", ...step.action.split(" ")];
|
|
504
653
|
if (prefix.some((t, i) => argv[i] !== t)) return [`${name}: ran chant ${argv.join(" ")}, which is not workspace ${step.action}`];
|
|
505
654
|
const rest = argv.slice(prefix.length);
|
|
506
|
-
const at = rest.findIndex((_, i) => step.args.every((a, j) => rest[i + j]
|
|
655
|
+
const at = rest.findIndex((_, i) => step.args.every((a, j) => sameArg(rest[i + j], a)));
|
|
507
656
|
if (step.args.length > 0 && at === -1) return [`${name}: ran chant ${argv.join(" ")}, which does not pass ${step.args.join(" ")} in order`];
|
|
508
657
|
const extra = step.args.length > 0 ? [...rest.slice(0, at), ...rest.slice(at + step.args.length)] : rest;
|
|
509
658
|
const allowed = WRITE_CONTRACT_JSON_FLAGS[step.action];
|
|
@@ -558,6 +707,16 @@ export function reportedWrites(action: WriteContractAction, doc: unknown): { pat
|
|
|
558
707
|
const ledger = (d.ledger ?? {}) as Record<string, unknown>;
|
|
559
708
|
return { paths: [], refs: str(ledger.commit) ? { "refs/heads/chant/lifecycle": String(ledger.commit) } : {} };
|
|
560
709
|
}
|
|
710
|
+
if (action === "box listing set") return { paths: Array.isArray(d.paths) ? d.paths.filter((p): p is string => typeof p === "string") : [], refs: {} };
|
|
711
|
+
if (action === "wip save" || action === "wip restore") {
|
|
712
|
+
// The one snapshot ref, at the snapshot taken (the restore's checkpoint), and any ref a push recorded under refs/chant/replica/.
|
|
713
|
+
const snapshot = (action === "wip save" ? d.snapshot : (d.checkpoint as Record<string, unknown> | undefined)?.snapshot) as { commit?: unknown } | undefined;
|
|
714
|
+
const refs: Record<string, string> = {};
|
|
715
|
+
if (str(d.ref) && str(snapshot?.commit)) refs[String(d.ref)] = String(snapshot!.commit);
|
|
716
|
+
const replication = (d.replication ?? null) as { remote?: string; pushed?: { ref: string; commit: string }[]; upToDate?: number } | null;
|
|
717
|
+
for (const p of replication?.pushed ?? []) refs[`refs/chant/replica/${replication!.remote}/${p.ref.slice("refs/".length)}`] = p.commit;
|
|
718
|
+
return { paths: Array.isArray(d.paths) ? d.paths.filter((p): p is string => typeof p === "string") : [], refs };
|
|
719
|
+
}
|
|
561
720
|
return { paths: str(d.path) ? [String(d.path)] : [], refs: {} };
|
|
562
721
|
}
|
|
563
722
|
|
|
@@ -582,12 +741,24 @@ export function unreportedChanges(
|
|
|
582
741
|
return problems;
|
|
583
742
|
}
|
|
584
743
|
|
|
585
|
-
/** Build the step a script step is at, from the documents of the steps before it. */
|
|
586
|
-
export function buildStep(s: WriterScriptStep, done: Record<string, Record<string, unknown
|
|
587
|
-
const params = s.params(done);
|
|
744
|
+
/** Build the step a script step is at, from the documents of the steps before it and where the suite put its inputs. */
|
|
745
|
+
export function buildStep(s: WriterScriptStep, done: Record<string, Record<string, unknown>>, inputs?: WriterInputs): WriteStep {
|
|
746
|
+
const params = s.params(done, inputs);
|
|
588
747
|
return { id: s.id, action: s.action, params, ...writeArgv(s.action, params) } as WriteStep;
|
|
589
748
|
}
|
|
590
749
|
|
|
750
|
+
/** The box listings `status --json` prints, by member: what a home site shows of each box (#3308). */
|
|
751
|
+
export async function readListing(run: (argv: string[]) => Promise<ChantRun>): Promise<Record<string, unknown>> {
|
|
752
|
+
const doc = JSON.parse((await run(["workspace", "status", "dev", "--json"])).stdout) as { members?: { name: string; box: { listing?: unknown } | null }[] };
|
|
753
|
+
return Object.fromEntries((doc.members ?? []).filter((m) => m.box?.listing).map((m) => [m.name, m.box!.listing]));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** Each branch's work-in-progress snapshots `wip --json` lists (#3172), by branch: each snapshot's kind and label, newest first. */
|
|
757
|
+
export async function readWip(run: (argv: string[]) => Promise<ChantRun>): Promise<Record<string, [string, string | null][]>> {
|
|
758
|
+
const doc = JSON.parse((await run(["workspace", "wip", "--json"])).stdout) as { branches?: { branch: string; snapshots: { kind: string; label: string | null }[] }[] };
|
|
759
|
+
return Object.fromEntries((doc.branches ?? []).map((b) => [b.branch, b.snapshots.map((s) => [s.kind, s.label] as [string, string | null])]));
|
|
760
|
+
}
|
|
761
|
+
|
|
591
762
|
/** The smallest writer that conforms: each step is its one command, and its facts are read through the read contract. */
|
|
592
763
|
export const referenceWriter: WorkspaceWriterFactory = (chant) => ({
|
|
593
764
|
async write(step) {
|
|
@@ -603,6 +774,8 @@ export const referenceWriter: WorkspaceWriterFactory = (chant) => ({
|
|
|
603
774
|
}
|
|
604
775
|
const runs = JSON.parse((await chant.run(["workspace", "runs", "--json"])).stdout) as { runs: { id: string; state: string }[] };
|
|
605
776
|
out.runs = runs.runs.map((r) => [r.id, r.state]).sort();
|
|
777
|
+
out.listing = await readListing((argv) => chant.run(argv));
|
|
778
|
+
out.wip = await readWip((argv) => chant.run(argv));
|
|
606
779
|
return out;
|
|
607
780
|
},
|
|
608
781
|
});
|
|
@@ -616,7 +789,8 @@ export interface WriterConformanceWorkspace {
|
|
|
616
789
|
/**
|
|
617
790
|
* Generate the writer conformance workspace: the reader suite's workspace
|
|
618
791
|
* ({@link createConformanceWorkspace}), with `__writer_fixture__/` copied over
|
|
619
|
-
* it, the four kinds declared in its `records`,
|
|
792
|
+
* it, the four kinds declared in its `records`, a box block on
|
|
793
|
+
* {@link WRITER_BOX} for the listing step, and that committed.
|
|
620
794
|
*/
|
|
621
795
|
export function createWriterConformanceWorkspace(options: { chantCommand?: string[]; timeoutMs?: number } = {}): WriterConformanceWorkspace {
|
|
622
796
|
const ws = createConformanceWorkspace({ chantCommand: options.chantCommand, timeoutMs: options.timeoutMs });
|
|
@@ -625,6 +799,8 @@ export function createWriterConformanceWorkspace(options: { chantCommand?: strin
|
|
|
625
799
|
const declFile = join(ws.dir, "chant.workspace.json");
|
|
626
800
|
const decl = JSON.parse(readFileSync(declFile, "utf-8")) as Record<string, unknown>;
|
|
627
801
|
decl.records = Object.values(WRITER_KINDS).map((kind) => ({ kind }));
|
|
802
|
+
const box = (decl.members as { name: string; box?: unknown }[] | undefined)?.find((m) => m.name === WRITER_BOX);
|
|
803
|
+
if (box) box.box = {};
|
|
628
804
|
writeFileSync(declFile, `${JSON.stringify(decl, null, 2)}\n`);
|
|
629
805
|
const git = (...args: string[]) =>
|
|
630
806
|
execFileSync("git", ["-c", "user.name=chant", "-c", "user.email=chant@localhost", "-c", "commit.gpgsign=false", ...args], {
|
|
@@ -712,6 +888,8 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
|
|
|
712
888
|
const privateState = options.privateState ?? [];
|
|
713
889
|
const ws = createWriterConformanceWorkspace({ chantCommand, timeoutMs });
|
|
714
890
|
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "chant-writer-state-")));
|
|
891
|
+
const inputsDir = realpathSync(mkdtempSync(join(tmpdir(), "chant-writer-inputs-")));
|
|
892
|
+
const inputs = writeWriterInputs(inputsDir);
|
|
715
893
|
const calls: ChantRun[] = [];
|
|
716
894
|
const transport: ChantTransport = {
|
|
717
895
|
async run(argv, opts) {
|
|
@@ -728,14 +906,14 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
|
|
|
728
906
|
throw new Error(`chant ${argv.join(" ")} printed no JSON (exit ${run.status}): ${run.stderr.trim()}`);
|
|
729
907
|
}
|
|
730
908
|
};
|
|
731
|
-
const after = { facts: [] as string[], state: [] as string[], amnesia: [] as string[], holds: [] as string[], readBack: [] as string[] };
|
|
909
|
+
const after = { facts: [] as string[], state: [] as string[], amnesia: [] as string[], holds: [] as string[], readBack: [] as string[], concurrent: [] as string[] };
|
|
732
910
|
const facts: { before: unknown; after: unknown } = { before: undefined, after: undefined };
|
|
733
911
|
const results: WriterStepResult[] = [];
|
|
734
912
|
try {
|
|
735
913
|
let built = writer(transport, { stateDir });
|
|
736
914
|
const done: Record<string, Record<string, unknown>> = {};
|
|
737
915
|
for (const s of WRITER_SCRIPT) {
|
|
738
|
-
const st = buildStep(s, done);
|
|
916
|
+
const st = buildStep(s, done, inputs);
|
|
739
917
|
if (!checked.includes(st.action)) {
|
|
740
918
|
const doc = await direct(["workspace", ...st.action.split(" "), ...st.args, ...WRITE_CONTRACT_JSON_FLAGS[st.action][0]], st.input);
|
|
741
919
|
done[st.id] = doc;
|
|
@@ -765,6 +943,11 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
|
|
|
765
943
|
results.push({ id: st.id, action: st.action, by: "writer", args: st.args, problems });
|
|
766
944
|
}
|
|
767
945
|
|
|
946
|
+
// Concurrent writes (#3173): three amendments of one record at once, each from the digest read.
|
|
947
|
+
if (checked.includes("records amend")) {
|
|
948
|
+
after.concurrent.push(...(await concurrentAmends(built, calls, direct, String(done.decision?.id ?? ""), ws.dir)));
|
|
949
|
+
}
|
|
950
|
+
|
|
768
951
|
// What the writer shows, read only through the read contract.
|
|
769
952
|
const readFacts = async (w: WorkspaceWriter, when: string): Promise<unknown> => {
|
|
770
953
|
const files = worktreeDigest(ws.dir);
|
|
@@ -849,13 +1032,22 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
|
|
|
849
1032
|
}
|
|
850
1033
|
const runs = new Map(((await direct(["workspace", "runs", "--json"])).runs as { id: string; state: string }[] | undefined ?? []).map((r) => [r.id, r.state]));
|
|
851
1034
|
const history = await direct(["workspace", "work", "history", "W-001", "--kind", WRITER_KINDS.work, "--json"]);
|
|
1035
|
+
const listings = await readListing((argv) => runChant(chantCommand, argv, ws.dir, timeoutMs));
|
|
1036
|
+
const wipDoc = await direct(["workspace", "wip", "--json"]);
|
|
1037
|
+
const snapshots = new Set(((wipDoc.branches ?? []) as { snapshots: { commit: string }[] }[]).flatMap((b) => b.snapshots.map((x) => x.commit)));
|
|
852
1038
|
const claims = (history.claims ?? []) as { token: string; ended: string | null; release?: { outcome?: string } | null }[];
|
|
1039
|
+
const questions = new Map(((await direct(["workspace", "points", "--kind", WRITER_KINDS.answer, "--json"])).questions as { id: string; retractions?: unknown[]; asked?: unknown }[] | undefined ?? []).map((q) => [q.id, q]));
|
|
853
1040
|
for (const s of WRITER_SCRIPT) {
|
|
854
1041
|
const doc = done[s.id] ?? {};
|
|
855
1042
|
if (s.action.startsWith("records ") || s.action.startsWith("points ")) {
|
|
856
1043
|
const kind = String((doc.kind as { file?: unknown } | undefined)?.file ?? "");
|
|
857
1044
|
const id = String(doc.id ?? "");
|
|
858
1045
|
if (!uncommitted.get(kind)?.has(id)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote ${id}, and records --uncommitted --kind ${kind} does not list it`);
|
|
1046
|
+
else if (s.action === "points retract" && !(questions.get(id)?.retractions?.length ?? 0)) after.readBack.push(`read back: ${s.id} (${s.action}) retracted the answer to ${id}, and points --json lists no retraction for it`);
|
|
1047
|
+
else if (s.action === "points ask") {
|
|
1048
|
+
const asked = (doc.question as { asked?: unknown } | undefined)?.asked;
|
|
1049
|
+
if (asked && !isDeepStrictEqual(questions.get(id)?.asked, asked)) after.readBack.push(`read back: ${s.id} (${s.action}) asked ${id} with its own question and candidates, and points --json does not list them as asked`);
|
|
1050
|
+
}
|
|
859
1051
|
} else if (s.action === "work evidence") {
|
|
860
1052
|
if (!uncommitted.get(WRITER_KINDS.work)?.has(String(doc.item ?? ""))) after.readBack.push(`read back: ${s.id} (${s.action}) amended ${String(doc.item)}, and records --uncommitted does not list it`);
|
|
861
1053
|
} else if (s.action.startsWith("work ")) {
|
|
@@ -868,13 +1060,132 @@ export async function runWorkspaceWriterConformance(writer: WorkspaceWriterFacto
|
|
|
868
1060
|
const want = s.action === "runs start" ? undefined : "ended";
|
|
869
1061
|
if (!runs.has(id)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote run ${id}, and runs does not list it`);
|
|
870
1062
|
else if (want && runs.get(id) !== want) after.readBack.push(`read back: ${s.id} (${s.action}) ended run ${id}, and runs says it is ${runs.get(id)}`);
|
|
1063
|
+
} else if (s.action === "box listing set") {
|
|
1064
|
+
const member = String(doc.member ?? "");
|
|
1065
|
+
if (!isDeepStrictEqual(listings[member], doc.listing)) after.readBack.push(`read back: ${s.id} (${s.action}) set ${member}'s listing to ${brief(doc.listing)}, and status --json says ${brief(listings[member] ?? null)}`);
|
|
1066
|
+
} else if (s.action === "wip save" || s.action === "wip restore") {
|
|
1067
|
+
const snap = (s.action === "wip save" ? doc.snapshot : (doc.checkpoint as Record<string, unknown> | undefined)?.snapshot) as { commit?: unknown } | undefined;
|
|
1068
|
+
const commit = String(snap?.commit ?? "");
|
|
1069
|
+
if (!snapshots.has(commit)) after.readBack.push(`read back: ${s.id} (${s.action}) wrote snapshot ${commit}, and wip --json does not list it`);
|
|
871
1070
|
}
|
|
872
1071
|
}
|
|
873
1072
|
|
|
874
|
-
const problems = [...results.flatMap((r) => r.problems), ...after.facts, ...after.state, ...after.amnesia, ...after.holds, ...after.readBack];
|
|
1073
|
+
const problems = [...results.flatMap((r) => r.problems), ...after.concurrent, ...after.facts, ...after.state, ...after.amnesia, ...after.holds, ...after.readBack];
|
|
875
1074
|
return { problems, checked, skipped, results, after, facts, workspaceDir: ws.dir };
|
|
876
1075
|
} finally {
|
|
877
1076
|
rmSync(stateDir, { recursive: true, force: true });
|
|
1077
|
+
rmSync(inputsDir, { recursive: true, force: true });
|
|
878
1078
|
ws.dispose();
|
|
879
1079
|
}
|
|
880
1080
|
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* The concurrent case (#3173, ws-089): the writer makes the three
|
|
1084
|
+
* {@link CONCURRENT_AMENDS} of record `id` at once, each from the digest the
|
|
1085
|
+
* suite read. Exactly one is written and the others are refused with
|
|
1086
|
+
* record-conflict naming its digest; each is then retried from the digest
|
|
1087
|
+
* its refusal named, and written. Returns the problems, each starting
|
|
1088
|
+
* `concurrent:`.
|
|
1089
|
+
*/
|
|
1090
|
+
export async function concurrentAmends(
|
|
1091
|
+
built: WorkspaceWriter,
|
|
1092
|
+
calls: ChantRun[],
|
|
1093
|
+
direct: (argv: string[], input?: string) => Promise<Record<string, unknown>>,
|
|
1094
|
+
id: string,
|
|
1095
|
+
dir: string,
|
|
1096
|
+
): Promise<string[]> {
|
|
1097
|
+
const problems: string[] = [];
|
|
1098
|
+
const digestOf = async (): Promise<{ digest: string; path: string } | undefined> => {
|
|
1099
|
+
const doc = await direct(["workspace", "records", "--kind", WRITER_KINDS.decision, "--json"]);
|
|
1100
|
+
const r = ((doc.records ?? []) as { id: string; digest: string; path: string }[]).find((x) => x.id === id);
|
|
1101
|
+
return r ? { digest: r.digest, path: r.path } : undefined;
|
|
1102
|
+
};
|
|
1103
|
+
const start = await digestOf();
|
|
1104
|
+
if (!start) return [`concurrent: the script's decision ${id || "(none)"} can't be read back to amend`];
|
|
1105
|
+
const files = { before: worktreeDigest(dir), after: {} as Record<string, string> };
|
|
1106
|
+
const refs = { before: gitRefs(dir), after: {} as Record<string, string> };
|
|
1107
|
+
const steps = CONCURRENT_AMENDS.map((c) => concurrentAmendStep(c, id, start.digest));
|
|
1108
|
+
calls.length = 0;
|
|
1109
|
+
const docs = await Promise.all(
|
|
1110
|
+
steps.map(async (st) => {
|
|
1111
|
+
try {
|
|
1112
|
+
return { st, doc: await built.write(st) };
|
|
1113
|
+
} catch (e) {
|
|
1114
|
+
problems.push(`concurrent: ${st.id} (${st.action}): the writer threw: ${(e as Error).message}`);
|
|
1115
|
+
return { st, doc: undefined };
|
|
1116
|
+
}
|
|
1117
|
+
}),
|
|
1118
|
+
);
|
|
1119
|
+
// Each step's call: the one that gave chant its fields.
|
|
1120
|
+
const printed = new Map<string, Record<string, unknown> | undefined>();
|
|
1121
|
+
for (const { st, doc } of docs) {
|
|
1122
|
+
const mine = calls.filter((c) => c.input !== undefined && st.input !== undefined && isDeepStrictEqual(safeJson(c.input), JSON.parse(st.input)));
|
|
1123
|
+
const callProblems = writerCallProblems(st, mine);
|
|
1124
|
+
if (callProblems.length > 0) {
|
|
1125
|
+
problems.push(...callProblems.map((p) => `concurrent: ${p}`));
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
const out = safeJson(mine[0].stdout) as Record<string, unknown> | undefined;
|
|
1129
|
+
printed.set(st.id, out);
|
|
1130
|
+
if (!isDeepStrictEqual(doc, out)) problems.push(`concurrent: ${st.id} (${st.action}): the writer must return the document chant printed, unchanged, refusal included, and it returned something else`);
|
|
1131
|
+
const { validate } = writeContractSchema(st.action);
|
|
1132
|
+
if (!validate(out)) problems.push(`concurrent: ${st.id} (${st.action}): the document does not validate against ${WRITE_CONTRACT_SCHEMAS[st.action]}: ${JSON.stringify(validate.errors)}`);
|
|
1133
|
+
}
|
|
1134
|
+
if (problems.length > 0) return problems;
|
|
1135
|
+
const won = steps.filter((st) => printed.get(st.id)?.error === undefined);
|
|
1136
|
+
const lost = steps.filter((st) => printed.get(st.id)?.error !== undefined);
|
|
1137
|
+
if (won.length !== 1) {
|
|
1138
|
+
problems.push(`concurrent: ${won.length} of the three amendments from digest ${start.digest} were written (${won.map((s) => s.id).join(", ") || "none"}); exactly one may be`);
|
|
1139
|
+
return problems;
|
|
1140
|
+
}
|
|
1141
|
+
const d1 = String(printed.get(won[0].id)?.digest ?? "");
|
|
1142
|
+
for (const st of lost) {
|
|
1143
|
+
const doc = printed.get(st.id)!;
|
|
1144
|
+
const code = (doc.error as { code?: string }).code;
|
|
1145
|
+
const conflict = doc.conflict as { digest?: string; expected?: string } | undefined;
|
|
1146
|
+
if (code !== "record-conflict") problems.push(`concurrent: ${st.id} was refused with ${code}, not record-conflict`);
|
|
1147
|
+
else if (conflict?.digest !== d1 || conflict?.expected !== start.digest) problems.push(`concurrent: ${st.id}'s conflict names ${brief(conflict)}, not expected ${start.digest} and digest ${d1}`);
|
|
1148
|
+
}
|
|
1149
|
+
files.after = worktreeDigest(dir);
|
|
1150
|
+
refs.after = gitRefs(dir);
|
|
1151
|
+
const changed = treeChanges(files.before, files.after).filter((c) => c.replace(/ \((added|changed|removed)\)$/, "") !== start.path);
|
|
1152
|
+
if (changed.length > 0) problems.push(`concurrent: files changed that chant did not report writing: ${changed.join(", ")}`);
|
|
1153
|
+
if (Object.keys(refChanges(refs.before, refs.after)).length > 0) problems.push(`concurrent: git refs changed: ${Object.keys(refChanges(refs.before, refs.after)).join(", ")}`);
|
|
1154
|
+
if (problems.length > 0) return problems;
|
|
1155
|
+
// Each loser retries from the digest it was told, one after the other.
|
|
1156
|
+
let digest = d1;
|
|
1157
|
+
for (const st of lost) {
|
|
1158
|
+
const retry = concurrentAmendStep(CONCURRENT_AMENDS.find((c) => c.id === st.id)!, id, digest);
|
|
1159
|
+
calls.length = 0;
|
|
1160
|
+
let doc: unknown;
|
|
1161
|
+
try {
|
|
1162
|
+
doc = await built.write(retry);
|
|
1163
|
+
} catch (e) {
|
|
1164
|
+
problems.push(`concurrent: ${st.id}'s retry: the writer threw: ${(e as Error).message}`);
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
const p = writerCallProblems({ ...retry, id: `${st.id} retry` } as WriteStep, calls);
|
|
1168
|
+
if (p.length > 0) {
|
|
1169
|
+
problems.push(...p.map((x) => `concurrent: ${x}`));
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
const out = safeJson(calls[0].stdout) as Record<string, unknown> | undefined;
|
|
1173
|
+
if (out?.error !== undefined) {
|
|
1174
|
+
problems.push(`concurrent: ${st.id}'s retry from ${digest} was refused: ${brief(out.error)}`);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
if (!isDeepStrictEqual(doc, out)) problems.push(`concurrent: ${st.id}'s retry: the writer must return the document chant printed, unchanged`);
|
|
1178
|
+
digest = String(out?.digest ?? "");
|
|
1179
|
+
}
|
|
1180
|
+
const end = await digestOf();
|
|
1181
|
+
if (problems.length === 0 && end?.digest !== digest) problems.push(`concurrent: after the retries records --json gives ${id} digest ${end?.digest}, and the last retry printed ${digest}`);
|
|
1182
|
+
return problems;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function safeJson(text: string): unknown {
|
|
1186
|
+
try {
|
|
1187
|
+
return JSON.parse(text);
|
|
1188
|
+
} catch {
|
|
1189
|
+
return undefined;
|
|
1190
|
+
}
|
|
1191
|
+
}
|