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.
Files changed (390) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/docs/architecture.md +473 -0
  4. package/docs/compatibility-versions.md +56 -0
  5. package/docs/compatibility.md +192 -0
  6. package/docs/connectors.md +323 -0
  7. package/docs/contracts.md +603 -0
  8. package/docs/integrations/hindsight.md +148 -0
  9. package/docs/quickstart.md +182 -0
  10. package/docs/release-notes/0.1.0-alpha.6.md +238 -0
  11. package/docs/review-probes.md +120 -0
  12. package/docs/review-protocol.md +251 -0
  13. package/docs/toolchains.md +167 -0
  14. package/docs/turbo-starter.md +269 -0
  15. package/docs/upgrade.md +202 -0
  16. package/examples/README.md +27 -0
  17. package/examples/external-connector/README.md +19 -0
  18. package/examples/external-connector/connector.mjs +55 -0
  19. package/examples/files-only/README.md +28 -0
  20. package/examples/files-only/context/norms/rules.md +4 -0
  21. package/examples/files-only/graph.json +23 -0
  22. package/examples/files-only/policy.json +30 -0
  23. package/examples/one-vendor/README.md +19 -0
  24. package/examples/one-vendor/policy.json +27 -0
  25. package/examples/single-task/README.md +18 -0
  26. package/examples/single-task/graph.json +27 -0
  27. package/examples/single-task/policy-same-vendor.json +55 -0
  28. package/examples/single-task/policy.json +52 -0
  29. package/examples/single-task/src/.gitkeep +0 -0
  30. package/examples/single-task/test/run.js +19 -0
  31. package/examples/toolchains/README.md +32 -0
  32. package/examples/toolchains/go/bad/label/label.go +9 -0
  33. package/examples/toolchains/go/good/label/label.go +9 -0
  34. package/examples/toolchains/go/policy.json +79 -0
  35. package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
  36. package/examples/toolchains/go/repo/_gitignore +1 -0
  37. package/examples/toolchains/go/repo/go.mod +5 -0
  38. package/examples/toolchains/go/repo/go.sum +2 -0
  39. package/examples/toolchains/go/repo/label/label.go +9 -0
  40. package/examples/toolchains/go/repo/label/label_test.go +27 -0
  41. package/examples/toolchains/go/repo/label/version.go +9 -0
  42. package/examples/toolchains/go/task.json +25 -0
  43. package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
  44. package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
  45. package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
  46. package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
  47. package/examples/toolchains/mixed-go/policy.json +126 -0
  48. package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
  49. package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
  50. package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
  51. package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
  52. package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
  53. package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
  54. package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
  55. package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
  56. package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
  57. package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
  58. package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
  59. package/examples/toolchains/mixed-go/task.json +25 -0
  60. package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
  61. package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
  62. package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
  63. package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
  64. package/examples/toolchains/mixed-python/policy.json +121 -0
  65. package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
  66. package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
  67. package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
  68. package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
  69. package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
  70. package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
  71. package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
  72. package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
  73. package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
  74. package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
  75. package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
  76. package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
  77. package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
  78. package/examples/toolchains/mixed-python/task.json +25 -0
  79. package/examples/toolchains/npm/bad/src/label.js +4 -0
  80. package/examples/toolchains/npm/good/src/label.js +4 -0
  81. package/examples/toolchains/npm/policy.json +82 -0
  82. package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
  83. package/examples/toolchains/npm/repo/_gitignore +4 -0
  84. package/examples/toolchains/npm/repo/package-lock.json +22 -0
  85. package/examples/toolchains/npm/repo/package.json +13 -0
  86. package/examples/toolchains/npm/repo/src/label.js +4 -0
  87. package/examples/toolchains/npm/repo/test/label.test.js +13 -0
  88. package/examples/toolchains/npm/task.json +25 -0
  89. package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
  90. package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
  91. package/examples/toolchains/pnpm/policy.json +85 -0
  92. package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
  93. package/examples/toolchains/pnpm/repo/_gitignore +4 -0
  94. package/examples/toolchains/pnpm/repo/package.json +10 -0
  95. package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
  96. package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
  97. package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
  98. package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
  99. package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
  100. package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
  101. package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
  102. package/examples/toolchains/pnpm/task.json +25 -0
  103. package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
  104. package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
  105. package/examples/toolchains/python/policy.json +80 -0
  106. package/examples/toolchains/python/repo/.python-version +1 -0
  107. package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
  108. package/examples/toolchains/python/repo/_gitignore +5 -0
  109. package/examples/toolchains/python/repo/pyproject.toml +19 -0
  110. package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
  111. package/examples/toolchains/python/repo/tests/test_label.py +13 -0
  112. package/examples/toolchains/python/repo/uv.lock +108 -0
  113. package/examples/toolchains/python/task.json +25 -0
  114. package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
  115. package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
  116. package/examples/toolchains/turbo/policy.json +110 -0
  117. package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
  118. package/examples/toolchains/turbo/repo/_gitignore +4 -0
  119. package/examples/toolchains/turbo/repo/package.json +13 -0
  120. package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
  121. package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
  122. package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
  123. package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
  124. package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
  125. package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
  126. package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
  127. package/examples/toolchains/turbo/repo/turbo.json +7 -0
  128. package/examples/toolchains/turbo/task.json +25 -0
  129. package/examples/turbo-starter/README.md +16 -0
  130. package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
  131. package/examples/turbo-starter/changes/issued-at.patch +130 -0
  132. package/examples/turbo-starter/cleanup.mjs +58 -0
  133. package/examples/turbo-starter/graph.json +32 -0
  134. package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
  135. package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
  136. package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
  137. package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
  138. package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
  139. package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
  140. package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
  141. package/examples/turbo-starter/harness/env/backend.env.template +7 -0
  142. package/examples/turbo-starter/harness/env/compose.env.template +6 -0
  143. package/examples/turbo-starter/policy.json +109 -0
  144. package/examples/turbo-starter/policy.lifecycle.json +114 -0
  145. package/examples/turbo-starter/setup.mjs +352 -0
  146. package/examples/two-vendors/policy.json +30 -0
  147. package/lib/agents/cli-adapter.d.ts +48 -0
  148. package/lib/agents/cli-adapter.js +386 -0
  149. package/lib/agents/inspect.d.ts +6 -0
  150. package/lib/agents/inspect.js +46 -0
  151. package/lib/agents/probe-server.d.ts +2 -0
  152. package/lib/agents/probe-server.js +13 -0
  153. package/lib/agents/review-probe.d.ts +295 -0
  154. package/lib/agents/review-probe.js +872 -0
  155. package/lib/agents/runtime-events.d.ts +11 -0
  156. package/lib/agents/runtime-events.js +21 -0
  157. package/lib/agents/schemas.d.ts +217 -0
  158. package/lib/agents/schemas.js +124 -0
  159. package/lib/agents/usage.d.ts +8 -0
  160. package/lib/agents/usage.js +83 -0
  161. package/lib/cli.d.ts +2 -0
  162. package/lib/cli.js +491 -0
  163. package/lib/connectors/files.d.ts +108 -0
  164. package/lib/connectors/files.js +133 -0
  165. package/lib/contracts/checks.d.ts +307 -0
  166. package/lib/contracts/checks.js +181 -0
  167. package/lib/contracts/cli.d.ts +3895 -0
  168. package/lib/contracts/cli.js +664 -0
  169. package/lib/contracts/context.d.ts +135 -0
  170. package/lib/contracts/context.js +129 -0
  171. package/lib/contracts/digest.d.ts +9 -0
  172. package/lib/contracts/digest.js +22 -0
  173. package/lib/contracts/errors.d.ts +81 -0
  174. package/lib/contracts/errors.js +70 -0
  175. package/lib/contracts/events.d.ts +827 -0
  176. package/lib/contracts/events.js +258 -0
  177. package/lib/contracts/execution.d.ts +3216 -0
  178. package/lib/contracts/execution.js +872 -0
  179. package/lib/contracts/failure.d.ts +67 -0
  180. package/lib/contracts/failure.js +127 -0
  181. package/lib/contracts/index.d.ts +18 -0
  182. package/lib/contracts/index.js +18 -0
  183. package/lib/contracts/policy.d.ts +1080 -0
  184. package/lib/contracts/policy.js +544 -0
  185. package/lib/contracts/ports.d.ts +417 -0
  186. package/lib/contracts/ports.js +1 -0
  187. package/lib/contracts/primitives.d.ts +63 -0
  188. package/lib/contracts/primitives.js +73 -0
  189. package/lib/contracts/profiles.d.ts +336 -0
  190. package/lib/contracts/profiles.js +109 -0
  191. package/lib/contracts/published.d.ts +3237 -0
  192. package/lib/contracts/published.js +34 -0
  193. package/lib/contracts/result.d.ts +1280 -0
  194. package/lib/contracts/result.js +311 -0
  195. package/lib/contracts/review.d.ts +441 -0
  196. package/lib/contracts/review.js +477 -0
  197. package/lib/contracts/toolchain.d.ts +96 -0
  198. package/lib/contracts/toolchain.js +89 -0
  199. package/lib/contracts/version.d.ts +17 -0
  200. package/lib/contracts/version.js +17 -0
  201. package/lib/contracts/work.d.ts +173 -0
  202. package/lib/contracts/work.js +178 -0
  203. package/lib/control/base-move.d.ts +44 -0
  204. package/lib/control/base-move.js +90 -0
  205. package/lib/control/doctor.d.ts +50 -0
  206. package/lib/control/doctor.js +113 -0
  207. package/lib/control/harness.d.ts +1125 -0
  208. package/lib/control/harness.js +741 -0
  209. package/lib/control/provenance.d.ts +17 -0
  210. package/lib/control/provenance.js +25 -0
  211. package/lib/control/recovery.d.ts +237 -0
  212. package/lib/control/recovery.js +236 -0
  213. package/lib/control/review.d.ts +62 -0
  214. package/lib/control/review.js +178 -0
  215. package/lib/control/workflow.d.ts +247 -0
  216. package/lib/control/workflow.js +259 -0
  217. package/lib/engine/budget.d.ts +345 -0
  218. package/lib/engine/budget.js +278 -0
  219. package/lib/engine/classify.d.ts +8 -0
  220. package/lib/engine/classify.js +32 -0
  221. package/lib/engine/engine.d.ts +1527 -0
  222. package/lib/engine/engine.js +2259 -0
  223. package/lib/engine/failure.d.ts +1 -0
  224. package/lib/engine/failure.js +3 -0
  225. package/lib/engine/joint-runner.d.ts +257 -0
  226. package/lib/engine/joint-runner.js +254 -0
  227. package/lib/engine/measured.d.ts +15 -0
  228. package/lib/engine/measured.js +82 -0
  229. package/lib/engine/prompt.d.ts +66 -0
  230. package/lib/engine/prompt.js +131 -0
  231. package/lib/engine/property-recovery.d.ts +50 -0
  232. package/lib/engine/property-recovery.js +171 -0
  233. package/lib/engine/repair.d.ts +33 -0
  234. package/lib/engine/repair.js +82 -0
  235. package/lib/engine/result.d.ts +9 -0
  236. package/lib/engine/result.js +218 -0
  237. package/lib/engine/reuse.d.ts +80 -0
  238. package/lib/engine/reuse.js +102 -0
  239. package/lib/engine/runner.d.ts +360 -0
  240. package/lib/engine/runner.js +1836 -0
  241. package/lib/engine/standalone.d.ts +6 -0
  242. package/lib/engine/standalone.js +48 -0
  243. package/lib/engine/usage.d.ts +87 -0
  244. package/lib/engine/usage.js +165 -0
  245. package/lib/environment/isolation.d.ts +110 -0
  246. package/lib/environment/isolation.js +238 -0
  247. package/lib/environment/lifecycle.d.ts +40 -0
  248. package/lib/environment/lifecycle.js +197 -0
  249. package/lib/environment/live-calls.d.ts +14 -0
  250. package/lib/environment/live-calls.js +73 -0
  251. package/lib/environment/os-tools.d.ts +16 -0
  252. package/lib/environment/os-tools.js +35 -0
  253. package/lib/environment/process.d.ts +32 -0
  254. package/lib/environment/process.js +369 -0
  255. package/lib/environment/redaction.d.ts +22 -0
  256. package/lib/environment/redaction.js +102 -0
  257. package/lib/environment/resources.d.ts +31 -0
  258. package/lib/environment/resources.js +120 -0
  259. package/lib/index.d.ts +65 -0
  260. package/lib/index.js +67 -0
  261. package/lib/integrations/connector/commands.d.ts +358 -0
  262. package/lib/integrations/connector/commands.js +428 -0
  263. package/lib/integrations/connector/config.d.ts +215 -0
  264. package/lib/integrations/connector/config.js +214 -0
  265. package/lib/integrations/connector/delivery.d.ts +107 -0
  266. package/lib/integrations/connector/delivery.js +591 -0
  267. package/lib/integrations/connector/fake.d.ts +291 -0
  268. package/lib/integrations/connector/fake.js +786 -0
  269. package/lib/integrations/connector/history.d.ts +124 -0
  270. package/lib/integrations/connector/history.js +497 -0
  271. package/lib/integrations/connector/index.d.ts +16 -0
  272. package/lib/integrations/connector/index.js +16 -0
  273. package/lib/integrations/connector/redact.d.ts +19 -0
  274. package/lib/integrations/connector/redact.js +91 -0
  275. package/lib/integrations/connector/registry.d.ts +15 -0
  276. package/lib/integrations/connector/registry.js +43 -0
  277. package/lib/integrations/connector/snapshot.d.ts +131 -0
  278. package/lib/integrations/connector/snapshot.js +422 -0
  279. package/lib/integrations/connector/source.d.ts +37 -0
  280. package/lib/integrations/connector/source.js +73 -0
  281. package/lib/integrations/connector/steps.d.ts +71 -0
  282. package/lib/integrations/connector/steps.js +188 -0
  283. package/lib/integrations/connector/store.d.ts +140 -0
  284. package/lib/integrations/connector/store.js +277 -0
  285. package/lib/integrations/connector/summary.d.ts +38 -0
  286. package/lib/integrations/connector/summary.js +59 -0
  287. package/lib/integrations/connector/testing-worker.d.ts +1 -0
  288. package/lib/integrations/connector/testing-worker.js +81 -0
  289. package/lib/integrations/connector/testing.d.ts +205 -0
  290. package/lib/integrations/connector/testing.js +1615 -0
  291. package/lib/integrations/connector/transport.d.ts +240 -0
  292. package/lib/integrations/connector/transport.js +28 -0
  293. package/lib/integrations/memory/context.d.ts +120 -0
  294. package/lib/integrations/memory/context.js +239 -0
  295. package/lib/integrations/memory/index.d.ts +8 -0
  296. package/lib/integrations/memory/index.js +8 -0
  297. package/lib/integrations/memory/provider.d.ts +79 -0
  298. package/lib/integrations/memory/provider.js +22 -0
  299. package/lib/integrations/memory/sink.d.ts +56 -0
  300. package/lib/integrations/memory/sink.js +116 -0
  301. package/lib/integrations/memory/testing.d.ts +44 -0
  302. package/lib/integrations/memory/testing.js +61 -0
  303. package/lib/integrations/relay-v1/index.d.ts +122 -0
  304. package/lib/integrations/relay-v1/index.js +860 -0
  305. package/lib/integrations/relay-v2/index.d.ts +145 -0
  306. package/lib/integrations/relay-v2/index.js +752 -0
  307. package/lib/integrations/relay-v2/process.d.ts +32 -0
  308. package/lib/integrations/relay-v2/process.js +112 -0
  309. package/lib/integrations/relay-v2/protocol.d.ts +172 -0
  310. package/lib/integrations/relay-v2/protocol.js +142 -0
  311. package/lib/package.d.ts +2 -0
  312. package/lib/package.js +3 -0
  313. package/lib/profiles/resolve.d.ts +190 -0
  314. package/lib/profiles/resolve.js +273 -0
  315. package/lib/storage/document.d.ts +1234 -0
  316. package/lib/storage/document.js +80 -0
  317. package/lib/storage/sqlite.d.ts +35 -0
  318. package/lib/storage/sqlite.js +260 -0
  319. package/lib/testing/index.d.ts +5 -0
  320. package/lib/testing/index.js +5 -0
  321. package/lib/testing/store-contract.d.ts +8 -0
  322. package/lib/testing/store-contract.js +167 -0
  323. package/lib/toolchains/index.d.ts +9 -0
  324. package/lib/toolchains/index.js +9 -0
  325. package/lib/toolchains/inspect.d.ts +105 -0
  326. package/lib/toolchains/inspect.js +831 -0
  327. package/lib/toolchains/manifests.d.ts +67 -0
  328. package/lib/toolchains/manifests.js +238 -0
  329. package/lib/toolchains/probe.d.ts +28 -0
  330. package/lib/toolchains/probe.js +54 -0
  331. package/lib/toolchains/recipes.d.ts +33 -0
  332. package/lib/toolchains/recipes.js +51 -0
  333. package/lib/validate.d.ts +36 -0
  334. package/lib/validate.js +175 -0
  335. package/lib/verification/acceptance.d.ts +284 -0
  336. package/lib/verification/acceptance.js +670 -0
  337. package/lib/verification/checks.d.ts +72 -0
  338. package/lib/verification/checks.js +190 -0
  339. package/lib/verification/confirm.d.ts +125 -0
  340. package/lib/verification/confirm.js +411 -0
  341. package/lib/verification/corpus.d.ts +1303 -0
  342. package/lib/verification/corpus.js +744 -0
  343. package/lib/verification/dependencies.d.ts +66 -0
  344. package/lib/verification/dependencies.js +144 -0
  345. package/lib/verification/gates.d.ts +160 -0
  346. package/lib/verification/gates.js +600 -0
  347. package/lib/verification/joint.d.ts +63 -0
  348. package/lib/verification/joint.js +121 -0
  349. package/lib/verification/proof.d.ts +54 -0
  350. package/lib/verification/proof.js +96 -0
  351. package/lib/verification/property.d.ts +86 -0
  352. package/lib/verification/property.js +214 -0
  353. package/lib/verification/requirements.d.ts +25 -0
  354. package/lib/verification/requirements.js +69 -0
  355. package/lib/verification/self-check.d.ts +29 -0
  356. package/lib/verification/self-check.js +99 -0
  357. package/lib/verification/spike.d.ts +79 -0
  358. package/lib/verification/spike.js +124 -0
  359. package/package.json +100 -4
  360. package/profiles/expo-mobile.json +15 -0
  361. package/profiles/go-api.json +15 -0
  362. package/profiles/go-gotestsum.json +21 -0
  363. package/profiles/mobile-maestro.json +29 -0
  364. package/profiles/nest-api.json +26 -0
  365. package/profiles/next-product.json +72 -0
  366. package/profiles/node-checks.json +77 -0
  367. package/profiles/npm-ci.json +22 -0
  368. package/profiles/pnpm-frozen.json +24 -0
  369. package/profiles/python-api.json +32 -0
  370. package/profiles/python-uv.json +26 -0
  371. package/profiles/react-native.json +50 -0
  372. package/profiles/react-vite-admin.json +71 -0
  373. package/schemas/context-snapshot.schema.json +231 -0
  374. package/schemas/contract-document.schema.json +385 -0
  375. package/schemas/execution-document.schema.json +3695 -0
  376. package/schemas/execution-policy.schema.json +2196 -0
  377. package/schemas/joint-verification.schema.json +617 -0
  378. package/schemas/result-envelope.schema.json +1652 -0
  379. package/schemas/review-record.schema.json +177 -0
  380. package/schemas/run-event.schema.json +2458 -0
  381. package/schemas/run-result.schema.json +1570 -0
  382. package/schemas/stack-profile.schema.json +535 -0
  383. package/schemas/work-graph.schema.json +381 -0
  384. package/schemas/work-order.schema.json +256 -0
  385. package/skills/harness/SKILL.md +64 -0
  386. package/templates/AGENTS.md +13 -0
  387. package/templates/controller-mode/AGENTS.controller.md +58 -0
  388. package/templates/controller-mode/README.md +44 -0
  389. package/templates/graph.json +22 -0
  390. package/templates/policy.json +28 -0
