devcontour-harness 0.1.0-alpha.5 → 0.1.0-alpha.7

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.
Files changed (263) hide show
  1. package/README.md +42 -2
  2. package/docs/compatibility-versions.md +56 -0
  3. package/docs/compatibility.md +2 -2
  4. package/docs/connectors.md +323 -0
  5. package/docs/contracts.md +33 -5
  6. package/docs/integrations/hindsight.md +148 -0
  7. package/docs/quickstart.md +1 -1
  8. package/docs/release-notes/0.1.0-alpha.6.md +238 -0
  9. package/docs/release-notes/0.1.0-alpha.7.md +71 -0
  10. package/docs/review-probes.md +199 -0
  11. package/docs/review-protocol.md +251 -0
  12. package/docs/toolchains.md +167 -0
  13. package/docs/turbo-starter.md +269 -0
  14. package/docs/upgrade.md +77 -24
  15. package/examples/README.md +1 -0
  16. package/examples/toolchains/README.md +32 -0
  17. package/examples/toolchains/go/bad/label/label.go +9 -0
  18. package/examples/toolchains/go/good/label/label.go +9 -0
  19. package/examples/toolchains/go/policy.json +79 -0
  20. package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
  21. package/examples/toolchains/go/repo/_gitignore +1 -0
  22. package/examples/toolchains/go/repo/go.mod +5 -0
  23. package/examples/toolchains/go/repo/go.sum +2 -0
  24. package/examples/toolchains/go/repo/label/label.go +9 -0
  25. package/examples/toolchains/go/repo/label/label_test.go +27 -0
  26. package/examples/toolchains/go/repo/label/version.go +9 -0
  27. package/examples/toolchains/go/task.json +25 -0
  28. package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
  29. package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
  30. package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
  31. package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
  32. package/examples/toolchains/mixed-go/policy.json +126 -0
  33. package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
  34. package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
  35. package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
  36. package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
  37. package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
  38. package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
  39. package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
  40. package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
  41. package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
  42. package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
  43. package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
  44. package/examples/toolchains/mixed-go/task.json +25 -0
  45. package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
  46. package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
  47. package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
  48. package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
  49. package/examples/toolchains/mixed-python/policy.json +121 -0
  50. package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
  51. package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
  52. package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
  53. package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
  54. package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
  55. package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
  56. package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
  57. package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
  58. package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
  59. package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
  60. package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
  61. package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
  62. package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
  63. package/examples/toolchains/mixed-python/task.json +25 -0
  64. package/examples/toolchains/npm/bad/src/label.js +4 -0
  65. package/examples/toolchains/npm/good/src/label.js +4 -0
  66. package/examples/toolchains/npm/policy.json +82 -0
  67. package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
  68. package/examples/toolchains/npm/repo/_gitignore +4 -0
  69. package/examples/toolchains/npm/repo/package-lock.json +22 -0
  70. package/examples/toolchains/npm/repo/package.json +13 -0
  71. package/examples/toolchains/npm/repo/src/label.js +4 -0
  72. package/examples/toolchains/npm/repo/test/label.test.js +13 -0
  73. package/examples/toolchains/npm/task.json +25 -0
  74. package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
  75. package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
  76. package/examples/toolchains/pnpm/policy.json +85 -0
  77. package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
  78. package/examples/toolchains/pnpm/repo/_gitignore +4 -0
  79. package/examples/toolchains/pnpm/repo/package.json +10 -0
  80. package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
  81. package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
  82. package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
  83. package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
  84. package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
  85. package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
  86. package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
  87. package/examples/toolchains/pnpm/task.json +25 -0
  88. package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
  89. package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
  90. package/examples/toolchains/python/policy.json +80 -0
  91. package/examples/toolchains/python/repo/.python-version +1 -0
  92. package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
  93. package/examples/toolchains/python/repo/_gitignore +5 -0
  94. package/examples/toolchains/python/repo/pyproject.toml +19 -0
  95. package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
  96. package/examples/toolchains/python/repo/tests/test_label.py +13 -0
  97. package/examples/toolchains/python/repo/uv.lock +108 -0
  98. package/examples/toolchains/python/task.json +25 -0
  99. package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
  100. package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
  101. package/examples/toolchains/turbo/policy.json +110 -0
  102. package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
  103. package/examples/toolchains/turbo/repo/_gitignore +4 -0
  104. package/examples/toolchains/turbo/repo/package.json +13 -0
  105. package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
  106. package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
  107. package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
  108. package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
  109. package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
  110. package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
  111. package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
  112. package/examples/toolchains/turbo/repo/turbo.json +7 -0
  113. package/examples/toolchains/turbo/task.json +25 -0
  114. package/examples/turbo-starter/README.md +16 -0
  115. package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
  116. package/examples/turbo-starter/changes/issued-at.patch +130 -0
  117. package/examples/turbo-starter/cleanup.mjs +58 -0
  118. package/examples/turbo-starter/graph.json +32 -0
  119. package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
  120. package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
  121. package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
  122. package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
  123. package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
  124. package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
  125. package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
  126. package/examples/turbo-starter/harness/env/backend.env.template +7 -0
  127. package/examples/turbo-starter/harness/env/compose.env.template +6 -0
  128. package/examples/turbo-starter/policy.json +109 -0
  129. package/examples/turbo-starter/policy.lifecycle.json +114 -0
  130. package/examples/turbo-starter/setup.mjs +352 -0
  131. package/lib/agents/cli-adapter.js +10 -1
  132. package/lib/agents/inspect.d.ts +56 -2
  133. package/lib/agents/inspect.js +264 -14
  134. package/lib/agents/review-probe.d.ts +229 -7
  135. package/lib/agents/review-probe.js +830 -72
  136. package/lib/agents/schemas.d.ts +3 -1
  137. package/lib/agents/schemas.js +7 -3
  138. package/lib/cli.js +167 -7
  139. package/lib/contracts/cli.d.ts +820 -11
  140. package/lib/contracts/cli.js +243 -4
  141. package/lib/contracts/errors.d.ts +3 -1
  142. package/lib/contracts/errors.js +2 -0
  143. package/lib/contracts/events.d.ts +37 -5
  144. package/lib/contracts/events.js +9 -0
  145. package/lib/contracts/execution.d.ts +389 -15
  146. package/lib/contracts/execution.js +76 -4
  147. package/lib/contracts/failure.js +5 -0
  148. package/lib/contracts/index.d.ts +1 -0
  149. package/lib/contracts/index.js +1 -0
  150. package/lib/contracts/policy.d.ts +15 -0
  151. package/lib/contracts/policy.js +10 -0
  152. package/lib/contracts/ports.d.ts +50 -0
  153. package/lib/contracts/profiles.d.ts +14 -0
  154. package/lib/contracts/profiles.js +4 -0
  155. package/lib/contracts/published.d.ts +279 -8
  156. package/lib/contracts/result.d.ts +171 -0
  157. package/lib/contracts/result.js +39 -1
  158. package/lib/contracts/review.d.ts +231 -0
  159. package/lib/contracts/review.js +326 -2
  160. package/lib/contracts/toolchain.d.ts +96 -0
  161. package/lib/contracts/toolchain.js +89 -0
  162. package/lib/control/doctor.d.ts +16 -1
  163. package/lib/control/doctor.js +45 -3
  164. package/lib/control/harness.d.ts +52 -6
  165. package/lib/control/harness.js +28 -1
  166. package/lib/control/recovery.d.ts +3 -1
  167. package/lib/control/review.js +23 -11
  168. package/lib/engine/budget.d.ts +3 -3
  169. package/lib/engine/classify.js +1 -0
  170. package/lib/engine/engine.d.ts +67 -17
  171. package/lib/engine/prompt.d.ts +29 -1
  172. package/lib/engine/prompt.js +57 -0
  173. package/lib/engine/property-recovery.js +2 -0
  174. package/lib/engine/result.js +52 -0
  175. package/lib/engine/reuse.js +6 -1
  176. package/lib/engine/runner.d.ts +25 -0
  177. package/lib/engine/runner.js +500 -123
  178. package/lib/environment/lifecycle.js +6 -0
  179. package/lib/environment/live-calls.js +3 -2
  180. package/lib/environment/os-tools.d.ts +16 -0
  181. package/lib/environment/os-tools.js +35 -0
  182. package/lib/environment/process.d.ts +8 -0
  183. package/lib/environment/process.js +13 -6
  184. package/lib/integrations/connector/commands.d.ts +358 -0
  185. package/lib/integrations/connector/commands.js +428 -0
  186. package/lib/integrations/connector/config.d.ts +215 -0
  187. package/lib/integrations/connector/config.js +214 -0
  188. package/lib/integrations/connector/delivery.d.ts +107 -0
  189. package/lib/integrations/connector/delivery.js +591 -0
  190. package/lib/integrations/connector/fake.d.ts +291 -0
  191. package/lib/integrations/connector/fake.js +786 -0
  192. package/lib/integrations/connector/history.d.ts +124 -0
  193. package/lib/integrations/connector/history.js +497 -0
  194. package/lib/integrations/connector/index.d.ts +16 -0
  195. package/lib/integrations/connector/index.js +16 -0
  196. package/lib/integrations/connector/redact.d.ts +19 -0
  197. package/lib/integrations/connector/redact.js +91 -0
  198. package/lib/integrations/connector/registry.d.ts +15 -0
  199. package/lib/integrations/connector/registry.js +43 -0
  200. package/lib/integrations/connector/snapshot.d.ts +131 -0
  201. package/lib/integrations/connector/snapshot.js +422 -0
  202. package/lib/integrations/connector/source.d.ts +37 -0
  203. package/lib/integrations/connector/source.js +73 -0
  204. package/lib/integrations/connector/steps.d.ts +71 -0
  205. package/lib/integrations/connector/steps.js +188 -0
  206. package/lib/integrations/connector/store.d.ts +140 -0
  207. package/lib/integrations/connector/store.js +277 -0
  208. package/lib/integrations/connector/summary.d.ts +38 -0
  209. package/lib/integrations/connector/summary.js +59 -0
  210. package/lib/integrations/connector/testing-worker.d.ts +1 -0
  211. package/lib/integrations/connector/testing-worker.js +81 -0
  212. package/lib/integrations/connector/testing.d.ts +205 -0
  213. package/lib/integrations/connector/testing.js +1615 -0
  214. package/lib/integrations/connector/transport.d.ts +240 -0
  215. package/lib/integrations/connector/transport.js +28 -0
  216. package/lib/integrations/memory/context.d.ts +120 -0
  217. package/lib/integrations/memory/context.js +239 -0
  218. package/lib/integrations/memory/index.d.ts +8 -0
  219. package/lib/integrations/memory/index.js +8 -0
  220. package/lib/integrations/memory/provider.d.ts +79 -0
  221. package/lib/integrations/memory/provider.js +22 -0
  222. package/lib/integrations/memory/sink.d.ts +56 -0
  223. package/lib/integrations/memory/sink.js +116 -0
  224. package/lib/integrations/memory/testing.d.ts +44 -0
  225. package/lib/integrations/memory/testing.js +61 -0
  226. package/lib/integrations/relay-v1/index.d.ts +122 -0
  227. package/lib/integrations/relay-v1/index.js +860 -0
  228. package/lib/integrations/relay-v2/index.d.ts +145 -0
  229. package/lib/integrations/relay-v2/index.js +752 -0
  230. package/lib/integrations/relay-v2/process.d.ts +32 -0
  231. package/lib/integrations/relay-v2/process.js +112 -0
  232. package/lib/integrations/relay-v2/protocol.d.ts +172 -0
  233. package/lib/integrations/relay-v2/protocol.js +142 -0
  234. package/lib/profiles/resolve.d.ts +5 -1
  235. package/lib/profiles/resolve.js +31 -0
  236. package/lib/storage/document.d.ts +116 -16
  237. package/lib/toolchains/index.d.ts +9 -0
  238. package/lib/toolchains/index.js +9 -0
  239. package/lib/toolchains/inspect.d.ts +105 -0
  240. package/lib/toolchains/inspect.js +831 -0
  241. package/lib/toolchains/manifests.d.ts +67 -0
  242. package/lib/toolchains/manifests.js +238 -0
  243. package/lib/toolchains/probe.d.ts +28 -0
  244. package/lib/toolchains/probe.js +54 -0
  245. package/lib/toolchains/recipes.d.ts +33 -0
  246. package/lib/toolchains/recipes.js +51 -0
  247. package/lib/verification/acceptance.d.ts +16 -0
  248. package/lib/verification/acceptance.js +16 -0
  249. package/lib/verification/gates.d.ts +38 -0
  250. package/lib/verification/gates.js +108 -26
  251. package/package.json +22 -1
  252. package/profiles/go-gotestsum.json +21 -0
  253. package/profiles/npm-ci.json +22 -0
  254. package/profiles/pnpm-frozen.json +24 -0
  255. package/profiles/python-uv.json +26 -0
  256. package/schemas/execution-document.schema.json +345 -4
  257. package/schemas/execution-policy.schema.json +48 -0
  258. package/schemas/result-envelope.schema.json +213 -1
  259. package/schemas/run-event.schema.json +93 -0
  260. package/schemas/run-result.schema.json +207 -0
  261. package/schemas/stack-profile.schema.json +43 -0
  262. package/templates/controller-mode/AGENTS.controller.md +58 -0
  263. package/templates/controller-mode/README.md +44 -0
