devcontour-harness 0.0.0-stage → 0.1.0-alpha.6
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/LICENSE +21 -0
- package/README.md +183 -2
- package/docs/architecture.md +473 -0
- package/docs/compatibility-versions.md +56 -0
- package/docs/compatibility.md +192 -0
- package/docs/connectors.md +323 -0
- package/docs/contracts.md +603 -0
- package/docs/integrations/hindsight.md +148 -0
- package/docs/quickstart.md +182 -0
- package/docs/release-notes/0.1.0-alpha.6.md +238 -0
- package/docs/review-probes.md +120 -0
- package/docs/review-protocol.md +251 -0
- package/docs/toolchains.md +167 -0
- package/docs/turbo-starter.md +269 -0
- package/docs/upgrade.md +202 -0
- package/examples/README.md +27 -0
- package/examples/external-connector/README.md +19 -0
- package/examples/external-connector/connector.mjs +55 -0
- package/examples/files-only/README.md +28 -0
- package/examples/files-only/context/norms/rules.md +4 -0
- package/examples/files-only/graph.json +23 -0
- package/examples/files-only/policy.json +30 -0
- package/examples/one-vendor/README.md +19 -0
- package/examples/one-vendor/policy.json +27 -0
- package/examples/single-task/README.md +18 -0
- package/examples/single-task/graph.json +27 -0
- package/examples/single-task/policy-same-vendor.json +55 -0
- package/examples/single-task/policy.json +52 -0
- package/examples/single-task/src/.gitkeep +0 -0
- package/examples/single-task/test/run.js +19 -0
- package/examples/toolchains/README.md +32 -0
- package/examples/toolchains/go/bad/label/label.go +9 -0
- package/examples/toolchains/go/good/label/label.go +9 -0
- package/examples/toolchains/go/policy.json +79 -0
- package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/go/repo/_gitignore +1 -0
- package/examples/toolchains/go/repo/go.mod +5 -0
- package/examples/toolchains/go/repo/go.sum +2 -0
- package/examples/toolchains/go/repo/label/label.go +9 -0
- package/examples/toolchains/go/repo/label/label_test.go +27 -0
- package/examples/toolchains/go/repo/label/version.go +9 -0
- package/examples/toolchains/go/task.json +25 -0
- package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/policy.json +126 -0
- package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
- package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-go/task.json +25 -0
- package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/policy.json +121 -0
- package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
- package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
- package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
- package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
- package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-python/task.json +25 -0
- package/examples/toolchains/npm/bad/src/label.js +4 -0
- package/examples/toolchains/npm/good/src/label.js +4 -0
- package/examples/toolchains/npm/policy.json +82 -0
- package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/npm/repo/_gitignore +4 -0
- package/examples/toolchains/npm/repo/package-lock.json +22 -0
- package/examples/toolchains/npm/repo/package.json +13 -0
- package/examples/toolchains/npm/repo/src/label.js +4 -0
- package/examples/toolchains/npm/repo/test/label.test.js +13 -0
- package/examples/toolchains/npm/task.json +25 -0
- package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/policy.json +85 -0
- package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/pnpm/repo/_gitignore +4 -0
- package/examples/toolchains/pnpm/repo/package.json +10 -0
- package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
- package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
- package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/pnpm/task.json +25 -0
- package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/policy.json +80 -0
- package/examples/toolchains/python/repo/.python-version +1 -0
- package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/python/repo/_gitignore +5 -0
- package/examples/toolchains/python/repo/pyproject.toml +19 -0
- package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/repo/tests/test_label.py +13 -0
- package/examples/toolchains/python/repo/uv.lock +108 -0
- package/examples/toolchains/python/task.json +25 -0
- package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/policy.json +110 -0
- package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/turbo/repo/_gitignore +4 -0
- package/examples/toolchains/turbo/repo/package.json +13 -0
- package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
- package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
- package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/turbo/repo/turbo.json +7 -0
- package/examples/toolchains/turbo/task.json +25 -0
- package/examples/turbo-starter/README.md +16 -0
- package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
- package/examples/turbo-starter/changes/issued-at.patch +130 -0
- package/examples/turbo-starter/cleanup.mjs +58 -0
- package/examples/turbo-starter/graph.json +32 -0
- package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
- package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
- package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
- package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
- package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
- package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
- package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
- package/examples/turbo-starter/harness/env/backend.env.template +7 -0
- package/examples/turbo-starter/harness/env/compose.env.template +6 -0
- package/examples/turbo-starter/policy.json +109 -0
- package/examples/turbo-starter/policy.lifecycle.json +114 -0
- package/examples/turbo-starter/setup.mjs +352 -0
- package/examples/two-vendors/policy.json +30 -0
- package/lib/agents/cli-adapter.d.ts +48 -0
- package/lib/agents/cli-adapter.js +386 -0
- package/lib/agents/inspect.d.ts +6 -0
- package/lib/agents/inspect.js +46 -0
- package/lib/agents/probe-server.d.ts +2 -0
- package/lib/agents/probe-server.js +13 -0
- package/lib/agents/review-probe.d.ts +295 -0
- package/lib/agents/review-probe.js +872 -0
- package/lib/agents/runtime-events.d.ts +11 -0
- package/lib/agents/runtime-events.js +21 -0
- package/lib/agents/schemas.d.ts +217 -0
- package/lib/agents/schemas.js +124 -0
- package/lib/agents/usage.d.ts +8 -0
- package/lib/agents/usage.js +83 -0
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +491 -0
- package/lib/connectors/files.d.ts +108 -0
- package/lib/connectors/files.js +133 -0
- package/lib/contracts/checks.d.ts +307 -0
- package/lib/contracts/checks.js +181 -0
- package/lib/contracts/cli.d.ts +3895 -0
- package/lib/contracts/cli.js +664 -0
- package/lib/contracts/context.d.ts +135 -0
- package/lib/contracts/context.js +129 -0
- package/lib/contracts/digest.d.ts +9 -0
- package/lib/contracts/digest.js +22 -0
- package/lib/contracts/errors.d.ts +81 -0
- package/lib/contracts/errors.js +70 -0
- package/lib/contracts/events.d.ts +827 -0
- package/lib/contracts/events.js +258 -0
- package/lib/contracts/execution.d.ts +3216 -0
- package/lib/contracts/execution.js +872 -0
- package/lib/contracts/failure.d.ts +67 -0
- package/lib/contracts/failure.js +127 -0
- package/lib/contracts/index.d.ts +18 -0
- package/lib/contracts/index.js +18 -0
- package/lib/contracts/policy.d.ts +1080 -0
- package/lib/contracts/policy.js +544 -0
- package/lib/contracts/ports.d.ts +417 -0
- package/lib/contracts/ports.js +1 -0
- package/lib/contracts/primitives.d.ts +63 -0
- package/lib/contracts/primitives.js +73 -0
- package/lib/contracts/profiles.d.ts +336 -0
- package/lib/contracts/profiles.js +109 -0
- package/lib/contracts/published.d.ts +3237 -0
- package/lib/contracts/published.js +34 -0
- package/lib/contracts/result.d.ts +1280 -0
- package/lib/contracts/result.js +311 -0
- package/lib/contracts/review.d.ts +441 -0
- package/lib/contracts/review.js +477 -0
- package/lib/contracts/toolchain.d.ts +96 -0
- package/lib/contracts/toolchain.js +89 -0
- package/lib/contracts/version.d.ts +17 -0
- package/lib/contracts/version.js +17 -0
- package/lib/contracts/work.d.ts +173 -0
- package/lib/contracts/work.js +178 -0
- package/lib/control/base-move.d.ts +44 -0
- package/lib/control/base-move.js +90 -0
- package/lib/control/doctor.d.ts +50 -0
- package/lib/control/doctor.js +113 -0
- package/lib/control/harness.d.ts +1125 -0
- package/lib/control/harness.js +741 -0
- package/lib/control/provenance.d.ts +17 -0
- package/lib/control/provenance.js +25 -0
- package/lib/control/recovery.d.ts +237 -0
- package/lib/control/recovery.js +236 -0
- package/lib/control/review.d.ts +62 -0
- package/lib/control/review.js +178 -0
- package/lib/control/workflow.d.ts +247 -0
- package/lib/control/workflow.js +259 -0
- package/lib/engine/budget.d.ts +345 -0
- package/lib/engine/budget.js +278 -0
- package/lib/engine/classify.d.ts +8 -0
- package/lib/engine/classify.js +32 -0
- package/lib/engine/engine.d.ts +1527 -0
- package/lib/engine/engine.js +2259 -0
- package/lib/engine/failure.d.ts +1 -0
- package/lib/engine/failure.js +3 -0
- package/lib/engine/joint-runner.d.ts +257 -0
- package/lib/engine/joint-runner.js +254 -0
- package/lib/engine/measured.d.ts +15 -0
- package/lib/engine/measured.js +82 -0
- package/lib/engine/prompt.d.ts +66 -0
- package/lib/engine/prompt.js +131 -0
- package/lib/engine/property-recovery.d.ts +50 -0
- package/lib/engine/property-recovery.js +171 -0
- package/lib/engine/repair.d.ts +33 -0
- package/lib/engine/repair.js +82 -0
- package/lib/engine/result.d.ts +9 -0
- package/lib/engine/result.js +218 -0
- package/lib/engine/reuse.d.ts +80 -0
- package/lib/engine/reuse.js +102 -0
- package/lib/engine/runner.d.ts +360 -0
- package/lib/engine/runner.js +1836 -0
- package/lib/engine/standalone.d.ts +6 -0
- package/lib/engine/standalone.js +48 -0
- package/lib/engine/usage.d.ts +87 -0
- package/lib/engine/usage.js +165 -0
- package/lib/environment/isolation.d.ts +110 -0
- package/lib/environment/isolation.js +238 -0
- package/lib/environment/lifecycle.d.ts +40 -0
- package/lib/environment/lifecycle.js +197 -0
- package/lib/environment/live-calls.d.ts +14 -0
- package/lib/environment/live-calls.js +73 -0
- package/lib/environment/os-tools.d.ts +16 -0
- package/lib/environment/os-tools.js +35 -0
- package/lib/environment/process.d.ts +32 -0
- package/lib/environment/process.js +369 -0
- package/lib/environment/redaction.d.ts +22 -0
- package/lib/environment/redaction.js +102 -0
- package/lib/environment/resources.d.ts +31 -0
- package/lib/environment/resources.js +120 -0
- package/lib/index.d.ts +65 -0
- package/lib/index.js +67 -0
- package/lib/integrations/connector/commands.d.ts +358 -0
- package/lib/integrations/connector/commands.js +428 -0
- package/lib/integrations/connector/config.d.ts +215 -0
- package/lib/integrations/connector/config.js +214 -0
- package/lib/integrations/connector/delivery.d.ts +107 -0
- package/lib/integrations/connector/delivery.js +591 -0
- package/lib/integrations/connector/fake.d.ts +291 -0
- package/lib/integrations/connector/fake.js +786 -0
- package/lib/integrations/connector/history.d.ts +124 -0
- package/lib/integrations/connector/history.js +497 -0
- package/lib/integrations/connector/index.d.ts +16 -0
- package/lib/integrations/connector/index.js +16 -0
- package/lib/integrations/connector/redact.d.ts +19 -0
- package/lib/integrations/connector/redact.js +91 -0
- package/lib/integrations/connector/registry.d.ts +15 -0
- package/lib/integrations/connector/registry.js +43 -0
- package/lib/integrations/connector/snapshot.d.ts +131 -0
- package/lib/integrations/connector/snapshot.js +422 -0
- package/lib/integrations/connector/source.d.ts +37 -0
- package/lib/integrations/connector/source.js +73 -0
- package/lib/integrations/connector/steps.d.ts +71 -0
- package/lib/integrations/connector/steps.js +188 -0
- package/lib/integrations/connector/store.d.ts +140 -0
- package/lib/integrations/connector/store.js +277 -0
- package/lib/integrations/connector/summary.d.ts +38 -0
- package/lib/integrations/connector/summary.js +59 -0
- package/lib/integrations/connector/testing-worker.d.ts +1 -0
- package/lib/integrations/connector/testing-worker.js +81 -0
- package/lib/integrations/connector/testing.d.ts +205 -0
- package/lib/integrations/connector/testing.js +1615 -0
- package/lib/integrations/connector/transport.d.ts +240 -0
- package/lib/integrations/connector/transport.js +28 -0
- package/lib/integrations/memory/context.d.ts +120 -0
- package/lib/integrations/memory/context.js +239 -0
- package/lib/integrations/memory/index.d.ts +8 -0
- package/lib/integrations/memory/index.js +8 -0
- package/lib/integrations/memory/provider.d.ts +79 -0
- package/lib/integrations/memory/provider.js +22 -0
- package/lib/integrations/memory/sink.d.ts +56 -0
- package/lib/integrations/memory/sink.js +116 -0
- package/lib/integrations/memory/testing.d.ts +44 -0
- package/lib/integrations/memory/testing.js +61 -0
- package/lib/integrations/relay-v1/index.d.ts +122 -0
- package/lib/integrations/relay-v1/index.js +860 -0
- package/lib/integrations/relay-v2/index.d.ts +145 -0
- package/lib/integrations/relay-v2/index.js +752 -0
- package/lib/integrations/relay-v2/process.d.ts +32 -0
- package/lib/integrations/relay-v2/process.js +112 -0
- package/lib/integrations/relay-v2/protocol.d.ts +172 -0
- package/lib/integrations/relay-v2/protocol.js +142 -0
- package/lib/package.d.ts +2 -0
- package/lib/package.js +3 -0
- package/lib/profiles/resolve.d.ts +190 -0
- package/lib/profiles/resolve.js +273 -0
- package/lib/storage/document.d.ts +1234 -0
- package/lib/storage/document.js +80 -0
- package/lib/storage/sqlite.d.ts +35 -0
- package/lib/storage/sqlite.js +260 -0
- package/lib/testing/index.d.ts +5 -0
- package/lib/testing/index.js +5 -0
- package/lib/testing/store-contract.d.ts +8 -0
- package/lib/testing/store-contract.js +167 -0
- package/lib/toolchains/index.d.ts +9 -0
- package/lib/toolchains/index.js +9 -0
- package/lib/toolchains/inspect.d.ts +105 -0
- package/lib/toolchains/inspect.js +831 -0
- package/lib/toolchains/manifests.d.ts +67 -0
- package/lib/toolchains/manifests.js +238 -0
- package/lib/toolchains/probe.d.ts +28 -0
- package/lib/toolchains/probe.js +54 -0
- package/lib/toolchains/recipes.d.ts +33 -0
- package/lib/toolchains/recipes.js +51 -0
- package/lib/validate.d.ts +36 -0
- package/lib/validate.js +175 -0
- package/lib/verification/acceptance.d.ts +284 -0
- package/lib/verification/acceptance.js +670 -0
- package/lib/verification/checks.d.ts +72 -0
- package/lib/verification/checks.js +190 -0
- package/lib/verification/confirm.d.ts +125 -0
- package/lib/verification/confirm.js +411 -0
- package/lib/verification/corpus.d.ts +1303 -0
- package/lib/verification/corpus.js +744 -0
- package/lib/verification/dependencies.d.ts +66 -0
- package/lib/verification/dependencies.js +144 -0
- package/lib/verification/gates.d.ts +160 -0
- package/lib/verification/gates.js +600 -0
- package/lib/verification/joint.d.ts +63 -0
- package/lib/verification/joint.js +121 -0
- package/lib/verification/proof.d.ts +54 -0
- package/lib/verification/proof.js +96 -0
- package/lib/verification/property.d.ts +86 -0
- package/lib/verification/property.js +214 -0
- package/lib/verification/requirements.d.ts +25 -0
- package/lib/verification/requirements.js +69 -0
- package/lib/verification/self-check.d.ts +29 -0
- package/lib/verification/self-check.js +99 -0
- package/lib/verification/spike.d.ts +79 -0
- package/lib/verification/spike.js +124 -0
- package/package.json +100 -4
- package/profiles/expo-mobile.json +15 -0
- package/profiles/go-api.json +15 -0
- package/profiles/go-gotestsum.json +21 -0
- package/profiles/mobile-maestro.json +29 -0
- package/profiles/nest-api.json +26 -0
- package/profiles/next-product.json +72 -0
- package/profiles/node-checks.json +77 -0
- package/profiles/npm-ci.json +22 -0
- package/profiles/pnpm-frozen.json +24 -0
- package/profiles/python-api.json +32 -0
- package/profiles/python-uv.json +26 -0
- package/profiles/react-native.json +50 -0
- package/profiles/react-vite-admin.json +71 -0
- package/schemas/context-snapshot.schema.json +231 -0
- package/schemas/contract-document.schema.json +385 -0
- package/schemas/execution-document.schema.json +3695 -0
- package/schemas/execution-policy.schema.json +2196 -0
- package/schemas/joint-verification.schema.json +617 -0
- package/schemas/result-envelope.schema.json +1652 -0
- package/schemas/review-record.schema.json +177 -0
- package/schemas/run-event.schema.json +2458 -0
- package/schemas/run-result.schema.json +1570 -0
- package/schemas/stack-profile.schema.json +535 -0
- package/schemas/work-graph.schema.json +381 -0
- package/schemas/work-order.schema.json +256 -0
- package/skills/harness/SKILL.md +64 -0
- package/templates/AGENTS.md +13 -0
- package/templates/controller-mode/AGENTS.controller.md +58 -0
- package/templates/controller-mode/README.md +44 -0
- package/templates/graph.json +22 -0
- package/templates/policy.json +28 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { STATE_VERSION, } from "../contracts/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Пустой документ: очередь ещё не запускалась — пауза без причины. Причина
|
|
4
|
+
* появляется только у решения (оператор, штатная остановка, отказ, операция):
|
|
5
|
+
* так первый запуск отличим от паузы, которую поставил человек.
|
|
6
|
+
*/
|
|
7
|
+
export const emptyDocument = () => ({
|
|
8
|
+
stateVersion: STATE_VERSION,
|
|
9
|
+
graphs: [],
|
|
10
|
+
works: [],
|
|
11
|
+
attempts: [],
|
|
12
|
+
verifications: [],
|
|
13
|
+
control: { paused: true },
|
|
14
|
+
sequence: 0,
|
|
15
|
+
});
|
|
16
|
+
/** Чтение и запись записей модели поверх документа — общая часть адаптеров хранилища. */
|
|
17
|
+
export class DocumentView {
|
|
18
|
+
doc;
|
|
19
|
+
constructor(doc) {
|
|
20
|
+
this.doc = doc;
|
|
21
|
+
}
|
|
22
|
+
control() {
|
|
23
|
+
return this.doc.control;
|
|
24
|
+
}
|
|
25
|
+
controller() {
|
|
26
|
+
return this.doc.controller;
|
|
27
|
+
}
|
|
28
|
+
graph(id) {
|
|
29
|
+
return this.doc.graphs.find((g) => g.id === id);
|
|
30
|
+
}
|
|
31
|
+
graphs() {
|
|
32
|
+
return this.doc.graphs;
|
|
33
|
+
}
|
|
34
|
+
work(id) {
|
|
35
|
+
return this.doc.works.find((w) => w.id === id);
|
|
36
|
+
}
|
|
37
|
+
works(graphId) {
|
|
38
|
+
return graphId ? this.doc.works.filter((w) => w.graphId === graphId) : this.doc.works;
|
|
39
|
+
}
|
|
40
|
+
attempt(id) {
|
|
41
|
+
return this.doc.attempts.find((a) => a.id === id);
|
|
42
|
+
}
|
|
43
|
+
attempts(workId) {
|
|
44
|
+
return workId ? this.doc.attempts.filter((a) => a.workId === workId) : this.doc.attempts;
|
|
45
|
+
}
|
|
46
|
+
verification(id) {
|
|
47
|
+
return this.doc.verifications.find((v) => v.id === id);
|
|
48
|
+
}
|
|
49
|
+
verifications() {
|
|
50
|
+
return this.doc.verifications;
|
|
51
|
+
}
|
|
52
|
+
putGraph(graph) {
|
|
53
|
+
upsert(this.doc.graphs, graph);
|
|
54
|
+
}
|
|
55
|
+
putWork(work) {
|
|
56
|
+
upsert(this.doc.works, work);
|
|
57
|
+
}
|
|
58
|
+
putAttempt(attempt) {
|
|
59
|
+
upsert(this.doc.attempts, attempt);
|
|
60
|
+
}
|
|
61
|
+
putVerification(verification) {
|
|
62
|
+
upsert(this.doc.verifications, verification);
|
|
63
|
+
}
|
|
64
|
+
setControl(control) {
|
|
65
|
+
this.doc.control = control;
|
|
66
|
+
}
|
|
67
|
+
setController(lease) {
|
|
68
|
+
if (lease)
|
|
69
|
+
this.doc.controller = lease;
|
|
70
|
+
else
|
|
71
|
+
delete this.doc.controller;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function upsert(list, item) {
|
|
75
|
+
const i = list.findIndex((x) => x.id === item.id);
|
|
76
|
+
if (i >= 0)
|
|
77
|
+
list[i] = item;
|
|
78
|
+
else
|
|
79
|
+
list.push(item);
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ExecutionStore, type ExecutionTransaction, type ExecutionView, type NotPromise, type RunEvent, type TransactionKind } from '../contracts/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone ExecutionStore: нейтральный документ, события и локальные
|
|
4
|
+
* записи в одной SQLite. Транзакция — `BEGIN IMMEDIATE` (вложенная —
|
|
5
|
+
* SAVEPOINT); документ читается один раз на внешнюю транзакцию, вложенные
|
|
6
|
+
* работают с ним же и при отказе восстанавливают его снимок и свой буфер
|
|
7
|
+
* событий. Документ проверяется схемой при каждой фиксации: состояние вне
|
|
8
|
+
* договора не записывается.
|
|
9
|
+
*
|
|
10
|
+
* Версия: в `meta` записаны версия формата и версия писателя. Неизвестная
|
|
11
|
+
* версия формата отклоняется при открытии, до любой записи.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SqliteExecutionStore implements ExecutionStore {
|
|
14
|
+
readonly file: string;
|
|
15
|
+
readonly writer: string;
|
|
16
|
+
readonly options: {
|
|
17
|
+
readOnly?: boolean;
|
|
18
|
+
};
|
|
19
|
+
readonly stateVersion: 1;
|
|
20
|
+
private db;
|
|
21
|
+
private frame?;
|
|
22
|
+
private savepoint;
|
|
23
|
+
constructor(file: string, writer: string, options?: {
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
});
|
|
26
|
+
/** Проверка существующего файла без единой записи; отказ — HarnessError. */
|
|
27
|
+
private static preflight;
|
|
28
|
+
private load;
|
|
29
|
+
read<T>(fn: (view: ExecutionView) => NotPromise<T>): T;
|
|
30
|
+
transact<T>(kind: TransactionKind, fn: (tx: ExecutionTransaction) => NotPromise<T>): T;
|
|
31
|
+
private writeEvents;
|
|
32
|
+
events(after?: number, limit?: number): RunEvent[];
|
|
33
|
+
close(): void;
|
|
34
|
+
private view;
|
|
35
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { existsSync, mkdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { API_VERSION, HarnessError, STATE_VERSION, SUPPORTED_STATE_VERSIONS, WORKSPACE_OWNER, executionDocumentSchema, localSchemas, runEventSchema, } from "../contracts/index.js";
|
|
5
|
+
import { DocumentView, emptyDocument } from "./document.js";
|
|
6
|
+
/**
|
|
7
|
+
* Строгая схема: поле, которого эта версия не знает (например, записанное
|
|
8
|
+
* более новой версией), — отказ до любой записи и выдачи, а не молчаливая
|
|
9
|
+
* потеря при перезаписи документа.
|
|
10
|
+
*/
|
|
11
|
+
function parseDocument(data) {
|
|
12
|
+
const parsed = executionDocumentSchema.safeParse(JSON.parse(data));
|
|
13
|
+
if (!parsed.success)
|
|
14
|
+
throw new HarnessError('STATE_VERSION_UNSUPPORTED', 'Состояние не соответствует формату этой версии пакета (возможно, его записала более новая версия): обновите пакет или восстановите согласованную копию', parsed.error.issues.slice(0, 20).map((i) => ({
|
|
15
|
+
path: i.path.filter((k) => typeof k !== 'symbol'),
|
|
16
|
+
message: i.message,
|
|
17
|
+
})));
|
|
18
|
+
return parsed.data;
|
|
19
|
+
}
|
|
20
|
+
/** Сколько событий одной транзакции допускается (договор курсора адаптеров). */
|
|
21
|
+
const MAX_EVENTS_PER_TRANSACTION = 1024;
|
|
22
|
+
/**
|
|
23
|
+
* Standalone ExecutionStore: нейтральный документ, события и локальные
|
|
24
|
+
* записи в одной SQLite. Транзакция — `BEGIN IMMEDIATE` (вложенная —
|
|
25
|
+
* SAVEPOINT); документ читается один раз на внешнюю транзакцию, вложенные
|
|
26
|
+
* работают с ним же и при отказе восстанавливают его снимок и свой буфер
|
|
27
|
+
* событий. Документ проверяется схемой при каждой фиксации: состояние вне
|
|
28
|
+
* договора не записывается.
|
|
29
|
+
*
|
|
30
|
+
* Версия: в `meta` записаны версия формата и версия писателя. Неизвестная
|
|
31
|
+
* версия формата отклоняется при открытии, до любой записи.
|
|
32
|
+
*/
|
|
33
|
+
export class SqliteExecutionStore {
|
|
34
|
+
file;
|
|
35
|
+
writer;
|
|
36
|
+
options;
|
|
37
|
+
stateVersion = STATE_VERSION;
|
|
38
|
+
db;
|
|
39
|
+
frame;
|
|
40
|
+
savepoint = 0;
|
|
41
|
+
constructor(file, writer, options = {}) {
|
|
42
|
+
this.file = file;
|
|
43
|
+
this.writer = writer;
|
|
44
|
+
this.options = options;
|
|
45
|
+
// Существующее состояние сначала проверяется целиком через отдельное
|
|
46
|
+
// соединение только для чтения: версия формата и документ строгой схемой.
|
|
47
|
+
// Несовместимый вход отклоняется до DDL, записи автора и смены journal
|
|
48
|
+
// mode — файл остаётся байт в байт прежним.
|
|
49
|
+
const existing = file !== ':memory:' && existsSync(file) && statSync(file).size > 0;
|
|
50
|
+
if (existing)
|
|
51
|
+
SqliteExecutionStore.preflight(file);
|
|
52
|
+
else if (options.readOnly)
|
|
53
|
+
throw new HarnessError('STATE_VERSION_UNSUPPORTED', 'Состояние не найдено');
|
|
54
|
+
if (options.readOnly) {
|
|
55
|
+
// Диагностическое открытие: ни автор, ни PRAGMA, ни схема не меняются.
|
|
56
|
+
this.db = new DatabaseSync(file, { readOnly: true });
|
|
57
|
+
this.db.exec('PRAGMA busy_timeout=20000;');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (file !== ':memory:')
|
|
61
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
62
|
+
this.db = new DatabaseSync(file);
|
|
63
|
+
try {
|
|
64
|
+
this.db.exec('PRAGMA journal_mode=WAL; PRAGMA busy_timeout=20000; PRAGMA synchronous=FULL;');
|
|
65
|
+
this.db.exec(`BEGIN IMMEDIATE;
|
|
66
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
67
|
+
CREATE TABLE IF NOT EXISTS state (id INTEGER PRIMARY KEY CHECK(id=1), data TEXT NOT NULL);
|
|
68
|
+
CREATE TABLE IF NOT EXISTS events (seq INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT NOT NULL);
|
|
69
|
+
CREATE TABLE IF NOT EXISTS local (namespace TEXT NOT NULL, owner TEXT NOT NULL, key TEXT NOT NULL, data TEXT NOT NULL, PRIMARY KEY(namespace, owner, key));`);
|
|
70
|
+
this.db
|
|
71
|
+
.prepare("INSERT OR IGNORE INTO meta VALUES('stateVersion', ?)")
|
|
72
|
+
.run(String(STATE_VERSION));
|
|
73
|
+
this.db.prepare("INSERT OR REPLACE INTO meta VALUES('writer', ?)").run(writer);
|
|
74
|
+
this.db
|
|
75
|
+
.prepare('INSERT OR IGNORE INTO state VALUES(1, ?)')
|
|
76
|
+
.run(JSON.stringify(emptyDocument()));
|
|
77
|
+
this.db.exec('COMMIT');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (this.db.isTransaction)
|
|
81
|
+
this.db.exec('ROLLBACK');
|
|
82
|
+
this.db.close();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Проверка существующего файла без единой записи; отказ — HarnessError. */
|
|
87
|
+
static preflight(file) {
|
|
88
|
+
const db = new DatabaseSync(file, { readOnly: true });
|
|
89
|
+
try {
|
|
90
|
+
db.exec('PRAGMA busy_timeout=20000;');
|
|
91
|
+
const tables = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((t) => t.name));
|
|
92
|
+
if (!tables.has('meta')) {
|
|
93
|
+
if (tables.size)
|
|
94
|
+
throw new HarnessError('STATE_VERSION_UNSUPPORTED', 'Файл содержит чужую базу, а не состояние harness');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const row = db.prepare("SELECT value FROM meta WHERE key='stateVersion'").get();
|
|
98
|
+
const version = Number(row?.value);
|
|
99
|
+
if (!SUPPORTED_STATE_VERSIONS.includes(version))
|
|
100
|
+
throw new HarnessError('STATE_VERSION_UNSUPPORTED', `Формат состояния ${row?.value ?? 'неизвестен'} не поддерживается этой версией (${SUPPORTED_STATE_VERSIONS.join(', ')})`);
|
|
101
|
+
if (tables.has('state')) {
|
|
102
|
+
const state = db.prepare('SELECT data FROM state WHERE id=1').get();
|
|
103
|
+
if (state)
|
|
104
|
+
parseDocument(state.data);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
db.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
load() {
|
|
112
|
+
const row = this.db.prepare('SELECT data FROM state WHERE id=1').get();
|
|
113
|
+
return parseDocument(row.data);
|
|
114
|
+
}
|
|
115
|
+
read(fn) {
|
|
116
|
+
if (this.frame)
|
|
117
|
+
return fn(this.view(this.frame));
|
|
118
|
+
this.db.exec('BEGIN');
|
|
119
|
+
try {
|
|
120
|
+
const value = fn(this.view({ doc: this.load(), drafts: [], depth: 0 }));
|
|
121
|
+
this.db.exec('COMMIT');
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
this.db.exec('ROLLBACK');
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
transact(kind, fn) {
|
|
130
|
+
if (this.options.readOnly)
|
|
131
|
+
throw new HarnessError('STATE_VERSION_UNSUPPORTED', 'Состояние открыто только для чтения');
|
|
132
|
+
const outer = this.frame;
|
|
133
|
+
const point = 'harness_' + ++this.savepoint;
|
|
134
|
+
this.db.exec(outer ? `SAVEPOINT ${point}` : 'BEGIN IMMEDIATE');
|
|
135
|
+
let loaded;
|
|
136
|
+
try {
|
|
137
|
+
if (!outer)
|
|
138
|
+
loaded = this.load();
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
this.db.exec('ROLLBACK');
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
const frame = outer ?? { doc: loaded, drafts: [], depth: 0 };
|
|
145
|
+
const saved = outer ? structuredClone(frame.doc) : undefined;
|
|
146
|
+
const drafts = frame.drafts.length;
|
|
147
|
+
frame.depth++;
|
|
148
|
+
this.frame = frame;
|
|
149
|
+
try {
|
|
150
|
+
const value = fn(this.view(frame));
|
|
151
|
+
if (value && typeof value.then === 'function')
|
|
152
|
+
throw new Error('ExecutionStore.transact: внутри транзакции нельзя ждать внешней работы');
|
|
153
|
+
if (outer) {
|
|
154
|
+
this.db.exec(`RELEASE ${point}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (frame.drafts.length > MAX_EVENTS_PER_TRANSACTION)
|
|
158
|
+
throw new Error('Слишком много событий в одной транзакции');
|
|
159
|
+
frame.doc.sequence++;
|
|
160
|
+
const doc = executionDocumentSchema.parse(frame.doc);
|
|
161
|
+
this.db.prepare('UPDATE state SET data=? WHERE id=1').run(JSON.stringify(doc));
|
|
162
|
+
if (kind !== 'heartbeat')
|
|
163
|
+
this.writeEvents(frame.drafts);
|
|
164
|
+
this.db.exec('COMMIT');
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (outer) {
|
|
170
|
+
this.db.exec(`ROLLBACK TO ${point}; RELEASE ${point}`);
|
|
171
|
+
for (const key of Object.keys(frame.doc))
|
|
172
|
+
delete frame.doc[key];
|
|
173
|
+
Object.assign(frame.doc, saved);
|
|
174
|
+
frame.drafts.length = drafts;
|
|
175
|
+
}
|
|
176
|
+
else
|
|
177
|
+
this.db.exec('ROLLBACK');
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
frame.depth--;
|
|
182
|
+
this.frame = outer;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
writeEvents(drafts) {
|
|
186
|
+
const insert = this.db.prepare('INSERT INTO events(data) VALUES(?)');
|
|
187
|
+
const at = new Date().toISOString();
|
|
188
|
+
for (const draft of drafts) {
|
|
189
|
+
const result = insert.run('{}');
|
|
190
|
+
const seq = Number(result.lastInsertRowid);
|
|
191
|
+
const event = runEventSchema.parse({ apiVersion: API_VERSION, seq, at, ...draft });
|
|
192
|
+
this.db.prepare('UPDATE events SET data=? WHERE seq=?').run(JSON.stringify(event), seq);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
events(after = 0, limit = 500) {
|
|
196
|
+
const rows = this.db
|
|
197
|
+
.prepare('SELECT data FROM events WHERE seq>? ORDER BY seq LIMIT ?')
|
|
198
|
+
.all(after, Math.min(Math.max(limit, 1), 500));
|
|
199
|
+
return rows.map((r) => runEventSchema.parse(JSON.parse(r.data)));
|
|
200
|
+
}
|
|
201
|
+
close() {
|
|
202
|
+
this.db.close();
|
|
203
|
+
}
|
|
204
|
+
view(frame) {
|
|
205
|
+
const doc = new DocumentView(frame.doc);
|
|
206
|
+
const db = this.db;
|
|
207
|
+
const inTx = () => this.frame === frame;
|
|
208
|
+
return {
|
|
209
|
+
control: () => doc.control(),
|
|
210
|
+
controller: () => doc.controller(),
|
|
211
|
+
graph: (id) => doc.graph(id),
|
|
212
|
+
graphs: () => doc.graphs(),
|
|
213
|
+
work: (id) => doc.work(id),
|
|
214
|
+
works: (graphId) => doc.works(graphId),
|
|
215
|
+
attempt: (id) => doc.attempt(id),
|
|
216
|
+
attempts: (workId) => doc.attempts(workId),
|
|
217
|
+
verification: (id) => doc.verification(id),
|
|
218
|
+
verifications: () => doc.verifications(),
|
|
219
|
+
local(namespace, owner, key) {
|
|
220
|
+
const row = db
|
|
221
|
+
.prepare('SELECT data FROM local WHERE namespace=? AND owner=? AND key=?')
|
|
222
|
+
.get(namespace, owner, key);
|
|
223
|
+
return row
|
|
224
|
+
? localSchemas[namespace].parse(JSON.parse(row.data))
|
|
225
|
+
: undefined;
|
|
226
|
+
},
|
|
227
|
+
locals(namespace, owner) {
|
|
228
|
+
const rows = (owner === undefined
|
|
229
|
+
? db
|
|
230
|
+
.prepare('SELECT owner,key,data FROM local WHERE namespace=? ORDER BY owner,key')
|
|
231
|
+
.all(namespace)
|
|
232
|
+
: db
|
|
233
|
+
.prepare('SELECT owner,key,data FROM local WHERE namespace=? AND owner=? ORDER BY key')
|
|
234
|
+
.all(namespace, owner));
|
|
235
|
+
return rows.map((r) => ({
|
|
236
|
+
owner: r.owner,
|
|
237
|
+
key: r.key,
|
|
238
|
+
value: localSchemas[namespace].parse(JSON.parse(r.data)),
|
|
239
|
+
}));
|
|
240
|
+
},
|
|
241
|
+
putGraph: (g) => doc.putGraph(g),
|
|
242
|
+
putWork: (w) => doc.putWork(w),
|
|
243
|
+
putAttempt: (a) => doc.putAttempt(a),
|
|
244
|
+
putVerification: (v) => doc.putVerification(v),
|
|
245
|
+
setControl: (c) => doc.setControl(c),
|
|
246
|
+
setController: (l) => doc.setController(l),
|
|
247
|
+
putLocal(namespace, owner, key, value) {
|
|
248
|
+
if (!inTx())
|
|
249
|
+
throw new Error('Локальная запись вне транзакции');
|
|
250
|
+
const parsed = localSchemas[namespace].parse(value);
|
|
251
|
+
db.prepare('INSERT OR REPLACE INTO local VALUES(?,?,?,?)').run(namespace, owner || WORKSPACE_OWNER, key, JSON.stringify(parsed));
|
|
252
|
+
},
|
|
253
|
+
emit(event) {
|
|
254
|
+
if (!inTx())
|
|
255
|
+
throw new Error('Событие вне транзакции');
|
|
256
|
+
frame.drafts.push(event);
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ExecutionStore } from '../contracts/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Общий набор проверок договора `ExecutionStore`. Его проходят standalone
|
|
4
|
+
* SQLite и адаптер любого хоста: `open` открывает хранилище в каталоге,
|
|
5
|
+
* повторный вызов с тем же каталогом — то же хранилище другим экземпляром.
|
|
6
|
+
* Регистрация тестов — через переданную `test` (node:test).
|
|
7
|
+
*/
|
|
8
|
+
export declare function executionStoreContract(name: string, test: (title: string, fn: () => void | Promise<void>) => unknown, open: (dir: string) => ExecutionStore, tempDir: () => string): void;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { WORKSPACE_OWNER, digestOf, } from "../contracts/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Общий набор проверок договора `ExecutionStore`. Его проходят standalone
|
|
5
|
+
* SQLite и адаптер любого хоста: `open` открывает хранилище в каталоге,
|
|
6
|
+
* повторный вызов с тем же каталогом — то же хранилище другим экземпляром.
|
|
7
|
+
* Регистрация тестов — через переданную `test` (node:test).
|
|
8
|
+
*/
|
|
9
|
+
export function executionStoreContract(name, test, open, tempDir) {
|
|
10
|
+
const work = (id, status = 'ready') => ({
|
|
11
|
+
id,
|
|
12
|
+
graphId: 'g',
|
|
13
|
+
order: {
|
|
14
|
+
apiVersion: 'devcontour-harness/v1alpha1',
|
|
15
|
+
id,
|
|
16
|
+
source: { connector: 'files', ref: id, revision: '1' },
|
|
17
|
+
title: 'Поручение ' + id,
|
|
18
|
+
goal: 'Цель поручения ' + id,
|
|
19
|
+
acceptance: ['критерий'],
|
|
20
|
+
role: 'developer',
|
|
21
|
+
repositoryId: 'app',
|
|
22
|
+
relatedRepositories: [],
|
|
23
|
+
writeScope: ['src'],
|
|
24
|
+
dependsOn: [],
|
|
25
|
+
requirements: [],
|
|
26
|
+
contracts: [],
|
|
27
|
+
context: [],
|
|
28
|
+
},
|
|
29
|
+
status,
|
|
30
|
+
attempt: 0,
|
|
31
|
+
});
|
|
32
|
+
const phase = (runId, p = 'running') => ({
|
|
33
|
+
type: 'attempt.phase',
|
|
34
|
+
runId,
|
|
35
|
+
payload: { phase: p },
|
|
36
|
+
});
|
|
37
|
+
const call = (id) => ({
|
|
38
|
+
id,
|
|
39
|
+
stage: 'implementation',
|
|
40
|
+
runtime: 'fixture',
|
|
41
|
+
model: null,
|
|
42
|
+
runtimeVersion: null,
|
|
43
|
+
startedAt: '2026-01-01T00:00:00.000Z',
|
|
44
|
+
outcome: 'returned',
|
|
45
|
+
promptBytes: 1,
|
|
46
|
+
promptDigest: digestOf(id),
|
|
47
|
+
usage: {
|
|
48
|
+
inputTokens: 1,
|
|
49
|
+
outputTokens: 1,
|
|
50
|
+
cacheReadTokens: 0,
|
|
51
|
+
cacheWriteTokens: 0,
|
|
52
|
+
reportedUsd: null,
|
|
53
|
+
complete: true,
|
|
54
|
+
source: 'fixture',
|
|
55
|
+
},
|
|
56
|
+
costUsd: null,
|
|
57
|
+
costSource: 'unknown',
|
|
58
|
+
});
|
|
59
|
+
test(`${name}: запись видна следующей транзакции и другому экземпляру`, () => {
|
|
60
|
+
const dir = tempDir();
|
|
61
|
+
const a = open(dir);
|
|
62
|
+
a.transact('run.phase', (tx) => {
|
|
63
|
+
tx.putWork(work('w1'));
|
|
64
|
+
tx.emit(phase('r1'));
|
|
65
|
+
});
|
|
66
|
+
assert.equal(a.read((v) => v.work('w1')?.status), 'ready');
|
|
67
|
+
const b = open(dir);
|
|
68
|
+
assert.equal(b.read((v) => v.work('w1')?.status), 'ready');
|
|
69
|
+
b.transact('run.phase', (tx) => tx.putWork({ ...tx.work('w1'), status: 'running' }));
|
|
70
|
+
assert.equal(a.read((v) => v.work('w1')?.status), 'running', 'первый экземпляр видит запись второго');
|
|
71
|
+
const events = a.events();
|
|
72
|
+
assert.equal(events.length, 1);
|
|
73
|
+
assert.equal(events[0].type, 'attempt.phase');
|
|
74
|
+
a.close();
|
|
75
|
+
b.close();
|
|
76
|
+
});
|
|
77
|
+
test(`${name}: исключение откатывает записи, локальные записи и события`, () => {
|
|
78
|
+
const s = open(tempDir());
|
|
79
|
+
assert.throws(() => s.transact('run.phase', (tx) => {
|
|
80
|
+
tx.putWork(work('w1'));
|
|
81
|
+
tx.putLocal('usage', 'app', 'c1', call('c1'));
|
|
82
|
+
tx.emit(phase('r1'));
|
|
83
|
+
throw new Error('отказ');
|
|
84
|
+
}));
|
|
85
|
+
assert.equal(s.read((v) => v.work('w1')), undefined);
|
|
86
|
+
assert.equal(s.read((v) => v.local('usage', 'app', 'c1')), undefined);
|
|
87
|
+
assert.deepEqual(s.events(), []);
|
|
88
|
+
s.close();
|
|
89
|
+
});
|
|
90
|
+
test(`${name}: вложенная сохраняет оба изменения; её отказ откатывает только её часть`, () => {
|
|
91
|
+
const s = open(tempDir());
|
|
92
|
+
s.transact('run.claimed', (tx) => {
|
|
93
|
+
tx.putWork(work('outer'));
|
|
94
|
+
s.transact('run.phase', (inner) => {
|
|
95
|
+
assert.ok(inner.work('outer'), 'вложенная видит несохранённое внешней');
|
|
96
|
+
inner.putWork(work('inner'));
|
|
97
|
+
inner.emit(phase('r-inner'));
|
|
98
|
+
});
|
|
99
|
+
assert.throws(() => s.transact('run.phase', (inner) => {
|
|
100
|
+
inner.putWork(work('lost'));
|
|
101
|
+
inner.putLocal('usage', 'app', 'lost', call('lost'));
|
|
102
|
+
inner.emit(phase('r-lost'));
|
|
103
|
+
throw new Error('вложенный отказ');
|
|
104
|
+
}));
|
|
105
|
+
tx.emit(phase('r-outer'));
|
|
106
|
+
});
|
|
107
|
+
assert.ok(s.read((v) => v.work('outer')));
|
|
108
|
+
assert.ok(s.read((v) => v.work('inner')));
|
|
109
|
+
assert.equal(s.read((v) => v.work('lost')), undefined);
|
|
110
|
+
assert.equal(s.read((v) => v.local('usage', 'app', 'lost')), undefined);
|
|
111
|
+
assert.deepEqual(s.events().map((e) => e.runId), ['r-inner', 'r-outer']);
|
|
112
|
+
s.close();
|
|
113
|
+
});
|
|
114
|
+
test(`${name}: Promise внутри транзакции отклоняется, ничего не записано`, () => {
|
|
115
|
+
const s = open(tempDir());
|
|
116
|
+
assert.throws(() => s.transact('run.phase', (tx) => {
|
|
117
|
+
tx.putWork(work('w1'));
|
|
118
|
+
return Promise.resolve(1);
|
|
119
|
+
}));
|
|
120
|
+
assert.equal(s.read((v) => v.work('w1')), undefined);
|
|
121
|
+
s.close();
|
|
122
|
+
});
|
|
123
|
+
test(`${name}: heartbeat без события; транзакция только с событием его пишет`, () => {
|
|
124
|
+
const s = open(tempDir());
|
|
125
|
+
s.transact('run.phase', (tx) => tx.putWork(work('w1')));
|
|
126
|
+
s.transact('heartbeat', (tx) => tx.putWork({ ...tx.work('w1'), attempt: 1 }));
|
|
127
|
+
assert.equal(s.read((v) => v.work('w1')?.attempt), 1);
|
|
128
|
+
assert.deepEqual(s.events(), []);
|
|
129
|
+
s.transact('run.phase', (tx) => tx.emit(phase('r1')));
|
|
130
|
+
assert.equal(s.events().length, 1);
|
|
131
|
+
s.close();
|
|
132
|
+
});
|
|
133
|
+
test(`${name}: события закрыты схемой; курсор строго по возрастанию`, () => {
|
|
134
|
+
const s = open(tempDir());
|
|
135
|
+
assert.throws(() => s.transact('run.phase', (tx) => tx.emit({ type: 'attempt.phase', payload: { phase: 'running', token: 'x' } })));
|
|
136
|
+
assert.deepEqual(s.events(), [], 'событие вне договора не записано');
|
|
137
|
+
for (let i = 0; i < 5; i++)
|
|
138
|
+
s.transact('run.phase', (tx) => tx.emit(phase('r' + i)));
|
|
139
|
+
const all = s.events();
|
|
140
|
+
assert.deepEqual(all.map((e) => e.runId), ['r0', 'r1', 'r2', 'r3', 'r4']);
|
|
141
|
+
for (let i = 1; i < all.length; i++)
|
|
142
|
+
assert.ok(all[i].seq > all[i - 1].seq);
|
|
143
|
+
assert.deepEqual(s.events(all[1].seq).map((e) => e.runId), ['r2', 'r3', 'r4']);
|
|
144
|
+
assert.deepEqual(s.events(0, 2).map((e) => e.runId), ['r0', 'r1']);
|
|
145
|
+
s.close();
|
|
146
|
+
});
|
|
147
|
+
test(`${name}: локальные записи различаются владельцем и проверяются схемой`, () => {
|
|
148
|
+
const s = open(tempDir());
|
|
149
|
+
s.transact('run.phase', (tx) => {
|
|
150
|
+
tx.putLocal('usage', 'app', 'k', call('a'));
|
|
151
|
+
tx.putLocal('usage', WORKSPACE_OWNER, 'k', call('b'));
|
|
152
|
+
});
|
|
153
|
+
assert.equal(s.read((v) => v.local('usage', 'app', 'k')?.id), 'a');
|
|
154
|
+
assert.equal(s.read((v) => v.local('usage', WORKSPACE_OWNER, 'k')?.id), 'b');
|
|
155
|
+
assert.equal(s.read((v) => v.locals('usage').length), 2);
|
|
156
|
+
assert.equal(s.read((v) => v.locals('usage', 'app').length), 1);
|
|
157
|
+
assert.throws(() => s.transact('run.phase', (tx) => tx.putLocal('usage', 'app', 'bad', { id: 'bad' })));
|
|
158
|
+
assert.equal(s.read((v) => v.local('usage', 'app', 'bad')), undefined);
|
|
159
|
+
s.close();
|
|
160
|
+
});
|
|
161
|
+
test(`${name}: запись вне договора модели отклоняется целиком`, () => {
|
|
162
|
+
const s = open(tempDir());
|
|
163
|
+
assert.throws(() => s.transact('run.phase', (tx) => tx.putWork({ ...work('w1'), status: 'finished' })));
|
|
164
|
+
assert.equal(s.read((v) => v.work('w1')), undefined);
|
|
165
|
+
s.close();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolchains: диагностика (doctor/preflight) и рецепты композиции целевого
|
|
3
|
+
* репозитория — npm, pnpm, Turborepo, Go, Python/uv и их смешение в одном
|
|
4
|
+
* Git root. Opt-in entrypoint: корень пакета и `./contracts` его не
|
|
5
|
+
* импортируют.
|
|
6
|
+
*/
|
|
7
|
+
export { inspectPolicy, inspectRepository, assertToolchainReady, problemLines, toolchainProblemCodes, type InspectOptions, type PlanEntry, type ToolEntry, type ToolchainProblem, type ToolchainProblemCode, type ToolchainReport, } from './inspect.ts';
|
|
8
|
+
export { defaultProbe, resolveExecutable, satisfies, versionOf as toolVersionOf, PROBE_TIMEOUT_MS, type ToolProbe, } from './probe.ts';
|
|
9
|
+
export { composeRepository, installRecipes, type InstallRecipe, type ToolchainComponent, } from './recipes.ts';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolchains: диагностика (doctor/preflight) и рецепты композиции целевого
|
|
3
|
+
* репозитория — npm, pnpm, Turborepo, Go, Python/uv и их смешение в одном
|
|
4
|
+
* Git root. Opt-in entrypoint: корень пакета и `./contracts` его не
|
|
5
|
+
* импортируют.
|
|
6
|
+
*/
|
|
7
|
+
export { inspectPolicy, inspectRepository, assertToolchainReady, problemLines, toolchainProblemCodes, } from "./inspect.js";
|
|
8
|
+
export { defaultProbe, resolveExecutable, satisfies, versionOf as toolVersionOf, PROBE_TIMEOUT_MS, } from "./probe.js";
|
|
9
|
+
export { composeRepository, installRecipes, } from "./recipes.js";
|