@@ -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 -->
@@ -0,0 +1,202 @@
1
+ # Обновление и откат
2
+
3
+ Документ описывает, какие форматы поддерживает пакет, как обновить установку
4
+ и как откатиться. Процедуры одинаковы для standalone-состояния harness и для
5
+ хоста, который держит состояние в своём хранилище (например, DevContour с
6
+ центральной базой или базами компонентов).
7
+
8
+ ## Версии
9
+
10
+ | Что | Правило |
11
+ | --------------- | --------------------------------------------------------------------------- |
12
+ | Пакет | `0.1.0-alpha.N`; выпущенная версия неизменяема (тот же номер — те же байты) |
13
+ | Публичный API | `devcontour-harness/v1alpha1` |
14
+ | Политика | `policyVersion: 1` |
15
+ | Состояние | `stateVersion: 1` |
16
+ | Отчёты и корпус | `version: 1` (отчёт свойств, договор проверки, корпус, receipt среды) |
17
+
18
+ Для `0.x`: patch — совместимые исправления; minor — отдельно описанные
19
+ изменения договора, включая несовместимые. Неизвестная версия входа, политики
20
+ или состояния отклоняется до любой записи и до выдачи попытки.
21
+
22
+ ## Матрица форматов
23
+
24
+ | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Пишет поверх | Примечание |
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
+ | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
35
+
36
+ Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи). Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
37
+ формата до записи проверяют тесты совместимости пакета
38
+ <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
39
+
40
+ Новые версии добавляют только необязательные поля и новые пространства
41
+ локальных записей. Схемы состояния строгие: прежняя версия отклоняет
42
+ незнакомое поле до любой записи, а не сохраняет его молча. Поэтому откат
43
+ пакета «формат совместим» возможен только для состояния без новых полей,
44
+ иначе — восстановление копии (таблица «Откат» ниже).
45
+
46
+ ### alpha.2: версия движка в роли controller
47
+
48
+ Роль controller записывает версию движка (`engineVersion`). Процесс другой
49
+ версии — и процесс прежней версии, записавший роль без версии, — живую роль
50
+ не перенимает, даже под тем же именем владельца: `harness run` получает
51
+ `CONTROLLER_BUSY` с обеими версиями, выдачи нет. После истечения срока роль
52
+ переходит штатно, а активные попытки прежней версии проходят recovery без
53
+ повторного использования кандидата. Штатная остановка снимает роль целиком.
54
+
55
+ ### alpha.3: версии движка в `doctor`
56
+
57
+ Формат состояния тот же, что у alpha.2 (столбец alpha.2 матрицы относится и
58
+ к alpha.3). `harness doctor --state D` читает состояние строгой схемой этой
59
+ версии и для совместимого состояния называет версии движка, записавшие его
60
+ (`state.engineVersions`: роль controller и попытки); состояние, которое эта
61
+ версия не читает, отмечается несовместимым без записи.
62
+
63
+ ### alpha.4: открытие без записи и владение попыткой по версии
64
+
65
+ - Существующее состояние сначала проверяется целиком соединением только для
66
+ чтения: версия формата и документ строгой схемой. Несовместимое состояние
67
+ отклоняется до создания таблиц, записи автора (`meta.writer`) и смены
68
+ journal mode — файл остаётся байт в байт прежним. `harness doctor --state`
69
+ открывает состояние только для чтения и автором не становится.
70
+ - Операции активной попытки (продление, фаза, evidence, принятие, отказ) и
71
+ совместной проверки выполняет только движок той версии, что их начал.
72
+ Другая версия — и попытка без версии, записанная до её появления, — получает
73
+ `LEASE_STALE` даже с верным token до истечения срока; продолжение — только
74
+ через истечение и recovery с новым владением.
75
+ - Совместная проверка записывает `engineVersion`; состояние, где она есть,
76
+ прежние версии не читают (строка матрицы выше), поэтому откат с alpha.4 на
77
+ более раннюю версию после первой совместной проверки — только через копию.
78
+
79
+ ### alpha.5: публикация в npm
80
+
81
+ Код движка, форматы состояния, политики, отчётов и публичный API те же, что
82
+ у alpha.4; меняются поставка (npm, тег `alpha`) и номер версии. Состояние
83
+ alpha.4 alpha.5 читает и пишет без преобразования, состояние alpha.5 читает
84
+ alpha.4 (новых полей нет).
85
+
86
+ Номер версии — это `engineVersion`, поэтому правила alpha.2–alpha.4
87
+ действуют и между alpha.4 и alpha.5:
88
+
89
+ - живая роль controller alpha.4 не переходит к alpha.5 (`CONTROLLER_BUSY`),
90
+ пока не будет снята штатной остановкой или не истечёт;
91
+ - активную попытку и совместную проверку, начатые alpha.4, alpha.5 не
92
+ продолжает даже с верным token (`LEASE_STALE`): после истечения срока
93
+ recovery передаёт их новой версии без повторного использования кандидата
94
+ прежней версии;
95
+ - в обратную сторону то же: после отката alpha.4 не продолжает попытки
96
+ alpha.5.
97
+
98
+ Поэтому обновление — по общей процедуре ниже: остановить writers, сделать
99
+ копию, проверить её новой версией. У хоста, который поставлял alpha.4
100
+ архивом, переход на registry — смена зависимости на точную версию
101
+ `0.1.0-alpha.5` и обновление lock; содержимое alpha.4 под своим номером не
102
+ меняется.
103
+
104
+ ### alpha.6: протокол ревью, доставка `uncertain`, toolchains
105
+
106
+ Формат `stateVersion: 1` прежний; добавлены только необязательные поля, но
107
+ строгие схемы прежних версий их не знают:
108
+
109
+ - записи ревью кода несут `reviewRound` и `findings[].id/status` (протокол
110
+ ревью, [review-protocol](review-protocol.md)); такая запись появляется после
111
+ первого ревью кода новой версией;
112
+ - запись доставки может иметь статус `uncertain` и поле `operation`; в журнале —
113
+ событие `delivery.uncertain`.
114
+
115
+ Состояние alpha.5 alpha.6 читает и продолжает без преобразования: прежние
116
+ записи ревью — legacy-раунды, прежние доставки и receipts не переписываются.
117
+ Standalone-состояние, в котором есть новые поля, alpha.5 и alpha.4 не читают:
118
+ отказ `STATE_VERSION_UNSUPPORTED` до любой записи. Прежний хост DevContour
119
+ (0.4.0-alpha.7 с адаптером alpha.4) такого отказа **не даёт**: состояние alpha.6
120
+ — и без записей ревью нового протокола, и с ними (`reviewRound`,
121
+ `findings[].id/status`) — он открывает и может записать в него роль controller,
122
+ события и документ, а при готовых поручениях — выдать по ним работу; новые поля при этом сохраняются, но гарантии строгой схемы
123
+ нет. Пока действует роль controller alpha.6, прежний writer её не перехватывает
124
+ и ничего не пишет. Защиты от запуска прежнего DevContour на состоянии alpha.6
125
+ нет, поэтому откат с alpha.6 — только восстановлением копии состояния,
126
+ сделанной до обновления; прежнюю версию на текущее состояние не запускать.
127
+
128
+ Правила `engineVersion` (alpha.2–alpha.5) действуют и между alpha.5 и alpha.6:
129
+ живая роль и активные попытки alpha.5 не продолжаются до истечения срока и
130
+ recovery.
131
+
132
+ Политики: новые необязательные поля `repositories[].toolchain` и
133
+ `reviewProtocolRetries`. Политика без них сохраняет прежние `policyDigest`,
134
+ `executionDigest` и закрепления; политика с ними получает новое основание
135
+ (новое согласование). Встроенные профили alpha.5 побайтно прежние; новые
136
+ профили — новые id (`npm-ci`, `pnpm-frozen`, `go-gotestsum`, `python-uv`).
137
+
138
+ ## Перед обновлением
139
+
140
+ 1. **Остановить записывающих.** Запретить новые выдачи (`harness pause`),
141
+ дождаться завершения или явно остановить активные попытки, остановить
142
+ controller, сервер и всех остальных, кто пишет в состояние. Одной паузы
143
+ очереди недостаточно.
144
+ 2. **Зафиксировать.** Версии пакета и потребителя, конфигурацию, вершины
145
+ target refs и result SHA принятых поручений (`git rev-parse` каждого
146
+ target ref, `harness result` каждого поручения).
147
+ 3. **Сделать копию.**
148
+ - standalone — каталог `--state` целиком, пока controller остановлен;
149
+ - центральная база хоста — штатная согласованная копия SQLite;
150
+ - базы компонентов — копия **всех** баз при остановленных writers
151
+ (команда резервной копии одной базы этого не обеспечивает).
152
+ Git-объекты должны оставаться доступными: копия базы без объектов, на
153
+ которые она ссылается, не восстанавливает историю.
154
+ 4. **Проверить на копии.** Открыть копию новой версией в отдельном каталоге
155
+ (`harness doctor --state <копия>`, `harness status --state <копия>`).
156
+ Проверка несовместимой версии не должна менять данные — сравните хэши
157
+ файлов копии до и после.
158
+
159
+ ## Обновление
160
+
161
+ 1. Установить новую версию точно (у хоста — смена зависимости и lock):
162
+ - из npm — `npm install --save-exact devcontour-harness@<new>`;
163
+ - из архива — `npm install --omit=dev --ignore-scripts ./devcontour-harness-<new>.tgz`.
164
+ 2. `harness doctor --state D` — версии, runtime, песочница, совместимость и
165
+ версии движка, записавшие состояние (`state.engineVersions`: роль
166
+ controller и попытки).
167
+ 3. Возобновить работу **одним** controller (`harness resume`, `harness run`).
168
+ 4. Попытки, активные во время остановки, не продолжаются со старыми
169
+ токенами: recovery либо принимает сохранённый integration SHA при полных
170
+ актуальных evidence, либо завершает попытку; новая попытка получает новое
171
+ владение и новые проверки. Кандидат попытки прежней версии движка повторно
172
+ не используется.
173
+
174
+ Установка новой версии не меняет политику, профили, закрепления контекста,
175
+ инструкции агентам и принятые результаты.
176
+
177
+ ## Откат
178
+
179
+ | Ситуация | Действие |
180
+ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
+ | Изменения исходников до выкатки | Вернуться к проверенной паре коммитов/версий в отдельном checkout или revert-коммитами; страховочную ветку не переписывать |
182
+ | Только пакет, формат данных совместим | Остановить writers → вернуть прежнюю версию (архив или точную версию npm), зависимость и lock → `harness doctor` → возобновить |
183
+ | Новая версия уже изменила формат | Остановить writers → восстановить согласованную копию вместе с совместимой версией пакета → сверить Git refs и артефакты (ниже); простого понижения версии мало |
184
+
185
+ ### Сверка Git после восстановления базы
186
+
187
+ База и Git не образуют одну транзакцию. Если после копии target ref ушёл
188
+ вперёд (поручение принято уже новой версией), восстановленная база не знает
189
+ об этом принятии.
190
+
191
+ 1. Для каждого репозитория сравните вершину target ref с зафиксированной до
192
+ обновления и с result SHA, известными восстановленной базе.
193
+ 2. Коммиты, которых база не знает, — принятые после копии. Их нельзя молча
194
+ «забыть»: либо повторите поручения новой попыткой (её интеграция
195
+ продолжит текущий target ref), либо явно задокументируйте, что эти
196
+ принятия выполнены вне восстановленного состояния.
197
+ 3. Не выполняйте `git reset --hard` пользовательского репозитория и не
198
+ переписывайте target ref ради совпадения с базой — это не откат, а потеря
199
+ принятой работы.
200
+
201
+ Откат объявляется успешным, только когда для каждого target ref известно,
202
+ какие его коммиты учтены восстановленным состоянием.
@@ -0,0 +1,27 @@
1
+ # Примеры конфигурации
2
+
3
+ Все примеры — вымышленные репозитории с gate `unit` (JUnit). Проверка без
4
+ моделей и записи, из каталога `examples/`:
5
+
6
+ ```sh
7
+ npx harness validate --graph single-task/graph.json --policy two-vendors/policy.json
8
+ npx harness validate --graph single-task/graph.json --policy one-vendor/policy.json
9
+ npx harness validate --graph files-only/graph.json --policy files-only/policy.json
10
+ ```
11
+
12
+ У `files-only/` свой граф с контекстом; политики `two-vendors/` и
13
+ `one-vendor/` подходят к графу `single-task/`.
14
+
15
+ | Каталог | Что показывает |
16
+ | --------------------- | ----------------------------------------------------------------------- |
17
+ | `single-task/` | одно поручение `add-greeting` — пример быстрого старта |
18
+ | `two-vendors/` | writer claude, reviewer codex для кода, плана и контракта (умолчание) |
19
+ | `one-vendor/` | writer и reviewer одного вендора — только с явным `same-vendor-allowed` |
20
+ | `files-only/` | поручение, контекст и результат только файлами, без внешних сервисов |
21
+ | `external-connector/` | источник поручений и получатель результата через порты библиотеки |
22
+ | `turbo-starter/` | pnpm/Turborepo-проект с генерацией SDK и Docker lifecycle (см. README) |
23
+
24
+ Reviewer задаётся правилом ревью явно; неявного «противоположного runtime»
25
+ нет. При `different-vendor` (умолчание) reviewer того же вендора, что автор,
26
+ отклоняется ошибкой `REVIEWER_SAME_VENDOR` до вызова модели. `alternates`
27
+ перечисляет запасных reviewer: берётся первый допустимый для этого автора.
@@ -0,0 +1,19 @@
1
+ # Внешний коннектор через порты
2
+
3
+ `connector.mjs` подключает вымышленный сервис задач как источник поручений
4
+ (`WorkSource`) и получатель результата (`ResultSink`). Политика — как в
5
+ `two-vendors/`. Контекст подключается так же портом `ContextSource`
6
+ (`snapshot(ids, at)` возвращает `ContextSnapshot` с содержимым файлов).
7
+
8
+ Гарантии не зависят от коннектора:
9
+
10
+ - граф проходит те же схемы и перекрёстные проверки, что из файла;
11
+ - повтор того же `source.revision` продолжает прежнюю работу, другой digest
12
+ той же редакции — `REVISION_CONFLICT`;
13
+ - доставка отделена от исхода: принятый код не откатывается из-за
14
+ недоступного сервиса, повтор доставки идёт с тем же ключом без моделей и
15
+ интеграции; новая редакция в сервисе получает `conflict`;
16
+ - `trusts` решает только, принимать ли внешний receipt предпосылки; SHA
17
+ предпосылки всё равно должен быть доступен из target ref.
18
+
19
+ Подключение нового источника не требует правки движка.
@@ -0,0 +1,55 @@
1
+ // Внешний коннектор через порты библиотеки: поручения приходят из сервиса
2
+ // задач, результат возвращается в него же. Движок о сервисе ничего не знает;
3
+ // коннектор не может менять политику, выдавать lease или ставить PASS —
4
+ // у портов нет таких операций.
5
+ import { readFileSync } from 'node:fs';
6
+ import { Harness, executionPolicySchema, workGraphSchema } from 'devcontour-harness';
7
+
8
+ /** Вымышленный сервис задач: `GET /plans/:ref`, `PUT /results/:workId`. */
9
+ const TRACKER = process.env.TRACKER_URL ?? 'http://127.0.0.1:8080';
10
+
11
+ /** WorkSource: граф и его редакция из сервиса. */
12
+ const source = {
13
+ id: 'tracker',
14
+ async fetch(ref) {
15
+ const response = await fetch(`${TRACKER}/plans/${encodeURIComponent(ref)}`);
16
+ if (!response.ok) throw new Error(`tracker: ${response.status}`);
17
+ const graph = workGraphSchema.parse(await response.json());
18
+ return { graph, source: graph.source };
19
+ },
20
+ async revision(ref) {
21
+ const response = await fetch(`${TRACKER}/plans/${encodeURIComponent(ref)}`, { method: 'HEAD' });
22
+ return response.headers.get('x-revision') ?? undefined;
23
+ },
24
+ // Внешним receipts по умолчанию не доверяем; доверие не отменяет проверку
25
+ // доступности result SHA в target ref.
26
+ async trusts() {
27
+ return false;
28
+ },
29
+ };
30
+
31
+ /** ResultSink: идемпотентно по ключу; новая редакция в сервисе — conflict. */
32
+ const sink = {
33
+ id: 'tracker',
34
+ async deliver({ key, result }) {
35
+ const response = await fetch(`${TRACKER}/results/${result.workOrderId}`, {
36
+ method: 'PUT',
37
+ headers: { 'content-type': 'application/json', 'idempotency-key': key },
38
+ body: JSON.stringify(result),
39
+ });
40
+ if (response.status === 409)
41
+ return { status: 'conflict', currentRevision: response.headers.get('x-revision') ?? '?' };
42
+ if (!response.ok) throw new Error(`tracker: ${response.status}`);
43
+ return { status: 'delivered', receipt: response.headers.get('location') ?? undefined };
44
+ },
45
+ };
46
+
47
+ const policy = executionPolicySchema.parse(JSON.parse(readFileSync('policy.json', 'utf8')));
48
+ const harness = Harness.open({ stateDir: '../notes-state', policy, baseDir: process.cwd() });
49
+ try {
50
+ const { graph } = await source.fetch('release-1');
51
+ const status = await harness.run({ graph, author: 'external', sink, source });
52
+ console.log(JSON.stringify(status, null, 2));
53
+ } finally {
54
+ harness.close();
55
+ }
@@ -0,0 +1,28 @@
1
+ # Только файлы
2
+
3
+ Поручение — `graph.json`, контекст — каталог `context/<id>/`, результат —
4
+ каталог получателя. Ни сервисов, ни базы задач.
5
+
6
+ ```text
7
+ notes-app/
8
+ graph.json поручение add-tags ссылается на контекст "norms"
9
+ policy.json
10
+ context/norms/rules.md закрепляется за попыткой целиком
11
+ ```
12
+
13
+ ```sh
14
+ npx harness validate --graph graph.json --policy policy.json
15
+ npx harness run --graph graph.json --policy policy.json --state ../notes-state \
16
+ --sink file:../notes-results
17
+ npx harness result --state ../notes-state --work add-tags
18
+ ```
19
+
20
+ Без `--context` CLI собирает снимок из `context/` рядом с графом: каждый
21
+ источник — подкаталог, файлы закрепляются с digest и содержимым; файлы
22
+ `references/` — справочные. Снимок передаётся writer и reviewer одними и теми
23
+ же байтами, его digest попадает в `result.inputs.contextDigest`. Файлы
24
+ контекста из этого репозитория — защищённые пути попытки.
25
+
26
+ Получатель `file:D` пишет `D/<workId>.json`. Повтор `harness deliver`
27
+ идемпотентен по ключу и не вызывает моделей или интеграции; если граф
28
+ уже в новой редакции, доставка старого результата — `conflict`.
@@ -0,0 +1,4 @@
1
+ # Нормы проекта
2
+
3
+ - Модули экспортируют функции через `export function`.
4
+ - Новые функции покрываются тестом в `test/`.