@@ -0,0 +1,167 @@
1
+ # Toolchains целевого проекта
2
+
3
+ Harness — Node.js-инструмент. Исполняемому проекту не нужны package.json, npm или
4
+ JavaScript: gates и шаги подготовки — произвольные argv, каталог gate — `cwd`, окружение —
5
+ `environment`. npm и pnpm — менеджеры пакетов, Turborepo — планировщик задач, Go и
6
+ Python/uv — отдельные toolchain. Единого «типа проекта» нет: репозиторий описывается
7
+ композицией шагов.
8
+
9
+ | Что | Где в политике |
10
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
11
+ | Установка зависимостей (locked) | `repositories[].prepare[]` — исполняется в каждом worktree попытки до writer, отдельно для candidate и integration |
12
+ | Команды задач и проверки | `repositories[].gates[]` (`check` / `test` с JUnit) |
13
+ | Каталог | `gates[].cwd`; у шага `prepare` — собственный флаг инструмента (`pnpm --dir`, `npm --prefix`, `uv --directory`, `go -C`) |
14
+ | Окружение | `environment` политики/репозитория (`values`, `inherit`, `secrets`) |
15
+ | Проверенные версии инструментов и явный выбор менеджера пакетов | `repositories[].toolchain` (`tools`, `packageManagers`), необязательно |
16
+
17
+ `toolchain` — необязательное аддитивное поле: политики и профили без него сохраняют прежний
18
+ digest. `tools` — `{ "gotestsum": "1.12.3" }` (названные компоненты версии совпадают) или
19
+ `{ "go": ">=1.24" }` (не ниже). `packageManagers` — `{ ".": "pnpm", "frontend": "pnpm" }`:
20
+ явный выбор снимает неоднозначность, когда в каталоге лежат lockfiles нескольких менеджеров.
21
+
22
+ ## Рецепты
23
+
24
+ Проверенный набор (macOS arm64, Darwin 24.6): Node 24.3.0, npm 11.4.2, pnpm 10.12.3,
25
+ Turbo 2.11.10 (devDependency fixture), Go 1.24.5, gotestsum v1.12.3, uv 0.11.28,
26
+ CPython 3.12.9 (выбран uv по `.python-version`), pytest 8.4.1, ruff 0.12.4.
27
+
28
+ | Вариант | prepare | gates | Встроенный профиль |
29
+ | ------------------------------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
30
+ | Node + npm | `npm ci` | `npm run check`, `npm run test` (JUnit `.reports/junit.xml`) | `npm-ci@0.1.0` |
31
+ | Node + pnpm | `pnpm install --frozen-lockfile` | `pnpm run check`, `pnpm run test` | `pnpm-frozen@0.1.0` |
32
+ | pnpm + Turborepo | `pnpm install --frozen-lockfile` | `pnpm exec turbo run check --filter=<pkg>`, `pnpm exec turbo run test --filter=<pkg> --force` | — (фильтры проекта: политика, см. `examples/toolchains/turbo`) |
33
+ | Go | `go mod download` | `go vet ./...`, `gotestsum --junitfile .reports/junit.xml -- -count=1 ./...` | `go-gotestsum@0.1.0` |
34
+ | Python | `uv sync --locked` | `uv run --no-sync ruff check .`, `uv run --no-sync pytest --junitxml=.reports/junit.xml` | `python-uv@0.1.0` |
35
+ | pnpm frontend + Go backend | `pnpm --dir frontend install --frozen-lockfile`, `go -C backend mod download` | gates с `cwd: frontend` / `cwd: backend` | — (`examples/toolchains/mixed-go`) |
36
+ | pnpm frontend + Python backend | `pnpm --dir frontend install --frozen-lockfile`, `uv sync --locked --directory backend` | gates с `cwd: frontend` / `cwd: backend` | — (`examples/toolchains/mixed-python`) |
37
+
38
+ Правила рецептов:
39
+
40
+ - Только locked-установка: `npm ci`, `pnpm install --frozen-lockfile`, `uv sync --locked`,
41
+ `go mod download`. Несовместимый lockfile — отказ шага с диагностикой менеджера, lockfile
42
+ не переписывается. `npm install`, `pnpm install` без `--frozen-lockfile`, `uv sync` без
43
+ `--locked`, `uv run` без `--no-sync`, `go get`, `go mod tidy` — проблема preflight.
44
+ - Точные версии, без `@latest`, `npx`, `pnpm dlx` без версии, `uvx` без версии, `--if-present`.
45
+ Отсутствующий test task не заменяется build/lint.
46
+ - Отчёт gate лежит относительно корня worktree, а не `cwd`: gate `cwd: backend` пишет JUnit в
47
+ `../.reports/backend.xml`, gate объявляет `.reports/backend.xml`. Harness удаляет файл
48
+ отчёта до запуска и передаёт путь в `HARNESS_REPORT_PATH`.
49
+ - Один monorepo — одна запись Git repository с разными `cwd`. `consumers` — только для
50
+ действительно отдельных репозиториев.
51
+ - Профили языков (`go-gotestsum`, `python-uv`, прежние `go-api`, `python-api`) не наследуют
52
+ npm-подготовку. Слои профиля с разными версиями одного инструмента или разными менеджерами
53
+ одного каталога — отказ разрешения (`Конфликт частей профиля: toolchain.…`).
54
+ - Композиция из кода: `composeRepository(own, components, tools)` и `installRecipes` из
55
+ opt-in entrypoint `devcontour-harness/toolchains`.
56
+
57
+ ## Установка harness для Go/Python-проекта
58
+
59
+ Target-проекту package.json не нужен. Harness ставится в отдельное окружение инструмента.
60
+
61
+ npm:
62
+
63
+ ```sh
64
+ mkdir -p ~/.local/share/harness-tool && cd ~/.local/share/harness-tool
65
+ npm init -y >/dev/null
66
+ npm install --save-exact devcontour-harness@<версия>
67
+ export PATH="$PWD/node_modules/.bin:$PATH"
68
+ harness doctor --policy /path/to/project-harness/policy.json
69
+ ```
70
+
71
+ pnpm:
72
+
73
+ ```sh
74
+ mkdir -p ~/.local/share/harness-tool && cd ~/.local/share/harness-tool
75
+ pnpm init >/dev/null
76
+ pnpm add --save-exact devcontour-harness@<версия>
77
+ export PATH="$PWD/node_modules/.bin:$PATH"
78
+ ```
79
+
80
+ Политика и граф могут лежать вне target-репозитория: `repositories[].path` относителен
81
+ каталогу политики (`examples/toolchains/go/policy.json` → `"path": "repo"`).
82
+
83
+ ## Cache и изменяемые каталоги
84
+
85
+ Каждая попытка исполняется в своём worktree (`<state>/repos/<repo>/worktrees/<attempt>` и
86
+ `…-integration`), и `prepare` устанавливает зависимости заново в нём. Изменяемые артефакты
87
+ одной попытки другой не видны.
88
+
89
+ | Допустимый общий cache (content-addressed, вне worktree) | Изменяемое, только внутри worktree попытки |
90
+ | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
91
+ | npm cache (`~/.npm`), pnpm store, Go module cache (`GOMODCACHE`), Go build cache (`GOCACHE`), uv cache | `node_modules/`, `.venv/`, `.reports/`, `.turbo/`, выходы сборки |
92
+
93
+ Cache сборки допустим, но не заменяет тест:
94
+
95
+ - Во время исполнения каждый gate `kind: test` получает `TURBO_FORCE=true` и `GOFLAGS`,
96
+ оканчивающийся на `-count=1` (после значения политики). Окружение наследуют все потомки, так
97
+ что защита действует при любой обёртке (package script, `pnpm --filter … run`, task runner),
98
+ в библиотечном `Harness.run`/`Runner` и в CLI одинаково. Заголовок лога gate записывает
99
+ применённые значения (`fresh={...}`); digest лога входит в evidence. `check` gates cache
100
+ используют как обычно.
101
+ - Turbo: preflight дополнительно требует явной свежести в самой команде — `--force`,
102
+ `--cache=` без чтения или `TURBO_FORCE=true` — и находит turbo внутри package scripts
103
+ (напрямую, через `pnpm run`/`npm run`, `pnpm --filter … run`, `pnpm exec`, `npx`). Без неё —
104
+ `CACHE_REPLAY`. Script turbo-проекта, который не разбирается статически (`$(…)`, обратные
105
+ кавычки, конвейер, `sh -c`, `eval`, `xargs`), — `SCRIPT_UNRESOLVED`: угадывать нельзя.
106
+ Проверено T03: при входах задачи без `src` неверная реализация вне harness даёт «cache hit,
107
+ replaying» и зелёный отчёт; тот же script через gate — «cache bypass, force executing» и
108
+ отказ, в том числе через общий `TURBO_CACHE_DIR` и `Harness.run` без CLI.
109
+ - Go: команда без `-count=1` в test gate — предупреждение (`warnings`): прежний `go-api`
110
+ продолжает проходить preflight, а свежесть обеспечивает `GOFLAGS` во время gate.
111
+
112
+ Isolation `os` пишет только в worktree и временный каталог проверки: для Go/uv/pnpm в
113
+ песочнице cache-каталоги (`GOCACHE`, `UV_CACHE_DIR`, store) нужно указать доступными или
114
+ использовать `isolation.mode: none`, как в примерах. Установка (`prepare`) идёт вне песочницы.
115
+
116
+ ## Диагностика: doctor и preflight
117
+
118
+ `harness doctor --policy policy.json` (или `--state`) добавляет `toolchains[]` — по
119
+ репозиторию политики:
120
+
121
+ - `plan` — каждый шаг и gate: фаза, `cwd`, argv, путь отчёта, поле конфигурации;
122
+ - `tools` — внешние инструменты из PATH (найден, версия по пробе, требования и их источник:
123
+ `toolchain.tools`, `package.json#packageManager`, `go.mod#go`) и инструменты среды проекта
124
+ (`turbo` из `package.json`: диапазон допустим, если lockfile закрепляет точную версию,
125
+ иначе `UNPINNED`; `ruff`/`pytest` из `uv.lock`) — последние не запускаются;
126
+ - `lockfiles` — каталоги node-проектов, их lockfiles и выбранный менеджер;
127
+ - `reports`, `ambiguities` (с `resolvedBy`), `problems`, `warnings`, `configSource`.
128
+
129
+ Каждая проблема — код, поле (`repositories.0.gates.1.command`) и путь файла проекта:
130
+ `TOOL_MISSING`, `TOOL_VERSION`, `TOOL_VERSION_UNKNOWN`, `TOOL_NOT_LOCKED`, `MANIFEST_MISSING`,
131
+ `LOCKFILE_MISSING`, `LOCKFILE_AMBIGUOUS`, `SCRIPT_UNKNOWN`, `TASK_UNKNOWN`, `FILTER_UNKNOWN`,
132
+ `INSTALL_UNLOCKED`, `UNPINNED`, `IF_PRESENT`, `CACHE_REPLAY`, `REPORT_MISMATCH`,
133
+ `COMPOSITION_CONFLICT`, `CWD_MISSING`, `PROJECT_FILE_MISSING`, `REPOSITORY_MISSING`,
134
+ `TOOL_IN_PROJECT`, `SCRIPT_UNRESOLVED`.
135
+
136
+ Диагностика только читает файлы (package.json, pnpm-workspace.yaml, turbo.json, go.mod,
137
+ pyproject.toml, uv.lock) и запускает пробы версий внешних инструментов: `<tool> --version`
138
+ (`go version`; для Go-бинарника без метки версии — `go version -m <файл>`), в нейтральном
139
+ временном каталоге, по одной на инструмент, с пределом 10 с и одним повтором с пределом 20 с, если проба не
140
+ уложилась во время (нагрузка). Перед пробой путь инструмента
141
+ разрешается до канонического (все symlink файла и каталога PATH): файл внутри любого
142
+ репозитория политики (включая его `node_modules/.bin`, `.venv`) или каталога состояния с
143
+ worktrees не запускается — проблема `TOOL_IN_PROJECT`. Ни установки, ни генерации, ни новых
144
+ файлов.
145
+
146
+ `doctor` по-прежнему информационный (exit 0, проблемы в `problems`). С политикой он требует
147
+ только runtime, назначенные политикой (`reason: "не назначен политикой"` у остальных).
148
+ `harness run` выполняет ту же диагностику как обязательный preflight: при любой проблеме —
149
+ exit 3, `NOT_READY` с issues `policy.<поле>` до записи `binding.json`, открытия состояния и
150
+ вызова runtime.
151
+
152
+ ## Пример и приёмка
153
+
154
+ `examples/toolchains/<variant>/` — `repo/` (target-проект; `_gitignore` становится
155
+ `.gitignore`), `policy.json`, `task.json`, `good/` и `bad/` (реализации для fixture writer).
156
+ <!-- repo-only -->
157
+
158
+ В репозитории harness (не входит в пакет). Сквозной прогон всех семи вариантов через обычный путь harness:
159
+
160
+ ```sh
161
+ node --import tsx scripts/acceptance/t01.mjs # все варианты
162
+ node --import tsx scripts/acceptance/t01.mjs go python # выборочно
163
+ ```
164
+
165
+ <!-- /repo-only -->
166
+
167
+ Нужны npm, pnpm, go + gotestsum, uv в PATH; отсутствующий инструмент — `BLOCKED`.
@@ -0,0 +1,269 @@
1
+ # Turbo Starter: рецепт и lifecycle-мост
2
+
3
+ Это первый продуктовый сценарий harness на настоящем pnpm/Turborepo-проекте.
4
+ В копию [Turbo Starter](https://github.com/oim-dev/turbo-starter) на commit
5
+ `78f1e76b80f76ae9d4289e0b0994df8a6f2b89cc` вносится небольшое изменение
6
+ контракта backend. OpenAPI и SDK генерируются штатной командой starter, затем
7
+ проверяется frontend-потребитель. Всё, чего нет в upstream (приёмочные
8
+ проверки с JUnit, политика, граф, мост Docker, шаблоны env), лежит в
9
+ `examples/turbo-starter/` пакета. Upstream не патчится.
10
+
11
+ | Файл | Назначение |
12
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
13
+ | `examples/turbo-starter/policy.json` | Политика основного сценария: `prepare` (`pnpm install --frozen-lockfile --offline`), gates, защищённые пути, toolchain (`pnpm 11.25.0`, `node >=24.3`) |
14
+ | `examples/turbo-starter/graph.json` | Поручение `issued-at`: область записи, gates, контекст `controller-mode` |
15
+ | `examples/turbo-starter/policy.lifecycle.json` | Политика с lifecycle `compose-bridge` (PostgreSQL на попытку и фазу) |
16
+ | `examples/turbo-starter/harness/` | Копируется в `.harness/` проекта: `compose-bridge.mjs`, `compose.acceptance.yml`, `env/*.template`, `acceptance/*.test.mjs` |
17
+ | `examples/turbo-starter/changes/*.patch` | Подготовленные изменения для fixture writer: `issued-at.patch` (верное, вместе с выходом генератора) и `issued-at-consumer-only.patch` (несовместимое) |
18
+ | `templates/controller-mode/` | Оверлей `AGENTS.controller.md` v1 и инструкция включения |
19
+
20
+ ## Как повторить
21
+
22
+ Всё нужное лежит в npm-архиве (`examples/turbo-starter/`, `templates/controller-mode/`);
23
+ checkout исходников harness не требуется. После `npm install devcontour-harness`:
24
+
25
+ ```sh
26
+ # 1. Копия starter + настройка (отдельный baseline-коммит) + pnpm 11.25.0 в <work>/tools,
27
+ # рядом — policy.json, graph.json и context/controller-mode/ (оверлей с provenance)
28
+ node node_modules/devcontour-harness/examples/turbo-starter/setup.mjs \
29
+ --work ./ts --source https://github.com/oim-dev/turbo-starter
30
+ # 2. Обычный путь harness (writer/reviewer — настоящие codex/claude из PATH)
31
+ cd ts && export PATH=$PWD/tools/node_modules/.bin:$PATH # npx найдёт harness проекта выше по дереву
32
+ npx harness validate --graph graph.json --policy policy.json
33
+ npx harness run --graph graph.json --policy policy.json --state state --sink file:results
34
+ npx harness result --state state --work issued-at
35
+ ```
36
+
37
+ Источник можно заменить локальным зеркалом (`--source <path>` или
38
+ `STARTER_SOURCE`). Копия клонируется с `--no-local`, после clone у неё
39
+ удаляется `origin`, поэтому источник не меняется. Настройка из `.harness/`
40
+ фиксируется отдельным коммитом `fixture: devcontour-harness
41
+ turbo-starter-setup/v1` с фиксированными автором и датой. При одинаковых
42
+ входах получается один и тот же baseline SHA. Манифест копии
43
+ (`<work>/fixture.json`) записывает upstream, baseline и sha256 каждого
44
+ файла настройки.
45
+
46
+ `--warm` один раз прогревает pnpm store и dlx-кэш генератора
47
+ (`@gromlab/rest-api-codegen@5.2.4`) на основном checkout копии. Без
48
+ прогрева `pnpm install --offline` в попытке на холодной машине упадёт с явной
49
+ ошибкой.
50
+
51
+ ### Библиотека подготовки (для W01 и своих сценариев)
52
+
53
+ `examples/turbo-starter/setup.mjs` (только встроенные модули Node, запускается
54
+ обычным `node`, импортируется как ES-модуль):
55
+
56
+ - `prepareStarterFixture({ workDir, source?, commit?, installPnpm?, warm? })`
57
+ возвращает `{ repo, baselineSha, upstream, pathPrepend, env, manifest }`.
58
+ - `starterPolicy(fixture, { template?, targetRef?, scenario?, repoPath?, edit? })`
59
+ возвращает политику примера. `scenario` подключает fixture runtime
60
+ (`FIXTURE_SCENARIO`).
61
+ - `starterGraph({ edit? })` возвращает граф примера, например для
62
+ отображения поручения Relay.
63
+ - `changeFiles(fixture, 'issued-at.patch')` возвращает файлы изменения
64
+ (`путь → содержимое`) для шага fixture writer.
65
+ - `controllerContext(fixture, dir)` создаёт источник контекста
66
+ `controller-mode` с provenance; `writeLayout(fixture, options?)` пишет
67
+ `policy.json`, `graph.json` и контекст рядом с копией.
68
+ - `approve()` и `implement(files)` возвращают шаги сценария управляемых
69
+ (fixture) runtime для повторяемых прогонов без модели.
70
+
71
+ <!-- repo-only -->
72
+
73
+ В checkout harness типы библиотеки — `scripts/acceptance/starter-fixture.d.mts`;
74
+ `scripts/acceptance/starter-fixture.mjs` реэкспортирует её для сценариев
75
+ приёмки, fixture runtime — `tests/fixtures/bin/fake-*`. Это инструменты
76
+ разработки harness, в архив они не входят.
77
+
78
+ <!-- /repo-only -->
79
+
80
+ ## Gates и кэш
81
+
82
+ | Gate | Команда | Что доказывает |
83
+ | ----------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
84
+ | `backend-types` | `pnpm --filter @oim/nest-backend check-types` | Backend компилируется (`prisma generate && tsc --noEmit`) |
85
+ | `sdk-generation` (test) | `node --test --test-reporter=junit … .harness/acceptance/generation.test.mjs` | Закоммиченные OpenAPI и оба SDK совпадают с выходом `pnpm rest-api-sdk:generate` |
86
+ | `sdk-build` | `pnpm --filter @oim/client-rest-api-sdk run build` | Web импортирует SDK из `dist` |
87
+ | `web-types` | `pnpm --filter @oim/next-web-app run check-types` | Потребитель frontend типизирован против SDK |
88
+ | `acceptance` (test) | `node --test --test-reporter=junit … .harness/acceptance/issued-at.test.mjs` | 5 testcases: поле в OpenAPI и SDK обоих realm, потребитель |
89
+
90
+ - JUnit пишет встроенный reporter Node 24, отдельные зависимости не нужны.
91
+ Отчёты лежат в `.artifacts/junit/` (ignored в starter). Harness удаляет
92
+ старый отчёт до запуска и принимает только свежий непустой отчёт на
93
+ проверяемом SHA.
94
+ - Turbo-кэш не участвует. Gates вызывают pnpm-фильтры напрямую, поэтому
95
+ результата из кэша (cache hit) нет. Каждая попытка ставит свои
96
+ `node_modules` в своём worktree. Общими между попытками остаются только
97
+ неизменяемый pnpm store и dlx-кэш. Если gate вызывает `turbo run`,
98
+ передавайте `--force`, иначе cache hit не докажет свежести.
99
+ `pnpm exec turbo` предварительная проверка toolchain сейчас отклоняет:
100
+ `turbo ^2.11.6` в package.json starter не закреплён, хотя lockfile
101
+ закрепляет 2.11.6.
102
+ - Gate `acceptance` поставляется примером, а не upstream:
103
+ `apps/nest-backend/AGENTS.md` запрещает добавлять тесты в backend.
104
+
105
+ ## Генерация и область записи (S02)
106
+
107
+ `generatedPaths` в harness означает «защищено»: путь из этого списка не
108
+ даёт права его менять. Поэтому выходы генерации OpenAPI и SDK туда **не**
109
+ входят. Схема такая:
110
+
111
+ 1. **Явное разрешение поручения.** `writeScope` поручения включает исходники
112
+ изменения и выходы генерации:
113
+ `apps/nest-backend/openapi`, `packages/{client,admin}-rest-api-sdk/src/generated`.
114
+ Без этого изменение генерации отклоняется как выход за область.
115
+ 2. **Защита входов генератора.** `protectedPaths` включает
116
+ `apps/nest-backend/src/export-openapi.ts`, `package.json` обоих SDK, где
117
+ закреплена версия codegen, lockfile, `.harness/` и AGENTS.md. Попытка не
118
+ может подменить генератор или проверку.
119
+ 3. **Доказательство детерминизма.** Gate `sdk-generation` на candidate и на
120
+ integration SHA запускает штатную генерацию и сверяет `git diff` и
121
+ неотслеживаемые файлы по каждому пути выхода. Затем он возвращает
122
+ дерево к проверяемому коммиту. Устаревший или вручную правленый выход
123
+ отклоняется с указанием пути.
124
+ 4. **`generatedPaths` по назначению.** Там остаётся только
125
+ `apps/nest-backend/src/generated`, клиент Prisma. Его никто не правит и не
126
+ коммитит (он ignored).
127
+
128
+ Проверки области и защищённых путей не выключаются. Защищённый путь важнее
129
+ `writeScope`.
130
+
131
+ ## Lifecycle-мост для существующего worktree (S03, S04, S06)
132
+
133
+ Starter-команды `worktree:create`/`worktree:up` требуют собственный реестр
134
+ `~/worktrees/.state` и свой каталог worktree. Harness их не вызывает и реестр
135
+ не подделывает. Worktree создаёт harness. `compose-bridge.mjs` поднимает
136
+ среду для уже существующего дерева через шаги lifecycle политики:
137
+
138
+ ```json
139
+ "lifecycle": {
140
+ "setup": [{ "id": "compose-setup", "command": ["node", ".harness/compose-bridge.mjs", "setup", "--port", "POSTGRES_PORT", "--env", ".harness/env/compose.env.template:compose.env", "--secret", "HX_DB_PASSWORD"] }],
141
+ "ready": [{ "id": "compose-ready", "command": ["node", ".harness/compose-bridge.mjs", "ready", "--timeout-ms", "120000"] }],
142
+ "teardown": [{ "id": "compose-teardown", "command": ["node", ".harness/compose-bridge.mjs", "teardown"] }]
143
+ }
144
+ ```
145
+
146
+ Gates, которым нужна база, запускаются через
147
+ `node .harness/compose-bridge.mjs exec -- <команда>`. Команда получает
148
+ переменные env-файлов среды и `HX_PROJECT`.
149
+
150
+ - **Каталог среды.** Harness передаёт шагам lifecycle, `prepare` и gates
151
+ фазы переменную `DEVCONTOUR_ENVIRONMENT_DIR` (псевдоним
152
+ `HARNESS_ENVIRONMENT_DIR`): `<state>/artifacts/<run>/<phase>/environment`.
153
+ Это каталог вне worktree, рядом с receipt harness `environment.json`.
154
+ Переменная сохраняется в receipt, поэтому повторная очистка в новом
155
+ процессе видит тот же каталог. Состояние моста лежит в `bridge/`:
156
+ `receipt.json`, `compose.json` (модель без подстановки значений),
157
+ `labels.json`, `env/*.env` (0600) и `diagnostics/`.
158
+ - **Уникальность.** Проект называется
159
+ `hx-<runId 12>-<phase>-<repository>`, своё имя на каждую попытку и фазу.
160
+ Порты выделяются свободными на `127.0.0.1` и отмечаются в реестре моста
161
+ (`$TMPDIR/devcontour-compose-bridge/ports`). Это защищает от гонки
162
+ «порт выделен, но ещё не занят». Тома и сеть принадлежат проекту.
163
+ - **Владение.** Каждый сервис, том и сеть получают метки
164
+ `devcontour.harness.run`, `devcontour.harness.phase`,
165
+ `devcontour.harness.repository` и `devcontour.harness.project`. Модель
166
+ проверяется до создания: `container_name`, bind mount, внешние тома и
167
+ сети, `network_mode: host` и `privileged` отклоняются.
168
+ - **Порядок.** setup (намерение в receipt → порты → env → `compose create`)
169
+ → ready (`up -d --wait --wait-timeout`) → `prepare` → gates → ревью →
170
+ teardown. До ready gates не запускаются. При отказе ready сохраняются
171
+ `ps` и `logs` с замаскированными секретами.
172
+ - **Очистка.** Сначала выполняется `compose down --volumes --remove-orphans`
173
+ по снимку модели. Затем то, что осталось, удаляется по меткам, причём
174
+ только при совпадении обеих меток, run и project. `docker prune` не
175
+ вызывается, контекст Docker не меняется, чужие контейнеры и тома не
176
+ трогаются. Env-файлы удаляются при любом исходе teardown. Receipt
177
+ сохраняет, что удалено и что осталось.
178
+ - **Окружение и секреты.** Шаги и gates получают только `inherit/values/secrets`
179
+ политики, переменные harness `DEVCONTOUR_*` и `HARNESS_*` и минимальную
180
+ базу (`PATH`, `HOME`, `TMPDIR`, `USER`, `LOGNAME`, `CI`); test-gates
181
+ дополнительно получают от harness `TURBO_FORCE` и `GOFLAGS=-count=1`
182
+ (свежесть, не секреты). Секрет задаётся
183
+ именем переменной оператора (`"secrets": {"HX_DB_PASSWORD": "HX_DB_PASSWORD_SOURCE"}`).
184
+ Его значение маскируют harness (логи шагов и gates, evidence, итог) и мост
185
+ (диагностика). В receipt попадают только имена переменных env-файлов.
186
+
187
+ ### Порядок принятия и очистки (S04)
188
+
189
+ Harness принимает попытку **после** teardown. Teardown candidate и integration
190
+ выполняются до CAS-продвижения target ref. Отсюда:
191
+
192
+ - сбой setup, ready или gates означает, что попытка не принята, а
193
+ teardown выполняется всё равно;
194
+ - сбой teardown тоже означает отказ (`CleanupFailure`). Target ref не
195
+ двигается, и Git-история не переписывается. Receipt получает статус
196
+ `cleanup-failed`, а в `cleanup-pending.json` перечисляются сохранённые
197
+ worktree. Ресурсы среды остаются, и это видно;
198
+ - если процесс убит при активной среде, receipt остаётся `active`. Новый
199
+ процесс с тем же state вызывает `Harness.cleanup(runId)`
200
+ (`Runner.finishCleanup`). Тот истекает попытку с просроченной арендой,
201
+ повторяет teardown по receipt только при мёртвом владельце и убирает
202
+ worktree. Повторный вызов безопасен и возвращает `nothing-pending`.
203
+ Evidence и логи сохраняются. Пример запуска (поставляется в пакете):
204
+ `node node_modules/devcontour-harness/examples/turbo-starter/cleanup.mjs --state <state> --run <runId>`.
205
+
206
+ Состояния «принято» и «очистка не завершена» поэтому не совпадают по
207
+ времени: принятая попытка уже прошла очистку своих сред. Остаётся
208
+ единственный случай, когда после принятия не удаляется worktree попытки
209
+ (`reason: remove-failed`). Он доводится той же очисткой и не трогает
210
+ принятый ref.
211
+
212
+ ## Controller mode (S05)
213
+
214
+ AGENTS.md starter обязывают агента самому создавать worktree, коммитить и
215
+ сливать изменения и вести историю Relay. Под harness эти операции выполняет
216
+ controller. Оверлей `templates/controller-mode/AGENTS.controller.md` v1
217
+ подключается **источником контекста**, а не файлом в репозитории (starter
218
+ запрещает новые `.md` файлы):
219
+
220
+ - `context/controller-mode/AGENTS.controller.md` содержит неизменённый
221
+ оверлей. Он вкладывается в запросы writer и обоих reviewer;
222
+ - в `context/controller-mode/references/**/AGENTS.md` лежат копии product
223
+ AGENTS.md. Они закреплены sha256, но в запрос не вкладываются: worker
224
+ читает живые файлы своего worktree;
225
+ - `context/controller-mode/provenance.json` содержит версию и sha256
226
+ оверлея, baseline SHA и blob каждого product AGENTS.md;
227
+ - в каждом поручении указан `"context": ["controller-mode"]`.
228
+
229
+ Оверлей перечисляет правила, делегированные controller-у: worktree, commit,
230
+ merge, история Relay, `pnpm run setup`, вложенные оркестраторы и `harness`.
231
+ Остальные правила продукта продолжают действовать (архитектура, запрет
232
+ ручных правок generated, lockfile). В запросе worker есть только запрет
233
+ делать commit. Обязанности коммитить в нём нет. Harness проверяет это
234
+ независимо от текста: commit worker-а или смена HEAD дают
235
+ `scope-violation`, и сценарий это проверяет.
236
+
237
+ ## Известные ограничения upstream
238
+
239
+ - `scripts/check-env.mjs` (`pnpm env:check`, часть корневого `pnpm lint`)
240
+ падает на Node 24.3.0: `fs.globSync` не находит dotfiles. На Node 24.22.1
241
+ проверка проходит. Пример корневой `pnpm lint` не использует и обходится
242
+ явными pnpm-фильтрами. Патч upstream не нужен.
243
+ - `react-admin-panel` `check-types` скачивает `@gromlab/svg-sprites` через
244
+ `npx --yes`, поэтому нужна сеть. В gates примера этот пакет не участвует.
245
+ - Изоляция gates в примере выключена (`isolation.mode: none`). pnpm store,
246
+ dlx-кэш и Docker socket лежат вне worktree. Песочница ОС для них
247
+ потребует отдельного списка путей и доменов.
248
+
249
+ <!-- repo-only -->
250
+
251
+ ## Сценарии приёмки
252
+
253
+ | ID | Скрипт | Что выполняется |
254
+ | ------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
+ | S01, S02, S05 | `scripts/acceptance/starter-s01.mjs` | Позитивный прогон через operator approval (`review-plan`, `approve`, `run`); несовместимый потребитель; область записи, защищённый путь, устаревший и вручную правленый выход; commit worker-а |
256
+ | S03 | `scripts/acceptance/starter-s03.mjs` | Две параллельные попытки с отдельными базами, барьер read-back, контрольный контейнер fixture |
257
+ | S04 | `scripts/acceptance/starter-s04.mjs` | Контрольный прогон, сбой setup после create, таймаут ready, сбой gate, сбой teardown, SIGKILL и очистка новым процессом |
258
+ | S06 | `scripts/acceptance/starter-s06.mjs` | Canary-значения, отказ с выводом секрета, явный повтор, доставка, поиск canary во всех артефактах |
259
+
260
+ Обёртка `tests/integration/starter-acceptance.test.ts` проверяет каждый
261
+ сценарий отдельным тестом. Без Docker или источника upstream тест падает с
262
+ `BLOCKED`, а не пропускается. Быстрые тесты без Docker и pnpm лежат в
263
+ `tests/starter-{setup,bridge,lifecycle}.test.ts`.
264
+
265
+ W01 (оба Relay) — `scripts/acceptance/w01.mjs relay-v1|relay-v2` и
266
+ `tests/integration/w01-relay-{v1,v2}.test.ts`: тот же Starter, публичный CLI
267
+ `source-*`, восстановление доставки и конфликт постановки.
268
+
269
+ <!-- /repo-only -->
package/docs/upgrade.md CHANGED
@@ -7,13 +7,13 @@
7
7
 
8
8
  ## Версии
9
9
 
10
- | Что | Правило |
11
- | --------------- | --------------------------------------------------------------------------- |
12
- | Пакет | `0.1.0-alpha.N`; выпущенная версия неизменяема (тот же номер — те же байты) |
13
- | Публичный API | `devcontour-harness/v1alpha1` |
14
- | Политика | `policyVersion: 1` |
15
- | Состояние | `stateVersion: 1` |
16
- | Отчёты и корпус | `version: 1` (отчёт свойств, договор проверки, корпус, receipt среды) |
10
+ | Что | Правило |
11
+ | --------------- | --------------------------------------------------------------------------- || --------------------
12
+ | Пакет | `0.1.0-alpha.N`; выпущенная версия неизменяема (тот же номер — те же байты) |
13
+ | Публичный API | `devcontour-harness/v1alpha1` |
14
+ | Политика | `policyVersion: 1` |
15
+ | Состояние | `stateVersion: 1` |
16
+ | Отчёты и корпус | `version: 1` (отчёт свойств, договор проверки, корпус, receipt среды) |
17
17
 
18
18
  Для `0.x`: patch — совместимые исправления; minor — отдельно описанные
19
19
  изменения договора, включая несовместимые. Неизвестная версия входа, политики
@@ -21,19 +21,22 @@
21
21
 
22
22
  ## Матрица форматов
23
23
 
24
- | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Пишет поверх | Примечание |
25
- | ---------------------------------------------- | ------------------------ | ------------------------ | ------------ | ----------------------------------------------------------------------------------- |
26
- | standalone `stateVersion: 1` версией alpha.1 | да | да | да | одна база SQLite: документ, события, локальные записи |
27
- | хост, центральная база | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
- | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
- | alpha.2 после штатной остановки controller | да | да | да | роль controller снята — нового поля в состоянии нет |
30
- | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
- | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
- | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
33
-
34
- Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion`. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
24
+ | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Читает 0.1.0-alpha.7 | Пишет поверх | Примечание |
25
+ | ------------------------------------------------------------------------------ | ------------------------ | ------------------------ | ----------------------- | -------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
26
+ | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
+ | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
+ | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
+ | alpha.2 после штатной остановки controller | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
+ | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
+ | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
+ | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
+ | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
+ | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
+ | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
36
+
37
+ Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
35
38
  формата до записи проверяют тесты совместимости пакета
36
- (`tests/compatibility.test.ts`).
39
+ <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
37
40
 
38
41
  Новые версии добавляют только необязательные поля и новые пространства
39
42
  локальных записей. Схемы состояния строгие: прежняя версия отклоняет
@@ -99,6 +102,56 @@ alpha.4 (новых полей нет).
99
102
  `0.1.0-alpha.5` и обновление lock; содержимое alpha.4 под своим номером не
100
103
  меняется.
101
104
 
105
+ ### alpha.6: протокол ревью, доставка `uncertain`, toolchains
106
+
107
+ Формат `stateVersion: 1` прежний; добавлены только необязательные поля, но
108
+ строгие схемы прежних версий их не знают:
109
+
110
+ - записи ревью кода несут `reviewRound` и `findings[].id/status` (протокол
111
+ ревью, [review-protocol](review-protocol.md)); такая запись появляется после
112
+ первого ревью кода новой версией;
113
+ - запись доставки может иметь статус `uncertain` и поле `operation`; в журнале —
114
+ событие `delivery.uncertain`.
115
+
116
+ Состояние alpha.5 alpha.6 читает и продолжает без преобразования: прежние
117
+ записи ревью — legacy-раунды, прежние доставки и receipts не переписываются.
118
+ Standalone-состояние, в котором есть новые поля, alpha.5 и alpha.4 не читают:
119
+ отказ `STATE_VERSION_UNSUPPORTED` до любой записи. Прежний хост DevContour
120
+ (0.4.0-alpha.7 с адаптером alpha.4) такого отказа **не даёт**: состояние alpha.6
121
+ — и без записей ревью нового протокола, и с ними (`reviewRound`,
122
+ `findings[].id/status`) — он открывает и может записать в него роль controller,
123
+ события и документ, а при готовых поручениях — выдать по ним работу; новые поля при этом сохраняются, но гарантии строгой схемы
124
+ нет. Пока действует роль controller alpha.6, прежний writer её не перехватывает
125
+ и ничего не пишет. Защиты от запуска прежнего DevContour на состоянии alpha.6
126
+ нет, поэтому откат с alpha.6 — только восстановлением копии состояния,
127
+ сделанной до обновления; прежнюю версию на текущее состояние не запускать.
128
+
129
+ Правила `engineVersion` (alpha.2–alpha.5) действуют и между alpha.5 и alpha.6:
130
+ живая роль и активные попытки alpha.5 не продолжаются до истечения срока и
131
+ recovery.
132
+
133
+ Политики: новые необязательные поля `repositories[].toolchain` и
134
+ `reviewProtocolRetries`. Политика без них сохраняет прежние `policyDigest`,
135
+ `executionDigest` и закрепления; политика с ними получает новое основание
136
+ (новое согласование). Встроенные профили alpha.5 побайтно прежние; новые
137
+ профили — новые id (`npm-ci`, `pnpm-frozen`, `go-gotestsum`, `python-uv`).
138
+
139
+ ### alpha.7: учёт выполненных команд reviewer
140
+
141
+ Формат `stateVersion: 1` прежний. `inspection` в `review.json` и evidence ревью кода получает
142
+ новые значения и необязательные поля ([review-probes](review-probes.md#итог-inspection-что-reviewer-действительно-выполнил)):
143
+ `source` — также `controller-probes` и `runtime-and-controller`; `commands[].exitCode` может
144
+ быть `null`; новые `commands[].origins/status/failure/gate/note`, `context`, `total`,
145
+ `excluded`, `excludedTotal`, `invalid`. Строки исхода в `probes.jsonl` несут `started` и
146
+ `scope`; `review.json.probes` — `exitCode`, `started`, `scope`, `note`.
147
+
148
+ Направление совместимости одностороннее: alpha.7 читает записи alpha.6 и ранее без
149
+ пересчёта; строгая схема alpha.6 не принимает новый `source`, `exitCode: null` и новые поля,
150
+ поэтому состояние с такими записями alpha.6 не читает. Потребителю, разбирающему
151
+ `inspection`, нужно принять новые значения `source`, nullable `exitCode` и необязательные
152
+ поля; признак выполнения — `mode`, а не наличие кода 0. Откат — как для alpha.6: только
153
+ восстановлением копии состояния, сделанной до обновления.
154
+
102
155
  ## Перед обновлением
103
156
 
104
157
  1. **Остановить записывающих.** Запретить новые выдачи (`harness pause`),
@@ -140,11 +193,11 @@ alpha.4 (новых полей нет).
140
193
 
141
194
  ## Откат
142
195
 
143
- | Ситуация | Действие |
144
- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
- | Изменения исходников до выкатки | Вернуться к проверенной паре коммитов/версий в отдельном checkout или revert-коммитами; страховочную ветку не переписывать |
146
- | Только пакет, формат данных совместим | Остановить writers → вернуть прежнюю версию (архив или точную версию npm), зависимость и lock → `harness doctor` → возобновить |
147
- | Новая версия уже изменила формат | Остановить writers → восстановить согласованную копию вместе с совместимой версией пакета → сверить Git refs и артефакты (ниже); простого понижения версии мало |
196
+ | Ситуация | Действие |
197
+ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- || --------------------
198
+ | Изменения исходников до выкатки | Вернуться к проверенной паре коммитов/версий в отдельном checkout или revert-коммитами; страховочную ветку не переписывать |
199
+ | Только пакет, формат данных совместим | Остановить writers → вернуть прежнюю версию (архив или точную версию npm), зависимость и lock → `harness doctor` → возобновить |
200
+ | Новая версия уже изменила формат | Остановить writers → восстановить согласованную копию вместе с совместимой версией пакета → сверить Git refs и артефакты (ниже); простого понижения версии мало |
148
201
 
149
202
  ### Сверка Git после восстановления базы
150
203
 
@@ -19,6 +19,7 @@ npx harness validate --graph files-only/graph.json --policy files-only/policy.js
19
19
  | `one-vendor/` | writer и reviewer одного вендора — только с явным `same-vendor-allowed` |
20
20
  | `files-only/` | поручение, контекст и результат только файлами, без внешних сервисов |
21
21
  | `external-connector/` | источник поручений и получатель результата через порты библиотеки |
22
+ | `turbo-starter/` | pnpm/Turborepo-проект с генерацией SDK и Docker lifecycle (см. README) |
22
23
 
23
24
  Reviewer задаётся правилом ревью явно; неявного «противоположного runtime»
24
25
  нет. При `different-vendor` (умолчание) reviewer того же вендора, что автор,