@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
|
@@ -19,12 +19,27 @@
|
|
|
19
19
|
* as well, after checking both files, so the two lists never differ.
|
|
20
20
|
* `records close` is in `records-close.ts`.
|
|
21
21
|
*
|
|
22
|
+
* Each write holds the working tree's write lock (`write-lock.ts`, #3173,
|
|
23
|
+
* ws-089) from its first read to its last write, so writes to one working
|
|
24
|
+
* tree never interleave. `amend`, `review` and `close` take `--expect
|
|
25
|
+
* <digest>`: the record's digest the caller last read. When the record has
|
|
26
|
+
* moved on since, the write is refused with record-conflict, naming the
|
|
27
|
+
* digest it has now and the last chant write of it, and the caller re-reads
|
|
28
|
+
* and tries again.
|
|
29
|
+
*
|
|
22
30
|
* `new --sign` and `amend --sign` seal the record's author (#2688), and
|
|
23
31
|
* `review --sign` seals a verdict (#2687): see `trust/seal.ts`. A record of
|
|
24
32
|
* a kind that declares `seal` gets its whole-file seal when `new` or `amend`
|
|
25
33
|
* writes it into a closed state (#2546, ws-063).
|
|
34
|
+
*
|
|
35
|
+
* `new` also writes a record of a content-addressed JSON kind (`format:
|
|
36
|
+
* "json"`, `idFrom: "sha256"`, ws-053), such as the reference workspace's
|
|
37
|
+
* evidence (ws-082): the fields as JSON, in a file named for the SHA-256 of
|
|
38
|
+
* its bytes. Such a record never changes, so amend, review and close refuse
|
|
39
|
+
* the kind.
|
|
26
40
|
*/
|
|
27
41
|
|
|
42
|
+
import { createHash } from "node:crypto";
|
|
28
43
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
29
44
|
import { dirname, join, posix, relative, resolve } from "node:path";
|
|
30
45
|
import type { CommandContext } from "../cli/registry";
|
|
@@ -35,6 +50,7 @@ import {
|
|
|
35
50
|
loadRecordKind,
|
|
36
51
|
parseFrontMatter,
|
|
37
52
|
readRecords,
|
|
53
|
+
readSessionSubjects,
|
|
38
54
|
RECORD_REASON_CODES,
|
|
39
55
|
RecordReadError,
|
|
40
56
|
RECORD_SEAL_FIELD,
|
|
@@ -53,6 +69,7 @@ import { workingTree } from "./tree";
|
|
|
53
69
|
import { AGENT_ENV, refuseRecordWrite, WRITE_SCOPE_CODES, WriteScopeError, type ScopeSource } from "./write-scope";
|
|
54
70
|
import { IDENTITY_CODES, IdentityError, refuseUnidentified } from "./identity";
|
|
55
71
|
import type { WriteVerb } from "./declaration";
|
|
72
|
+
import { lastWriteReader, noteWrites, withWriteLock, writeFileAtomic, WriteLockError, WRITE_LOCK_CODES, type LastWrite, type WriteLockWho } from "./write-lock";
|
|
56
73
|
|
|
57
74
|
// ── Contract ─────────────────────────────────────────────────────────────────
|
|
58
75
|
|
|
@@ -78,6 +95,7 @@ export const NEW_ERROR_CODES = [
|
|
|
78
95
|
"source-harvest-not-proposed",
|
|
79
96
|
"record-state-not-initial",
|
|
80
97
|
"ratify-quorum-not-met",
|
|
98
|
+
...WRITE_LOCK_CODES,
|
|
81
99
|
...WRITE_SCOPE_CODES,
|
|
82
100
|
...IDENTITY_CODES,
|
|
83
101
|
...RECORD_REASON_CODES,
|
|
@@ -94,6 +112,8 @@ export const AMEND_ERROR_CODES = [
|
|
|
94
112
|
"amend-supersede-instead",
|
|
95
113
|
"record-sign-failed",
|
|
96
114
|
"ratify-quorum-not-met",
|
|
115
|
+
"record-conflict",
|
|
116
|
+
...WRITE_LOCK_CODES,
|
|
97
117
|
...WRITE_SCOPE_CODES,
|
|
98
118
|
...IDENTITY_CODES,
|
|
99
119
|
...RECORD_REASON_CODES,
|
|
@@ -110,6 +130,8 @@ export const REVIEW_ERROR_CODES = [
|
|
|
110
130
|
"review-sign-failed",
|
|
111
131
|
"session-unknown",
|
|
112
132
|
"session-not-open",
|
|
133
|
+
"record-conflict",
|
|
134
|
+
...WRITE_LOCK_CODES,
|
|
113
135
|
...WRITE_SCOPE_CODES,
|
|
114
136
|
...IDENTITY_CODES,
|
|
115
137
|
...RECORD_REASON_CODES,
|
|
@@ -127,12 +149,28 @@ export class RecordWriteError extends Error {
|
|
|
127
149
|
constructor(
|
|
128
150
|
readonly code: WriteErrorCode,
|
|
129
151
|
message: string,
|
|
152
|
+
/** With record-conflict: the record as it is now. */
|
|
153
|
+
readonly conflict?: RecordConflict,
|
|
130
154
|
) {
|
|
131
155
|
super(message);
|
|
132
156
|
this.name = "RecordWriteError";
|
|
133
157
|
}
|
|
134
158
|
}
|
|
135
159
|
|
|
160
|
+
/**
|
|
161
|
+
* What a record-conflict refusal says about the record (#3173): the digest
|
|
162
|
+
* the caller expected, the one it has now, and the last chant write of it
|
|
163
|
+
* when the journal knows it. The caller re-reads the record and writes again
|
|
164
|
+
* with `--expect` set to `digest`.
|
|
165
|
+
*/
|
|
166
|
+
export interface RecordConflict {
|
|
167
|
+
id: string;
|
|
168
|
+
path: string;
|
|
169
|
+
expected: string;
|
|
170
|
+
digest: string;
|
|
171
|
+
lastWrite: LastWrite | null;
|
|
172
|
+
}
|
|
173
|
+
|
|
136
174
|
export interface KindView {
|
|
137
175
|
name: string;
|
|
138
176
|
schema: string;
|
|
@@ -151,6 +189,12 @@ export interface WriteResult {
|
|
|
151
189
|
dryRun: boolean;
|
|
152
190
|
/** The written record's warnings, as `records` would report them. */
|
|
153
191
|
warnings: RecordWarning[];
|
|
192
|
+
/**
|
|
193
|
+
* The record's digest after the write, as `records --json` reports it
|
|
194
|
+
* (#3173): what the caller passes as `--expect` to its next write of the
|
|
195
|
+
* record.
|
|
196
|
+
*/
|
|
197
|
+
digest: string;
|
|
154
198
|
/** With --dry-run, the whole text the command would write. */
|
|
155
199
|
text?: string;
|
|
156
200
|
}
|
|
@@ -159,6 +203,8 @@ export interface WriteFailure<C> {
|
|
|
159
203
|
$schema: string;
|
|
160
204
|
contract: number;
|
|
161
205
|
error: { code: C; message: string };
|
|
206
|
+
/** With record-conflict (#3173): the record as it is now. */
|
|
207
|
+
conflict?: RecordConflict;
|
|
162
208
|
}
|
|
163
209
|
|
|
164
210
|
/** A record's author seal as `new` and `amend` write it with `--sign` (#2688). */
|
|
@@ -339,15 +385,26 @@ export interface Opened {
|
|
|
339
385
|
dirRel: string;
|
|
340
386
|
}
|
|
341
387
|
|
|
342
|
-
|
|
388
|
+
/** A kind `records new` writes as one JSON file named for the SHA-256 of its bytes (ws-053, ws-082). */
|
|
389
|
+
export function isContentAddressed(kind: LoadedRecordKind["kind"]): boolean {
|
|
390
|
+
return kind.format === "json" && kind.idFrom === "sha256";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function open(kind: string, cwd: string, opts: { contentAddressed?: boolean } = {}): Promise<Opened> {
|
|
343
394
|
const real = realpathOr(cwd);
|
|
344
395
|
const root = gitRoot(real) ?? real;
|
|
345
396
|
const loaded = await loadRecordKind(kind, real);
|
|
346
|
-
// The writers write Markdown front matter with an id field.
|
|
347
|
-
// content-addressed kind (ws-
|
|
348
|
-
|
|
397
|
+
// The writers write Markdown front matter with an id field. `records new`
|
|
398
|
+
// also writes a content-addressed JSON kind (ws-082), whose record never
|
|
399
|
+
// changes once written, so amend, review and close never open one. A JSON
|
|
400
|
+
// kind with an id field is read by records, and written by its own tool.
|
|
401
|
+
const writable = (loaded.kind.format === "markdown-front-matter" && loaded.kind.idField !== undefined) || (!!opts.contentAddressed && isContentAddressed(loaded.kind));
|
|
402
|
+
if (!writable) {
|
|
349
403
|
const what = loaded.kind.format !== "markdown-front-matter" ? `format ${loaded.kind.format}` : `ids from ${loaded.kind.idFrom}`;
|
|
350
|
-
|
|
404
|
+
const verbs = isContentAddressed(loaded.kind)
|
|
405
|
+
? "a content-addressed record never changes once records new writes it, so amend, review and close never write one"
|
|
406
|
+
: "records new, amend and review write only Markdown front matter records with an idField, and records new a JSON record named for the sha256 of its bytes";
|
|
407
|
+
throw new RecordWriteError("write-usage-invalid", `the ${loaded.kind.name} kind has ${what}: ${verbs}`);
|
|
351
408
|
}
|
|
352
409
|
const dirRel = relative(root, loaded.dir).split("\\").join("/") || ".";
|
|
353
410
|
return {
|
|
@@ -392,11 +449,7 @@ function refuseUnidentifiedAuthors(source: ScopeSource, kind: Opened["loaded"]["
|
|
|
392
449
|
*/
|
|
393
450
|
export async function readAll(o: Opened, source: RecordSource): Promise<RecordEntry[]> {
|
|
394
451
|
const assets = workingTree(o.workspaceRoot === "." ? o.root : join(o.root, ...o.workspaceRoot.split("/")));
|
|
395
|
-
|
|
396
|
-
if (o.loaded.kind.session) {
|
|
397
|
-
const subjectKind = await loadRecordKind(resolve(dirname(o.loaded.file), o.loaded.kind.session.subjects.kind), o.root);
|
|
398
|
-
subjects = { records: (await readRecords(subjectKind, { root: o.root, source })).records, reviews: subjectKind.kind.reviews?.field ?? "reviews" };
|
|
399
|
-
}
|
|
452
|
+
const subjects = await readSessionSubjects(o.loaded, o.root, source);
|
|
400
453
|
return (await readRecords(o.loaded, { root: o.root, source, assets, workspaceRoot: o.workspaceRoot, ...(subjects ? { subjects } : {}) })).records;
|
|
401
454
|
}
|
|
402
455
|
|
|
@@ -470,6 +523,35 @@ async function refuseRatifyBelowQuorum(o: Opened, written: RecordEntry, id: stri
|
|
|
470
523
|
);
|
|
471
524
|
}
|
|
472
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Refuse a write to `target` when the caller expected another digest
|
|
528
|
+
* (#3173): someone wrote the record since the caller read it. Called holding
|
|
529
|
+
* the write lock, after the records are read, so the digest compared is the
|
|
530
|
+
* one the write would build on.
|
|
531
|
+
*/
|
|
532
|
+
export function refuseStale(o: Opened, target: RecordEntry, id: string, expect: string | undefined): void {
|
|
533
|
+
if (expect === undefined || expect === target.digest) return;
|
|
534
|
+
const lastWrite = lastWriteReader(o.root)(target.path, o.source.read(target.path));
|
|
535
|
+
const who = lastWrite ? ` It was last written by chant workspace ${lastWrite.verb}${lastWrite.by ? ` for ${lastWrite.by}` : ""}${lastWrite.agent ? ` in agent session ${lastWrite.agent}` : ""} at ${lastWrite.at}.` : "";
|
|
536
|
+
throw new RecordWriteError(
|
|
537
|
+
"record-conflict",
|
|
538
|
+
`${id} has changed since the digest given with --expect (${expect}): its digest is now ${target.digest}.${who} Read it again with chant workspace records --json, and write again with --expect ${target.digest} if the change still stands`,
|
|
539
|
+
{ id, path: target.path, expected: expect, digest: target.digest, lastWrite },
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Refuse a --expect that is not a digest, before anything is read. */
|
|
544
|
+
function refuseExpectShape(expect: string | undefined): void {
|
|
545
|
+
if (expect !== undefined && !/^[0-9a-f]{64}$/.test(expect)) {
|
|
546
|
+
throw new RecordWriteError("write-usage-invalid", `--expect takes a record's digest, 64 lowercase hex digits as records --json prints it, not ${JSON.stringify(expect)}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Who a write names, for the lock's owner file and the journal. */
|
|
551
|
+
export function writeWho(verb: string, opts: { by?: string; agent?: string }): WriteLockWho {
|
|
552
|
+
return { verb, by: opts.by ?? null, agent: opts.agent ?? null };
|
|
553
|
+
}
|
|
554
|
+
|
|
473
555
|
export function findRecord(entries: RecordEntry[], id: string, kind: string): RecordEntry & { data: Record<string, unknown> } {
|
|
474
556
|
const hits = entries.filter((e) => e.id === id);
|
|
475
557
|
if (hits.length === 0) throw new RecordWriteError("record-not-found", `no ${kind} record has id ${id}`);
|
|
@@ -648,8 +730,9 @@ function today(): string {
|
|
|
648
730
|
}
|
|
649
731
|
|
|
650
732
|
export function failure<C>(schema: string, err: unknown): WriteFailure<C> {
|
|
651
|
-
if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError || err instanceof IdentityError) {
|
|
652
|
-
|
|
733
|
+
if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError || err instanceof IdentityError || err instanceof WriteLockError) {
|
|
734
|
+
const conflict = err instanceof RecordWriteError ? err.conflict : undefined;
|
|
735
|
+
return { $schema: schema, contract: RECORDS_WRITE_CONTRACT_VERSION, error: { code: err.code as C, message: err.message }, ...(conflict ? { conflict } : {}) };
|
|
653
736
|
}
|
|
654
737
|
throw err;
|
|
655
738
|
}
|
|
@@ -786,19 +869,28 @@ function applyChannel(o: Opened, fields: Record<string, unknown>, opts: ChannelO
|
|
|
786
869
|
return out;
|
|
787
870
|
}
|
|
788
871
|
|
|
789
|
-
/** `records new`: write one new record from validated fields, sealed by its author with `sign`. */
|
|
872
|
+
/** `records new`: write one new record from validated fields, sealed by its author with `sign`. Holds the write lock (#3173). */
|
|
790
873
|
export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promise<NewDocument> {
|
|
874
|
+
try {
|
|
875
|
+
return await withWriteLock(opts.cwd, writeWho("records new", opts), opts.dryRun, () => newRecordLocked(opts));
|
|
876
|
+
} catch (err) {
|
|
877
|
+
return failure<NewErrorCode>(RECORDS_NEW_SCHEMA_ID, err);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async function newRecordLocked(opts: NewRecordOptions & ChannelOptions): Promise<NewDocument> {
|
|
791
882
|
try {
|
|
792
883
|
if (opts.prefix !== undefined && !/^[A-Za-z][A-Za-z0-9]*$/.test(opts.prefix)) {
|
|
793
884
|
throw new RecordWriteError("write-usage-invalid", `--prefix takes letters and digits, starting with a letter, not ${JSON.stringify(opts.prefix)}`);
|
|
794
885
|
}
|
|
795
|
-
const o = await open(opts.kind, opts.cwd);
|
|
886
|
+
const o = await open(opts.kind, opts.cwd, { contentAddressed: true });
|
|
796
887
|
const scope = refuseOutOfScope(o, "new", opts.cwd, opts);
|
|
797
888
|
const fields = applyChannel(o, parseFields(opts.fields, "--from"), opts, true, "--from");
|
|
798
889
|
const { kind, schema } = o.loaded;
|
|
799
890
|
refuseUnidentifiedAuthors(scope, kind, fields, "--from", opts.agent);
|
|
800
891
|
refuseSealField(o, fields, "--from");
|
|
801
892
|
refuseRevisionFields(kind, fields, {}, "--from");
|
|
893
|
+
if (isContentAddressed(kind)) return await newContentAddressed(o, fields, opts);
|
|
802
894
|
const idField = kind.idField!;
|
|
803
895
|
const before = await readAll(o, o.source);
|
|
804
896
|
const given = fields[idField];
|
|
@@ -830,7 +922,10 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
830
922
|
const written = await validatedEntry(o, before, path, text);
|
|
831
923
|
await refuseRatifyBelowQuorum(o, written, id);
|
|
832
924
|
const warnings = written.warnings;
|
|
833
|
-
if (!opts.dryRun)
|
|
925
|
+
if (!opts.dryRun) {
|
|
926
|
+
writeFileSync(abs(o, path), text, { flag: "wx" });
|
|
927
|
+
noteWrites(o.root, [{ path, text }], writeWho("records new", opts));
|
|
928
|
+
}
|
|
834
929
|
return {
|
|
835
930
|
$schema: RECORDS_NEW_SCHEMA_ID,
|
|
836
931
|
contract: RECORDS_WRITE_CONTRACT_VERSION,
|
|
@@ -840,6 +935,7 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
840
935
|
...(seal ? { seal } : {}),
|
|
841
936
|
dryRun: !!opts.dryRun,
|
|
842
937
|
warnings,
|
|
938
|
+
digest: written.digest,
|
|
843
939
|
...(opts.dryRun ? { text } : {}),
|
|
844
940
|
};
|
|
845
941
|
} catch (err) {
|
|
@@ -847,6 +943,43 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
847
943
|
}
|
|
848
944
|
}
|
|
849
945
|
|
|
946
|
+
/**
|
|
947
|
+
* `records new` on a content-addressed JSON kind (ws-053, ws-082), such as
|
|
948
|
+
* the reference workspace's evidence: the fields, in the schema's order, as
|
|
949
|
+
* JSON with two-space indents and a final newline, in a file named for the
|
|
950
|
+
* SHA-256 of those bytes. The id is that hash, so the fields give no id, and
|
|
951
|
+
* the record never changes afterwards.
|
|
952
|
+
*/
|
|
953
|
+
async function newContentAddressed(o: Opened, fields: Record<string, unknown>, opts: NewRecordOptions): Promise<NewDocument> {
|
|
954
|
+
const { kind, schema } = o.loaded;
|
|
955
|
+
if (opts.prefix !== undefined) throw new RecordWriteError("write-usage-invalid", `the ${kind.name} kind's ids are the sha256 of each record's bytes, so --prefix allocates nothing`);
|
|
956
|
+
if (opts.sign !== undefined) throw new RecordWriteError("write-usage-invalid", `the ${kind.name} kind names no author to seal: its records are named for the sha256 of their bytes`);
|
|
957
|
+
const data = pick(fields, schemaOrder(fields, schema));
|
|
958
|
+
const text = `${JSON.stringify(data, null, 2)}\n`;
|
|
959
|
+
const id = createHash("sha256").update(text, "utf-8").digest("hex");
|
|
960
|
+
const name = `${id}.json`;
|
|
961
|
+
if (!new RegExp(kind.location.match).test(name)) throw new RecordWriteError("record-path-unmatched", `the kind's location.match ${kind.location.match} does not match ${name}`);
|
|
962
|
+
const path = o.dirRel === "." ? name : `${o.dirRel}/${name}`;
|
|
963
|
+
if (o.source.list(o.dirRel)?.includes(name)) throw new RecordWriteError("record-id-taken", `${path} already exists, with the same bytes: the record is already written`);
|
|
964
|
+
const before = await readAll(o, o.source);
|
|
965
|
+
const written = await validatedEntry(o, before, path, text);
|
|
966
|
+
if (!opts.dryRun) {
|
|
967
|
+
writeFileSync(abs(o, path), text, { flag: "wx" });
|
|
968
|
+
noteWrites(o.root, [{ path, text }], writeWho("records new", opts));
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
$schema: RECORDS_NEW_SCHEMA_ID,
|
|
972
|
+
contract: RECORDS_WRITE_CONTRACT_VERSION,
|
|
973
|
+
kind: o.view,
|
|
974
|
+
path,
|
|
975
|
+
id,
|
|
976
|
+
dryRun: !!opts.dryRun,
|
|
977
|
+
warnings: written.warnings,
|
|
978
|
+
digest: written.digest,
|
|
979
|
+
...(opts.dryRun ? { text } : {}),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
850
983
|
// ── records amend ────────────────────────────────────────────────────────────
|
|
851
984
|
|
|
852
985
|
export interface AmendRecordOptions {
|
|
@@ -860,6 +993,8 @@ export interface AmendRecordOptions {
|
|
|
860
993
|
sign?: string | true;
|
|
861
994
|
/** The agent session the write is made in (#2548). */
|
|
862
995
|
agent?: string;
|
|
996
|
+
/** The record's digest the caller last read (#3173): the write is refused with record-conflict when the record has another. */
|
|
997
|
+
expect?: string;
|
|
863
998
|
}
|
|
864
999
|
|
|
865
1000
|
/**
|
|
@@ -877,6 +1012,15 @@ export interface AmendRecordOptions {
|
|
|
877
1012
|
* `--sign` with nothing to change seals the record as it is.
|
|
878
1013
|
*/
|
|
879
1014
|
export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Promise<AmendDocument> {
|
|
1015
|
+
try {
|
|
1016
|
+
refuseExpectShape(opts.expect);
|
|
1017
|
+
return await withWriteLock(opts.cwd, writeWho("records amend", opts), opts.dryRun, () => amendRecordLocked(opts));
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
return failure<AmendErrorCode>(RECORDS_AMEND_SCHEMA_ID, err);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
async function amendRecordLocked(opts: AmendRecordOptions & ChannelOptions): Promise<AmendDocument> {
|
|
880
1024
|
try {
|
|
881
1025
|
const o = await open(opts.kind, opts.cwd);
|
|
882
1026
|
const scope = refuseOutOfScope(o, "amend", opts.cwd, opts);
|
|
@@ -886,6 +1030,7 @@ export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Pr
|
|
|
886
1030
|
refuseSealField(o, given, "--set");
|
|
887
1031
|
const before = await readAll(o, o.source);
|
|
888
1032
|
const target = findRecord(before, opts.id, kind.name);
|
|
1033
|
+
refuseStale(o, target, opts.id, opts.expect);
|
|
889
1034
|
const old = target.data;
|
|
890
1035
|
refuseRevisionFields(kind, given, old, "--set");
|
|
891
1036
|
const isClosed = target.state !== null && (kind.closedStates ?? []).includes(target.state);
|
|
@@ -951,12 +1096,17 @@ export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Pr
|
|
|
951
1096
|
if (stableJson(old[kind.seal.field]) !== stableJson(closedSeal.data[kind.seal.field])) changed.push(kind.seal.field);
|
|
952
1097
|
}
|
|
953
1098
|
let warnings = target.warnings;
|
|
1099
|
+
let digest = target.digest;
|
|
954
1100
|
if (changed.length > 0) {
|
|
955
1101
|
const written = await validatedEntry(o, before, target.path, text);
|
|
956
1102
|
if (kind.stateField !== undefined && changed.includes(kind.stateField)) await refuseRatifyBelowQuorum(o, written, opts.id);
|
|
957
1103
|
warnings = written.warnings;
|
|
1104
|
+
digest = written.digest;
|
|
1105
|
+
}
|
|
1106
|
+
if (!opts.dryRun && changed.length > 0) {
|
|
1107
|
+
writeFileAtomic(abs(o, target.path), text);
|
|
1108
|
+
noteWrites(o.root, [{ path: target.path, text }], writeWho("records amend", opts));
|
|
958
1109
|
}
|
|
959
|
-
if (!opts.dryRun && changed.length > 0) writeFileSync(abs(o, target.path), text);
|
|
960
1110
|
return {
|
|
961
1111
|
$schema: RECORDS_AMEND_SCHEMA_ID,
|
|
962
1112
|
contract: RECORDS_WRITE_CONTRACT_VERSION,
|
|
@@ -968,6 +1118,7 @@ export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Pr
|
|
|
968
1118
|
...(sealDropped ? { sealDropped } : {}),
|
|
969
1119
|
dryRun: !!opts.dryRun,
|
|
970
1120
|
warnings,
|
|
1121
|
+
digest,
|
|
971
1122
|
...(opts.dryRun ? { text } : {}),
|
|
972
1123
|
};
|
|
973
1124
|
} catch (err) {
|
|
@@ -997,6 +1148,8 @@ export interface ReviewRecordOptions {
|
|
|
997
1148
|
sign?: string | true;
|
|
998
1149
|
/** The agent session the write is made in (#2548). */
|
|
999
1150
|
agent?: string;
|
|
1151
|
+
/** The record's digest the reviewer judged (#3173): the verdict is refused with record-conflict when the record has moved on. */
|
|
1152
|
+
expect?: string;
|
|
1000
1153
|
}
|
|
1001
1154
|
|
|
1002
1155
|
/**
|
|
@@ -1006,6 +1159,15 @@ export interface ReviewRecordOptions {
|
|
|
1006
1159
|
* id, the digest, the verdict, the reviewer and the date (`trust/seal.ts`).
|
|
1007
1160
|
*/
|
|
1008
1161
|
export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDocument> {
|
|
1162
|
+
try {
|
|
1163
|
+
refuseExpectShape(opts.expect);
|
|
1164
|
+
return await withWriteLock(opts.cwd, writeWho("records review", opts), opts.dryRun, () => reviewRecordLocked(opts));
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
return failure<ReviewErrorCode>(RECORDS_REVIEW_SCHEMA_ID, err);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
async function reviewRecordLocked(opts: ReviewRecordOptions): Promise<ReviewDocument> {
|
|
1009
1171
|
try {
|
|
1010
1172
|
if (!(VERDICTS as readonly string[]).includes(opts.verdict)) {
|
|
1011
1173
|
throw new RecordWriteError("write-usage-invalid", `--verdict takes ${VERDICTS.join(", ")}, not ${JSON.stringify(opts.verdict)}`);
|
|
@@ -1021,6 +1183,7 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
1021
1183
|
const field = kind.reviews.field;
|
|
1022
1184
|
const before = await readAll(o, o.source);
|
|
1023
1185
|
const target = findRecord(before, opts.id, kind.name);
|
|
1186
|
+
refuseStale(o, target, opts.id, opts.expect);
|
|
1024
1187
|
if (target.state !== null && (kind.closedStates ?? []).includes(target.state)) {
|
|
1025
1188
|
throw new RecordWriteError("record-closed", `${opts.id} is ${target.state}, a closed state, so it takes no more reviews`);
|
|
1026
1189
|
}
|
|
@@ -1047,7 +1210,8 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
1047
1210
|
const list = [...reviews, review];
|
|
1048
1211
|
const text = replaceFields(current, { [field]: list }, { ...target.data, [field]: list });
|
|
1049
1212
|
if (text === undefined) throw new RecordWriteError("record-unparseable", `${target.path}: the ${field} block can't be rewritten in place without changing the rest of the file`);
|
|
1050
|
-
const
|
|
1213
|
+
const writtenEntry = await validatedEntry(o, before, target.path, text);
|
|
1214
|
+
const warnings = writtenEntry.warnings;
|
|
1051
1215
|
// The same verdict on the session's own list (#2693), validated with the review in place, so the two lists never differ.
|
|
1052
1216
|
let session: { path: string; text: string; out: ReviewSession } | undefined;
|
|
1053
1217
|
if (inSession) {
|
|
@@ -1063,8 +1227,9 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
1063
1227
|
session = { path: st.path, text: sText, out: { id: opts.session!, path: st.path, verdict, ...(opts.dryRun ? { text: sText } : {}) } };
|
|
1064
1228
|
}
|
|
1065
1229
|
if (!opts.dryRun) {
|
|
1066
|
-
|
|
1067
|
-
if (session)
|
|
1230
|
+
writeFileAtomic(abs(o, target.path), text);
|
|
1231
|
+
if (session) writeFileAtomic(abs(o, session.path), session.text);
|
|
1232
|
+
noteWrites(o.root, [{ path: target.path, text }, ...(session ? [{ path: session.path, text: session.text }] : [])], writeWho("records review", opts));
|
|
1068
1233
|
}
|
|
1069
1234
|
return {
|
|
1070
1235
|
$schema: RECORDS_REVIEW_SCHEMA_ID,
|
|
@@ -1076,6 +1241,7 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
1076
1241
|
...(session ? { session: session.out } : {}),
|
|
1077
1242
|
dryRun: !!opts.dryRun,
|
|
1078
1243
|
warnings,
|
|
1244
|
+
digest: writtenEntry.digest,
|
|
1079
1245
|
...(opts.dryRun ? { text } : {}),
|
|
1080
1246
|
};
|
|
1081
1247
|
} catch (err) {
|
|
@@ -1130,9 +1296,9 @@ async function seal(sign: string | true, cwd: string, v: { record: string; diges
|
|
|
1130
1296
|
|
|
1131
1297
|
export const WRITE_USAGE = [
|
|
1132
1298
|
"chant workspace records new [<kind file or declared kind>] --from <file|-> [--prefix <prefix>] [--by <name>] [--sign [<key file>]] [--dry-run]",
|
|
1133
|
-
"chant workspace records amend <id> [--kind <kind file>] --set <file|-> [--sign [<key file>]] [--dry-run]",
|
|
1134
|
-
"chant workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--sign [<key file>]] [--dry-run]",
|
|
1135
|
-
"chant workspace records close <session id> [--kind <session kind file>] [--dry-run]",
|
|
1299
|
+
"chant workspace records amend <id> [--kind <kind file>] --set <file|-> [--expect <digest>] [--sign [<key file>]] [--dry-run]",
|
|
1300
|
+
"chant workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--expect <digest>] [--sign [<key file>]] [--dry-run]",
|
|
1301
|
+
"chant workspace records close <session id> [--kind <session kind file>] [--expect <digest>] [--dry-run]",
|
|
1136
1302
|
].join("\n");
|
|
1137
1303
|
|
|
1138
1304
|
function usage(schema: string, message: string): WriteFailure<"write-usage-invalid"> {
|
|
@@ -1226,12 +1392,13 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1226
1392
|
if (!id) return print(usage(RECORDS_CLOSE_SCHEMA_ID, "close needs the session's id"));
|
|
1227
1393
|
const kind = args.kind !== undefined ? resolveWriteKind(args.kind, cwd) : await declaredSessionKind(RECORDS_CLOSE_SCHEMA_ID, cwd);
|
|
1228
1394
|
if (typeof kind !== "string") return print(kind);
|
|
1229
|
-
return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd, agent }));
|
|
1395
|
+
return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd, agent, expect: args.expect }));
|
|
1230
1396
|
}
|
|
1231
1397
|
if (verb === "new") {
|
|
1232
1398
|
const named = args.extraPositional2 ?? args.kind;
|
|
1233
1399
|
const kind = named !== undefined ? resolveWriteKind(named, cwd) : declaredWriteKind(RECORDS_NEW_SCHEMA_ID, cwd, "new needs the kind file");
|
|
1234
1400
|
if (typeof kind !== "string") return print(kind);
|
|
1401
|
+
if (args.expect !== undefined) return print(usage(RECORDS_NEW_SCHEMA_ID, "--expect is not taken by new: a new record has no digest yet, and its id is allocated under the write lock"));
|
|
1235
1402
|
const input = readInput(RECORDS_NEW_SCHEMA_ID, "--from", args.migrateFrom, cwd);
|
|
1236
1403
|
if (typeof input !== "string") return print(input);
|
|
1237
1404
|
return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
|
|
@@ -1245,11 +1412,11 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1245
1412
|
if (args.by !== undefined) return print(usage(schema, "--by is not taken by amend: set decided_by, or a kind's proposedBy field, with --set"));
|
|
1246
1413
|
const input = readInput(schema, "--set", args.set, cwd);
|
|
1247
1414
|
if (typeof input !== "string") return print(input);
|
|
1248
|
-
return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
|
|
1415
|
+
return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd, agent, expect: args.expect }));
|
|
1249
1416
|
}
|
|
1250
1417
|
if (args.verdict === undefined) return print(usage(schema, "--verdict agree|dissent|abstain is required"));
|
|
1251
1418
|
if (args.by === undefined) return print(usage(schema, "--by <principal> is required"));
|
|
1252
1419
|
return print(
|
|
1253
|
-
await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd, agent }),
|
|
1420
|
+
await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd, agent, expect: args.expect }),
|
|
1254
1421
|
);
|
|
1255
1422
|
}
|
|
@@ -302,6 +302,22 @@
|
|
|
302
302
|
"enum": ["committed", "modified", "new"],
|
|
303
303
|
"description": "Added in contract 1 by #3160, on every record of a read of the working tree in a git repository; absent under --at and outside git. committed when the file is as HEAD holds it, modified when it differs from HEAD, new when HEAD doesn't hold it, staged or not. modified and new are the records a person has kept and not yet committed or applied."
|
|
304
304
|
},
|
|
305
|
+
"lastWrite": {
|
|
306
|
+
"description": "Added in contract 1 by #3173, beside worktree. The last chant write of the record's file in this working tree (records new, amend, review or close, points ask or answer), from the write journal in the git directory, while the file still holds the text that write left; null when the journal doesn't know it, such as after a commit was checked out or the file was edited by hand. A UI shows it with digest, so a person sees who changed a record they are looking at. It is a cache, never a fact: the journal can be lost.",
|
|
307
|
+
"oneOf": [
|
|
308
|
+
{ "type": "null" },
|
|
309
|
+
{
|
|
310
|
+
"type": "object",
|
|
311
|
+
"required": ["verb", "by", "agent", "at"],
|
|
312
|
+
"properties": {
|
|
313
|
+
"verb": { "type": "string", "description": "The write command, such as records amend or points answer." },
|
|
314
|
+
"by": { "type": ["string", "null"], "description": "The principal the write named (--by, or the people who answered), or null when it named none." },
|
|
315
|
+
"agent": { "type": ["string", "null"], "description": "The agent session the write was made in (CHANT_AGENT), or null." },
|
|
316
|
+
"at": { "type": "string", "description": "When, as an ISO 8601 time." }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
},
|
|
305
321
|
"contract": {
|
|
306
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.",
|
|
307
323
|
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLink" }]
|
package/src/workspace/records.ts
CHANGED
|
@@ -381,9 +381,11 @@ export const recordKindSchema = z
|
|
|
381
381
|
* A review-session kind (#2673, #2650 C10): the front-matter list of the
|
|
382
382
|
* verdicts a session produced, the field that seals a closed session, and
|
|
383
383
|
* the records its verdicts name, as the kind file that locates them
|
|
384
|
-
* (relative to this kind file's directory)
|
|
385
|
-
*
|
|
386
|
-
*
|
|
384
|
+
* (relative to this kind file's directory), or as `kinds`, several kind
|
|
385
|
+
* files, when a session judges records of more than one kind, such as
|
|
386
|
+
* decisions and contracts (#3148, ws-082). The entries of each such
|
|
387
|
+
* kind's reviews list (its `reviews.field`, or `reviews`) name a session
|
|
388
|
+
* in `session`. Optional.
|
|
387
389
|
*
|
|
388
390
|
* `openedRev`, `closedRev` and `closedOn` (#2693) name the fields that
|
|
389
391
|
* hold the commit a session opened at, the commit it closed at, and when
|
|
@@ -395,7 +397,13 @@ export const recordKindSchema = z
|
|
|
395
397
|
.object({
|
|
396
398
|
verdicts: z.string().min(1),
|
|
397
399
|
seal: z.string().min(1),
|
|
398
|
-
subjects: z.
|
|
400
|
+
subjects: z.union([
|
|
401
|
+
z.object({ kind: z.string().min(1) }).strict(),
|
|
402
|
+
z
|
|
403
|
+
.object({ kinds: z.array(z.string().min(1)).min(1) })
|
|
404
|
+
.strict()
|
|
405
|
+
.refine((x) => new Set(x.kinds).size === x.kinds.length, { message: "session.subjects.kinds lists each kind file once", path: ["kinds"] }),
|
|
406
|
+
]),
|
|
399
407
|
openedRev: z.string().min(1).optional(),
|
|
400
408
|
closedRev: z.string().min(1).optional(),
|
|
401
409
|
closedOn: z.string().min(1).optional(),
|
|
@@ -448,7 +456,12 @@ export const recordKindSchema = z
|
|
|
448
456
|
acceptance: z.object({ field: z.string().min(1), implementer: z.string().min(1) }).strict().optional(),
|
|
449
457
|
contract: z.object({ field: z.string().min(1), kind: z.string().min(1) }).strict().optional(),
|
|
450
458
|
tier: z
|
|
451
|
-
.object({
|
|
459
|
+
.object({
|
|
460
|
+
field: z.string().min(1),
|
|
461
|
+
tiers: z.array(z.string().min(1)).min(1),
|
|
462
|
+
// What each tier may hold, for the slice-tier point's fits_<tier> inputs (#3150, work-size.ts).
|
|
463
|
+
limits: z.record(z.string().min(1), z.object({ criteria: z.number().int().min(0).optional(), files: z.number().int().min(0).optional(), words: z.number().int().min(0).optional() }).strict()).optional(),
|
|
464
|
+
})
|
|
452
465
|
.strict()
|
|
453
466
|
.refine((t) => new Set(t.tiers).size === t.tiers.length, { message: "work.tier.tiers lists each tier once", path: ["tiers"] })
|
|
454
467
|
.optional(),
|
|
@@ -561,6 +574,34 @@ export const ANSWER_STATES = ["escalated", "proposed", "answered"] as const;
|
|
|
561
574
|
|
|
562
575
|
export type RecordKind = z.infer<typeof recordKindSchema>;
|
|
563
576
|
|
|
577
|
+
/** The kind files a session kind's verdicts name records of, relative to the session kind file: `subjects.kind`, or each of `subjects.kinds` (ws-082). */
|
|
578
|
+
export function sessionSubjectKinds(session: NonNullable<RecordKind["session"]>): string[] {
|
|
579
|
+
return "kinds" in session.subjects ? session.subjects.kinds : [session.subjects.kind];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The records of one of a session kind's subject kinds, and the name of their reviews list. */
|
|
583
|
+
export interface SessionSubjects {
|
|
584
|
+
/** The subject kind file, as the session kind names it. */
|
|
585
|
+
kind: string;
|
|
586
|
+
records: RecordEntry[];
|
|
587
|
+
reviews: string;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* A session kind's subject records, each subject kind read from `source`
|
|
592
|
+
* (#2673, ws-082), for {@link readRecords}' `subjects`. Undefined for a kind
|
|
593
|
+
* that is not a session kind.
|
|
594
|
+
*/
|
|
595
|
+
export async function readSessionSubjects(loaded: LoadedRecordKind, root: string, source: RecordSource): Promise<SessionSubjects[] | undefined> {
|
|
596
|
+
if (!loaded.kind.session) return undefined;
|
|
597
|
+
const out: SessionSubjects[] = [];
|
|
598
|
+
for (const file of sessionSubjectKinds(loaded.kind.session)) {
|
|
599
|
+
const subjectKind = await loadRecordKind(resolve(dirname(loaded.file), file), root);
|
|
600
|
+
out.push({ kind: file, records: (await readRecords(subjectKind, { root, source })).records, reviews: subjectKind.kind.reviews?.field ?? "reviews" });
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
}
|
|
604
|
+
|
|
564
605
|
/** A kind as loaded: its data, where it came from, and its schema. */
|
|
565
606
|
export interface LoadedRecordKind {
|
|
566
607
|
kind: RecordKind;
|
|
@@ -1195,11 +1236,11 @@ export interface ReadRecordsOptions {
|
|
|
1195
1236
|
*/
|
|
1196
1237
|
history?: RecordHistory;
|
|
1197
1238
|
/**
|
|
1198
|
-
* For a session kind: the records its
|
|
1199
|
-
* from the same tree (#2673). Without them no verdict is checked
|
|
1200
|
-
* session is cited.
|
|
1239
|
+
* For a session kind: the records each of its subject kinds locates, read
|
|
1240
|
+
* from the same tree (#2673, ws-082). Without them no verdict is checked
|
|
1241
|
+
* and no session is cited.
|
|
1201
1242
|
*/
|
|
1202
|
-
subjects?:
|
|
1243
|
+
subjects?: SessionSubjects[];
|
|
1203
1244
|
/**
|
|
1204
1245
|
* The workspace root, from `root` with / separators ("." for `root`
|
|
1205
1246
|
* itself): where a content-addressed record's own path is reported from
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chant-reference-app-kind",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "The app member kind (INTENTIUS/chant#3151): a data-only kinds file a workspace pins by path. chant reads it as data and never runs it.",
|
|
6
|
+
"files": ["workspace-kinds.json"],
|
|
7
|
+
"exports": {
|
|
8
|
+
"./workspace-kinds": "./workspace-kinds.json"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "The app member kind (#3151, ws-093). A workspace pins a copy of this directory by path, as the reference workspace does at kinds/app, and chant reads it as data. The fields are the conventions an orchestrator relies on to run, migrate, test and check an app: the scripts in its package.json, the environment variables it reads, and the path it answers health on. The canonical copy ships in @intentius/chant at src/workspace/reference-kinds/app.",
|
|
3
|
+
"schema": 1,
|
|
4
|
+
"kinds": [
|
|
5
|
+
{
|
|
6
|
+
"name": "app",
|
|
7
|
+
"description": "an app: a Node package with a start script in the package.json directly in its directory",
|
|
8
|
+
"precedence": 100,
|
|
9
|
+
"probe": {
|
|
10
|
+
"anyJsonKey": {
|
|
11
|
+
"in": ["package.json"],
|
|
12
|
+
"pointers": ["/scripts/start"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"outputs": ["source", "url"],
|
|
16
|
+
"fields": {
|
|
17
|
+
"scripts": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Which package.json script plays each part, run with npm run from the member's directory",
|
|
20
|
+
"properties": {
|
|
21
|
+
"start": { "type": "string", "description": "Runs a release of the app; it listens on the port env.port names", "default": "start" },
|
|
22
|
+
"dev": { "type": "string", "description": "Runs the app in development", "default": "dev" },
|
|
23
|
+
"test": { "type": "string", "description": "The app's own tests, run before a release", "default": "test" },
|
|
24
|
+
"migrate": { "type": "string", "description": "Applies the app's migrations to the data directory env.data names; a missing script means there are none", "default": "migrate" }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"env": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"description": "The environment variables the app reads, by name",
|
|
30
|
+
"properties": {
|
|
31
|
+
"port": { "type": "string", "description": "The port the app listens on", "default": "PORT", "pattern": "^[A-Z_][A-Z0-9_]*$" },
|
|
32
|
+
"data": { "type": "string", "description": "The directory the app keeps its data in", "default": "APP_DATA", "pattern": "^[A-Z_][A-Z0-9_]*$" },
|
|
33
|
+
"revision": { "type": "string", "description": "The revision the running app reports, such as the release's commit", "default": "APP_REVISION", "pattern": "^[A-Z_][A-Z0-9_]*$" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"health": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The path the running app answers on with a status below 500 once it is up",
|
|
39
|
+
"default": "/health",
|
|
40
|
+
"pattern": "^/"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|