devcontour-harness 0.0.0-stage → 0.1.0-alpha.5

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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -2
  3. package/docs/architecture.md +473 -0
  4. package/docs/compatibility.md +192 -0
  5. package/docs/contracts.md +575 -0
  6. package/docs/quickstart.md +182 -0
  7. package/docs/upgrade.md +166 -0
  8. package/examples/README.md +26 -0
  9. package/examples/external-connector/README.md +19 -0
  10. package/examples/external-connector/connector.mjs +55 -0
  11. package/examples/files-only/README.md +28 -0
  12. package/examples/files-only/context/norms/rules.md +4 -0
  13. package/examples/files-only/graph.json +23 -0
  14. package/examples/files-only/policy.json +30 -0
  15. package/examples/one-vendor/README.md +19 -0
  16. package/examples/one-vendor/policy.json +27 -0
  17. package/examples/single-task/README.md +18 -0
  18. package/examples/single-task/graph.json +27 -0
  19. package/examples/single-task/policy-same-vendor.json +55 -0
  20. package/examples/single-task/policy.json +52 -0
  21. package/examples/single-task/src/.gitkeep +0 -0
  22. package/examples/single-task/test/run.js +19 -0
  23. package/examples/two-vendors/policy.json +30 -0
  24. package/lib/agents/cli-adapter.d.ts +48 -0
  25. package/lib/agents/cli-adapter.js +384 -0
  26. package/lib/agents/inspect.d.ts +6 -0
  27. package/lib/agents/inspect.js +46 -0
  28. package/lib/agents/probe-server.d.ts +2 -0
  29. package/lib/agents/probe-server.js +13 -0
  30. package/lib/agents/review-probe.d.ts +123 -0
  31. package/lib/agents/review-probe.js +279 -0
  32. package/lib/agents/runtime-events.d.ts +11 -0
  33. package/lib/agents/runtime-events.js +21 -0
  34. package/lib/agents/schemas.d.ts +215 -0
  35. package/lib/agents/schemas.js +120 -0
  36. package/lib/agents/usage.d.ts +8 -0
  37. package/lib/agents/usage.js +83 -0
  38. package/lib/cli.d.ts +2 -0
  39. package/lib/cli.js +331 -0
  40. package/lib/connectors/files.d.ts +108 -0
  41. package/lib/connectors/files.js +133 -0
  42. package/lib/contracts/checks.d.ts +307 -0
  43. package/lib/contracts/checks.js +181 -0
  44. package/lib/contracts/cli.d.ts +3086 -0
  45. package/lib/contracts/cli.js +425 -0
  46. package/lib/contracts/context.d.ts +135 -0
  47. package/lib/contracts/context.js +129 -0
  48. package/lib/contracts/digest.d.ts +9 -0
  49. package/lib/contracts/digest.js +22 -0
  50. package/lib/contracts/errors.d.ts +79 -0
  51. package/lib/contracts/errors.js +68 -0
  52. package/lib/contracts/events.d.ts +795 -0
  53. package/lib/contracts/events.js +249 -0
  54. package/lib/contracts/execution.d.ts +3048 -0
  55. package/lib/contracts/execution.js +864 -0
  56. package/lib/contracts/failure.d.ts +67 -0
  57. package/lib/contracts/failure.js +122 -0
  58. package/lib/contracts/index.d.ts +17 -0
  59. package/lib/contracts/index.js +17 -0
  60. package/lib/contracts/policy.d.ts +1065 -0
  61. package/lib/contracts/policy.js +534 -0
  62. package/lib/contracts/ports.d.ts +367 -0
  63. package/lib/contracts/ports.js +1 -0
  64. package/lib/contracts/primitives.d.ts +63 -0
  65. package/lib/contracts/primitives.js +73 -0
  66. package/lib/contracts/profiles.d.ts +322 -0
  67. package/lib/contracts/profiles.js +105 -0
  68. package/lib/contracts/published.d.ts +3014 -0
  69. package/lib/contracts/published.js +34 -0
  70. package/lib/contracts/result.d.ts +1109 -0
  71. package/lib/contracts/result.js +273 -0
  72. package/lib/contracts/review.d.ts +210 -0
  73. package/lib/contracts/review.js +153 -0
  74. package/lib/contracts/version.d.ts +17 -0
  75. package/lib/contracts/version.js +17 -0
  76. package/lib/contracts/work.d.ts +173 -0
  77. package/lib/contracts/work.js +178 -0
  78. package/lib/control/base-move.d.ts +44 -0
  79. package/lib/control/base-move.js +90 -0
  80. package/lib/control/doctor.d.ts +35 -0
  81. package/lib/control/doctor.js +71 -0
  82. package/lib/control/harness.d.ts +1079 -0
  83. package/lib/control/harness.js +714 -0
  84. package/lib/control/provenance.d.ts +17 -0
  85. package/lib/control/provenance.js +25 -0
  86. package/lib/control/recovery.d.ts +235 -0
  87. package/lib/control/recovery.js +236 -0
  88. package/lib/control/review.d.ts +62 -0
  89. package/lib/control/review.js +170 -0
  90. package/lib/control/workflow.d.ts +247 -0
  91. package/lib/control/workflow.js +259 -0
  92. package/lib/engine/budget.d.ts +345 -0
  93. package/lib/engine/budget.js +278 -0
  94. package/lib/engine/classify.d.ts +8 -0
  95. package/lib/engine/classify.js +31 -0
  96. package/lib/engine/engine.d.ts +1503 -0
  97. package/lib/engine/engine.js +2259 -0
  98. package/lib/engine/failure.d.ts +1 -0
  99. package/lib/engine/failure.js +3 -0
  100. package/lib/engine/joint-runner.d.ts +257 -0
  101. package/lib/engine/joint-runner.js +254 -0
  102. package/lib/engine/measured.d.ts +15 -0
  103. package/lib/engine/measured.js +82 -0
  104. package/lib/engine/prompt.d.ts +38 -0
  105. package/lib/engine/prompt.js +74 -0
  106. package/lib/engine/property-recovery.d.ts +50 -0
  107. package/lib/engine/property-recovery.js +169 -0
  108. package/lib/engine/repair.d.ts +33 -0
  109. package/lib/engine/repair.js +82 -0
  110. package/lib/engine/result.d.ts +9 -0
  111. package/lib/engine/result.js +166 -0
  112. package/lib/engine/reuse.d.ts +80 -0
  113. package/lib/engine/reuse.js +97 -0
  114. package/lib/engine/runner.d.ts +335 -0
  115. package/lib/engine/runner.js +1467 -0
  116. package/lib/engine/standalone.d.ts +6 -0
  117. package/lib/engine/standalone.js +48 -0
  118. package/lib/engine/usage.d.ts +87 -0
  119. package/lib/engine/usage.js +165 -0
  120. package/lib/environment/isolation.d.ts +110 -0
  121. package/lib/environment/isolation.js +238 -0
  122. package/lib/environment/lifecycle.d.ts +40 -0
  123. package/lib/environment/lifecycle.js +191 -0
  124. package/lib/environment/live-calls.d.ts +14 -0
  125. package/lib/environment/live-calls.js +72 -0
  126. package/lib/environment/process.d.ts +32 -0
  127. package/lib/environment/process.js +368 -0
  128. package/lib/environment/redaction.d.ts +22 -0
  129. package/lib/environment/redaction.js +102 -0
  130. package/lib/environment/resources.d.ts +31 -0
  131. package/lib/environment/resources.js +120 -0
  132. package/lib/index.d.ts +65 -0
  133. package/lib/index.js +67 -0
  134. package/lib/package.d.ts +2 -0
  135. package/lib/package.js +3 -0
  136. package/lib/profiles/resolve.d.ts +186 -0
  137. package/lib/profiles/resolve.js +242 -0
  138. package/lib/storage/document.d.ts +1186 -0
  139. package/lib/storage/document.js +80 -0
  140. package/lib/storage/sqlite.d.ts +35 -0
  141. package/lib/storage/sqlite.js +260 -0
  142. package/lib/testing/index.d.ts +5 -0
  143. package/lib/testing/index.js +5 -0
  144. package/lib/testing/store-contract.d.ts +8 -0
  145. package/lib/testing/store-contract.js +167 -0
  146. package/lib/validate.d.ts +36 -0
  147. package/lib/validate.js +175 -0
  148. package/lib/verification/acceptance.d.ts +268 -0
  149. package/lib/verification/acceptance.js +654 -0
  150. package/lib/verification/checks.d.ts +72 -0
  151. package/lib/verification/checks.js +190 -0
  152. package/lib/verification/confirm.d.ts +125 -0
  153. package/lib/verification/confirm.js +411 -0
  154. package/lib/verification/corpus.d.ts +1303 -0
  155. package/lib/verification/corpus.js +744 -0
  156. package/lib/verification/dependencies.d.ts +66 -0
  157. package/lib/verification/dependencies.js +144 -0
  158. package/lib/verification/gates.d.ts +144 -0
  159. package/lib/verification/gates.js +559 -0
  160. package/lib/verification/joint.d.ts +63 -0
  161. package/lib/verification/joint.js +121 -0
  162. package/lib/verification/proof.d.ts +54 -0
  163. package/lib/verification/proof.js +96 -0
  164. package/lib/verification/property.d.ts +86 -0
  165. package/lib/verification/property.js +214 -0
  166. package/lib/verification/requirements.d.ts +25 -0
  167. package/lib/verification/requirements.js +69 -0
  168. package/lib/verification/self-check.d.ts +29 -0
  169. package/lib/verification/self-check.js +99 -0
  170. package/lib/verification/spike.d.ts +79 -0
  171. package/lib/verification/spike.js +124 -0
  172. package/package.json +79 -4
  173. package/profiles/expo-mobile.json +15 -0
  174. package/profiles/go-api.json +15 -0
  175. package/profiles/mobile-maestro.json +29 -0
  176. package/profiles/nest-api.json +26 -0
  177. package/profiles/next-product.json +72 -0
  178. package/profiles/node-checks.json +77 -0
  179. package/profiles/python-api.json +32 -0
  180. package/profiles/react-native.json +50 -0
  181. package/profiles/react-vite-admin.json +71 -0
  182. package/schemas/context-snapshot.schema.json +231 -0
  183. package/schemas/contract-document.schema.json +385 -0
  184. package/schemas/execution-document.schema.json +3540 -0
  185. package/schemas/execution-policy.schema.json +2148 -0
  186. package/schemas/joint-verification.schema.json +617 -0
  187. package/schemas/result-envelope.schema.json +1440 -0
  188. package/schemas/review-record.schema.json +177 -0
  189. package/schemas/run-event.schema.json +2365 -0
  190. package/schemas/run-result.schema.json +1363 -0
  191. package/schemas/stack-profile.schema.json +492 -0
  192. package/schemas/work-graph.schema.json +381 -0
  193. package/schemas/work-order.schema.json +256 -0
  194. package/skills/harness/SKILL.md +64 -0
  195. package/templates/AGENTS.md +13 -0
  196. package/templates/graph.json +22 -0
  197. package/templates/policy.json +28 -0
@@ -0,0 +1,182 @@
1
+ # Quickstart
2
+
3
+ Пять минут от пустого каталога до принятого поручения. Пример — вымышленный
4
+ репозиторий `greeting` с одной задачей: добавить функцию `greet(name)`.
5
+
6
+ ## 1. Что понадобится
7
+
8
+ - Node.js ≥ 24.3.0 и Git.
9
+ - Установленные и авторизованные CLI `claude` и `codex` — writer и reviewer
10
+ примера. `harness doctor` проверяет их без вызова модели.
11
+ - npm (идёт с Node.js) — для установки из registry или из локального архива.
12
+
13
+ ```sh
14
+ mkdir harness-demo && cd harness-demo
15
+ npm init -y >/dev/null
16
+ npm install --save-exact devcontour-harness@0.1.0-alpha.5
17
+ npx harness --version
18
+ npx harness doctor
19
+ ```
20
+
21
+ Вместо точной версии можно взять текущую alpha: `devcontour-harness@alpha`.
22
+ Без registry пакет ставится из архива:
23
+ `npm install --omit=dev --ignore-scripts ./devcontour-harness-<version>.tgz`.
24
+
25
+ CLI установлен локально, в `node_modules/.bin` каталога `harness-demo`.
26
+ `npx harness` находит его из этого каталога и его подкаталогов (дальше
27
+ работа идёт в `harness-demo/greeting`); глобальная команда `harness` не
28
+ нужна.
29
+
30
+ ## 2. Репозиторий и входы
31
+
32
+ ```sh
33
+ cp -R node_modules/devcontour-harness/examples/single-task greeting
34
+ cd greeting
35
+ git init -q -b main && git add -A && git commit -qm init
36
+ ```
37
+
38
+ В каталоге:
39
+
40
+ | Файл | Что это |
41
+ | ------------------------- | ---------------------------------------------------------------- |
42
+ | `graph.json` | `WorkGraph`: одно поручение `add-greeting` с критериями приёмки |
43
+ | `policy.json` | `ExecutionPolicy`: репозиторий, gate `unit` с JUnit, роли, ревью |
44
+ | `policy-same-vendor.json` | Та же политика, но reviewer того же вендора, разрешённый явно |
45
+ | `test/run.js` | Проверка: пишет JUnit-отчёт в путь из `DEVCONTOUR_REPORT_PATH` |
46
+
47
+ Поручение в `graph.json`:
48
+
49
+ ```json
50
+ {
51
+ "id": "add-greeting",
52
+ "title": "Add a greeting function",
53
+ "goal": "Add greet(name) to src/greet.js returning 'Hello, <name>!'.",
54
+ "acceptance": ["greet('Ada') returns 'Hello, Ada!'", "npm test passes with a JUnit report"],
55
+ "role": "developer",
56
+ "repositoryId": "app",
57
+ "writeScope": ["src"],
58
+ "gates": ["unit"]
59
+ }
60
+ ```
61
+
62
+ ## 3. Проверить входы
63
+
64
+ ```sh
65
+ npx harness validate --graph graph.json --policy policy.json
66
+ ```
67
+
68
+ Команда не вызывает моделей и ничего не пишет. В ответе — digests графа и
69
+ политики и число поручений. Любая ошибка — exit 2 и список `issues` с путями.
70
+
71
+ ## 4. Запустить
72
+
73
+ ```sh
74
+ npx harness run --graph graph.json --policy policy.json \
75
+ --state ../greeting-state --author external --sink file:../greeting-results
76
+ ```
77
+
78
+ `run`:
79
+
80
+ 1. регистрирует граф и проводит техническое ревью плана reviewer-ом из
81
+ политики (здесь — codex);
82
+ 2. согласует граф (`approvalMode: agent`; при `operator` нужен `harness approve`);
83
+ 3. выдаёт попытку writer-у (claude) в отдельном Git worktree;
84
+ 4. прогоняет gate `unit` и независимое ревью на candidate SHA;
85
+ 5. интегрирует кандидата с текущей принятой веткой, снова прогоняет gate и
86
+ ревью — уже на integration SHA;
87
+ 6. продвигает `refs/heads/harness/accepted` compare-and-swap и записывает
88
+ результат; `--sink` доставляет его в каталог `../greeting-results`.
89
+
90
+ Основная ветка и рабочее дерево не меняются. Повторный `run` той же редакции
91
+ графа продолжает работу и не создаёт второй попытки.
92
+
93
+ ## 5. Прочитать результат
94
+
95
+ ```sh
96
+ npx harness result --state ../greeting-state --work add-greeting
97
+ ```
98
+
99
+ Сокращённый ответ (SHA и digests здесь условные):
100
+
101
+ ```json
102
+ {
103
+ "apiVersion": "devcontour-harness/v1alpha1",
104
+ "command": "result",
105
+ "ok": true,
106
+ "data": {
107
+ "apiVersion": "devcontour-harness/v1alpha1",
108
+ "result": {
109
+ "outcome": "accepted",
110
+ "workOrderId": "add-greeting",
111
+ "graphId": "greeting",
112
+ "runId": "…",
113
+ "attempt": 1,
114
+ "repositoryId": "app",
115
+ "targetRef": "refs/heads/harness/accepted",
116
+ "shas": {
117
+ "base": "1111…",
118
+ "candidate": "2222…",
119
+ "integration": "3333…",
120
+ "result": "3333…"
121
+ },
122
+ "inputs": {
123
+ "harnessVersion": "<version>",
124
+ "stateVersion": 1,
125
+ "workOrderDigest": "…",
126
+ "graphDigest": "…",
127
+ "policyDigest": "…",
128
+ "writer": "claude"
129
+ },
130
+ "evidence": [
131
+ { "kind": "test", "phase": "candidate", "gate": "unit", "sha": "2222…", "passed": true },
132
+ {
133
+ "kind": "review",
134
+ "phase": "candidate",
135
+ "gate": "independent-review",
136
+ "sha": "2222…",
137
+ "passed": true
138
+ },
139
+ { "kind": "test", "phase": "integration", "gate": "unit", "sha": "3333…", "passed": true },
140
+ {
141
+ "kind": "review",
142
+ "phase": "integration",
143
+ "gate": "independent-review",
144
+ "sha": "3333…",
145
+ "passed": true
146
+ }
147
+ ],
148
+ "findings": [],
149
+ "limitations": [],
150
+ "finishedAt": "…"
151
+ },
152
+ "delivery": [{ "sink": "files", "status": "delivered", "attempts": 1 }]
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## 6. Проверить result SHA
158
+
159
+ ```sh
160
+ git rev-parse refs/heads/harness/accepted # = data.result.shas.result
161
+ git merge-base --is-ancestor <shas.base> refs/heads/harness/accepted && echo ok
162
+ git show --stat refs/heads/harness/accepted # изменения только в src/
163
+ ```
164
+
165
+ `result` должен совпасть с вершиной target ref; каждый PASS в `evidence`
166
+ относится ровно к `candidate` или `integration` SHA своей фазы.
167
+
168
+ ## 7. Что дальше
169
+
170
+ - Пауза и продолжение: `harness pause --state D`, `harness resume --state D`.
171
+ - Отмена и повтор: `harness cancel --work ID`, `harness retry --work ID`.
172
+ - События: `harness events --state D --after N`.
173
+ - Один вендор: запустите с `--policy policy-same-vendor.json` — reviewer того
174
+ же вендора допускается только потому, что политика это явно разрешает.
175
+ - Другие конфигурации — в `examples/` этого руководства.
176
+
177
+ Исход без принятия (`rejected`, `blocked`, `stopped`, `cancelled`) несёт
178
+ `stopReason{code, message}`; result SHA у него нет. Exit codes: 0 успех,
179
+ 2 неверный вход, 3 отказ или блокировка, 4 инфраструктура.
180
+
181
+ Тесты пакета используют fixture executables вместо живых CLI; их
182
+ результаты проверяют механику, а не качество моделей.
@@ -0,0 +1,166 @@
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 | Пишет поверх | Примечание |
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`. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
35
+ формата до записи проверяют тесты совместимости пакета
36
+ (`tests/compatibility.test.ts`).
37
+
38
+ Новые версии добавляют только необязательные поля и новые пространства
39
+ локальных записей. Схемы состояния строгие: прежняя версия отклоняет
40
+ незнакомое поле до любой записи, а не сохраняет его молча. Поэтому откат
41
+ пакета «формат совместим» возможен только для состояния без новых полей,
42
+ иначе — восстановление копии (таблица «Откат» ниже).
43
+
44
+ ### alpha.2: версия движка в роли controller
45
+
46
+ Роль controller записывает версию движка (`engineVersion`). Процесс другой
47
+ версии — и процесс прежней версии, записавший роль без версии, — живую роль
48
+ не перенимает, даже под тем же именем владельца: `harness run` получает
49
+ `CONTROLLER_BUSY` с обеими версиями, выдачи нет. После истечения срока роль
50
+ переходит штатно, а активные попытки прежней версии проходят recovery без
51
+ повторного использования кандидата. Штатная остановка снимает роль целиком.
52
+
53
+ ### alpha.3: версии движка в `doctor`
54
+
55
+ Формат состояния тот же, что у alpha.2 (столбец alpha.2 матрицы относится и
56
+ к alpha.3). `harness doctor --state D` читает состояние строгой схемой этой
57
+ версии и для совместимого состояния называет версии движка, записавшие его
58
+ (`state.engineVersions`: роль controller и попытки); состояние, которое эта
59
+ версия не читает, отмечается несовместимым без записи.
60
+
61
+ ### alpha.4: открытие без записи и владение попыткой по версии
62
+
63
+ - Существующее состояние сначала проверяется целиком соединением только для
64
+ чтения: версия формата и документ строгой схемой. Несовместимое состояние
65
+ отклоняется до создания таблиц, записи автора (`meta.writer`) и смены
66
+ journal mode — файл остаётся байт в байт прежним. `harness doctor --state`
67
+ открывает состояние только для чтения и автором не становится.
68
+ - Операции активной попытки (продление, фаза, evidence, принятие, отказ) и
69
+ совместной проверки выполняет только движок той версии, что их начал.
70
+ Другая версия — и попытка без версии, записанная до её появления, — получает
71
+ `LEASE_STALE` даже с верным token до истечения срока; продолжение — только
72
+ через истечение и recovery с новым владением.
73
+ - Совместная проверка записывает `engineVersion`; состояние, где она есть,
74
+ прежние версии не читают (строка матрицы выше), поэтому откат с alpha.4 на
75
+ более раннюю версию после первой совместной проверки — только через копию.
76
+
77
+ ### alpha.5: публикация в npm
78
+
79
+ Код движка, форматы состояния, политики, отчётов и публичный API те же, что
80
+ у alpha.4; меняются поставка (npm, тег `alpha`) и номер версии. Состояние
81
+ alpha.4 alpha.5 читает и пишет без преобразования, состояние alpha.5 читает
82
+ alpha.4 (новых полей нет).
83
+
84
+ Номер версии — это `engineVersion`, поэтому правила alpha.2–alpha.4
85
+ действуют и между alpha.4 и alpha.5:
86
+
87
+ - живая роль controller alpha.4 не переходит к alpha.5 (`CONTROLLER_BUSY`),
88
+ пока не будет снята штатной остановкой или не истечёт;
89
+ - активную попытку и совместную проверку, начатые alpha.4, alpha.5 не
90
+ продолжает даже с верным token (`LEASE_STALE`): после истечения срока
91
+ recovery передаёт их новой версии без повторного использования кандидата
92
+ прежней версии;
93
+ - в обратную сторону то же: после отката alpha.4 не продолжает попытки
94
+ alpha.5.
95
+
96
+ Поэтому обновление — по общей процедуре ниже: остановить writers, сделать
97
+ копию, проверить её новой версией. У хоста, который поставлял alpha.4
98
+ архивом, переход на registry — смена зависимости на точную версию
99
+ `0.1.0-alpha.5` и обновление lock; содержимое alpha.4 под своим номером не
100
+ меняется.
101
+
102
+ ## Перед обновлением
103
+
104
+ 1. **Остановить записывающих.** Запретить новые выдачи (`harness pause`),
105
+ дождаться завершения или явно остановить активные попытки, остановить
106
+ controller, сервер и всех остальных, кто пишет в состояние. Одной паузы
107
+ очереди недостаточно.
108
+ 2. **Зафиксировать.** Версии пакета и потребителя, конфигурацию, вершины
109
+ target refs и result SHA принятых поручений (`git rev-parse` каждого
110
+ target ref, `harness result` каждого поручения).
111
+ 3. **Сделать копию.**
112
+ - standalone — каталог `--state` целиком, пока controller остановлен;
113
+ - центральная база хоста — штатная согласованная копия SQLite;
114
+ - базы компонентов — копия **всех** баз при остановленных writers
115
+ (команда резервной копии одной базы этого не обеспечивает).
116
+ Git-объекты должны оставаться доступными: копия базы без объектов, на
117
+ которые она ссылается, не восстанавливает историю.
118
+ 4. **Проверить на копии.** Открыть копию новой версией в отдельном каталоге
119
+ (`harness doctor --state <копия>`, `harness status --state <копия>`).
120
+ Проверка несовместимой версии не должна менять данные — сравните хэши
121
+ файлов копии до и после.
122
+
123
+ ## Обновление
124
+
125
+ 1. Установить новую версию точно (у хоста — смена зависимости и lock):
126
+ - из npm — `npm install --save-exact devcontour-harness@<new>`;
127
+ - из архива — `npm install --omit=dev --ignore-scripts ./devcontour-harness-<new>.tgz`.
128
+ 2. `harness doctor --state D` — версии, runtime, песочница, совместимость и
129
+ версии движка, записавшие состояние (`state.engineVersions`: роль
130
+ controller и попытки).
131
+ 3. Возобновить работу **одним** controller (`harness resume`, `harness run`).
132
+ 4. Попытки, активные во время остановки, не продолжаются со старыми
133
+ токенами: recovery либо принимает сохранённый integration SHA при полных
134
+ актуальных evidence, либо завершает попытку; новая попытка получает новое
135
+ владение и новые проверки. Кандидат попытки прежней версии движка повторно
136
+ не используется.
137
+
138
+ Установка новой версии не меняет политику, профили, закрепления контекста,
139
+ инструкции агентам и принятые результаты.
140
+
141
+ ## Откат
142
+
143
+ | Ситуация | Действие |
144
+ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145
+ | Изменения исходников до выкатки | Вернуться к проверенной паре коммитов/версий в отдельном checkout или revert-коммитами; страховочную ветку не переписывать |
146
+ | Только пакет, формат данных совместим | Остановить writers → вернуть прежнюю версию (архив или точную версию npm), зависимость и lock → `harness doctor` → возобновить |
147
+ | Новая версия уже изменила формат | Остановить writers → восстановить согласованную копию вместе с совместимой версией пакета → сверить Git refs и артефакты (ниже); простого понижения версии мало |
148
+
149
+ ### Сверка Git после восстановления базы
150
+
151
+ База и Git не образуют одну транзакцию. Если после копии target ref ушёл
152
+ вперёд (поручение принято уже новой версией), восстановленная база не знает
153
+ об этом принятии.
154
+
155
+ 1. Для каждого репозитория сравните вершину target ref с зафиксированной до
156
+ обновления и с result SHA, известными восстановленной базе.
157
+ 2. Коммиты, которых база не знает, — принятые после копии. Их нельзя молча
158
+ «забыть»: либо повторите поручения новой попыткой (её интеграция
159
+ продолжит текущий target ref), либо явно задокументируйте, что эти
160
+ принятия выполнены вне восстановленного состояния.
161
+ 3. Не выполняйте `git reset --hard` пользовательского репозитория и не
162
+ переписывайте target ref ради совпадения с базой — это не откат, а потеря
163
+ принятой работы.
164
+
165
+ Откат объявляется успешным, только когда для каждого target ref известно,
166
+ какие его коммиты учтены восстановленным состоянием.
@@ -0,0 +1,26 @@
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
+
23
+ Reviewer задаётся правилом ревью явно; неявного «противоположного runtime»
24
+ нет. При `different-vendor` (умолчание) reviewer того же вендора, что автор,
25
+ отклоняется ошибкой `REVIEWER_SAME_VENDOR` до вызова модели. `alternates`
26
+ перечисляет запасных 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/`.
@@ -0,0 +1,23 @@
1
+ {
2
+ "apiVersion": "devcontour-harness/v1alpha1",
3
+ "id": "notes",
4
+ "source": { "connector": "files", "ref": "graph.json", "revision": "1" },
5
+ "orders": [
6
+ {
7
+ "apiVersion": "devcontour-harness/v1alpha1",
8
+ "id": "add-tags",
9
+ "source": { "connector": "files", "ref": "add-tags", "revision": "1" },
10
+ "title": "Add tags to notes",
11
+ "goal": "Notes accept a list of tags; tags are trimmed and deduplicated.",
12
+ "acceptance": [
13
+ "createNote({ tags: [' a', 'a', 'b'] }) stores ['a', 'b']",
14
+ "npm test passes with a JUnit report"
15
+ ],
16
+ "role": "developer",
17
+ "repositoryId": "app",
18
+ "writeScope": ["src", "test"],
19
+ "gates": ["unit"],
20
+ "context": ["norms"]
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "policyVersion": 1,
3
+ "repositories": [
4
+ {
5
+ "id": "app",
6
+ "path": ".",
7
+ "targetRef": "refs/heads/harness/accepted",
8
+ "gates": [
9
+ {
10
+ "id": "unit",
11
+ "kind": "test",
12
+ "command": ["npm", "test"],
13
+ "timeoutMs": 240000,
14
+ "report": { "type": "junit", "path": ".reports/junit.xml" }
15
+ }
16
+ ],
17
+ "protectedPaths": [".github/", "package-lock.json", "policy.json", "graph.json"]
18
+ }
19
+ ],
20
+ "roles": {
21
+ "developer": { "writer": { "runtime": "claude" }, "writePaths": ["src", "test"] }
22
+ },
23
+ "review": {
24
+ "code": { "reviewer": { "runtime": "codex" } },
25
+ "plan": { "reviewer": { "runtime": "codex" }, "alternates": [{ "runtime": "claude" }] },
26
+ "contract": { "reviewer": { "runtime": "codex" }, "alternates": [{ "runtime": "claude" }] }
27
+ },
28
+ "maxAttempts": 3,
29
+ "budgets": { "work": { "usd": 5 }, "total": { "usd": 50 }, "onUnknown": "stop" }
30
+ }
@@ -0,0 +1,19 @@
1
+ # Один вендор
2
+
3
+ Writer и reviewer — claude. Это разрешено только потому, что каждое правило
4
+ ревью явно говорит `"vendor": "same-vendor-allowed"`. Без этой строки
5
+ `harness validate` отклоняет политику (`REVIEWER_SAME_VENDOR`).
6
+
7
+ Что сохраняется и при одном вендоре:
8
+
9
+ - reviewer — отдельная роль и отдельный вызов со свежей сессией: в его запросе
10
+ нет разговора, отчёта и идентификатора сессии writer;
11
+ - reviewer получает исходные критерии приёмки, закреплённый контекст, diff,
12
+ evidence проверенного SHA и прежние находки;
13
+ - reviewer работает только на чтение: правка дерева или HEAD делает его
14
+ ответ недействительным;
15
+ - приёмка требует тех же gates и ревью обеих фаз; разные модели не нужны.
16
+
17
+ Тот же эффект для одного runtime без смены вендора по умолчанию даёт
18
+ `alternates`: reviewer `codex`, запасной `claude` — для автора codex будет
19
+ выбран claude только при `same-vendor-allowed`.
@@ -0,0 +1,27 @@
1
+ {
2
+ "policyVersion": 1,
3
+ "repositories": [
4
+ {
5
+ "id": "app",
6
+ "path": ".",
7
+ "targetRef": "refs/heads/harness/accepted",
8
+ "gates": [
9
+ {
10
+ "id": "unit",
11
+ "kind": "test",
12
+ "command": ["npm", "test"],
13
+ "report": { "type": "junit", "path": ".reports/junit.xml" }
14
+ }
15
+ ],
16
+ "protectedPaths": [".github/", "package-lock.json", "policy.json", "graph.json"]
17
+ }
18
+ ],
19
+ "roles": {
20
+ "developer": { "writer": { "runtime": "claude" } }
21
+ },
22
+ "review": {
23
+ "code": { "reviewer": { "runtime": "claude" }, "vendor": "same-vendor-allowed" },
24
+ "plan": { "reviewer": { "runtime": "claude" }, "vendor": "same-vendor-allowed" },
25
+ "contract": { "reviewer": { "runtime": "claude" }, "vendor": "same-vendor-allowed" }
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ # Пример: одна задача
2
+
3
+ Вымышленный репозиторий с одним поручением: добавить `greet(name)` в
4
+ `src/greet.js`. Проверка — `node test/run.js`, она пишет JUnit-отчёт в путь из
5
+ `DEVCONTOUR_REPORT_PATH`.
6
+
7
+ ```sh
8
+ cp -R node_modules/devcontour-harness/examples/single-task greeting && cd greeting
9
+ git init -q && git add -A && git commit -qm init
10
+ npx harness validate --graph graph.json --policy policy.json
11
+ npx harness run --graph graph.json --policy policy.json --state ../greeting-state --author external --sink file:../greeting-results
12
+ npx harness result --state ../greeting-state --work add-greeting
13
+ git rev-parse refs/heads/harness/accepted # совпадает с result.shas.result
14
+ ```
15
+
16
+ `policy.json` — writer claude, reviewer codex (разные вендоры).
17
+ `policy-same-vendor.json` — reviewer того же вендора, разрешённый явно.
18
+ Для запуска нужны установленные и авторизованные CLI `codex` и `claude`.
@@ -0,0 +1,27 @@
1
+ {
2
+ "apiVersion": "devcontour-harness/v1alpha1",
3
+ "id": "greeting",
4
+ "source": {
5
+ "connector": "files",
6
+ "ref": "graph.json",
7
+ "revision": "1"
8
+ },
9
+ "orders": [
10
+ {
11
+ "apiVersion": "devcontour-harness/v1alpha1",
12
+ "id": "add-greeting",
13
+ "source": {
14
+ "connector": "files",
15
+ "ref": "add-greeting",
16
+ "revision": "1"
17
+ },
18
+ "title": "Add a greeting function",
19
+ "goal": "Add greet(name) to src/greet.js returning 'Hello, <name>!'.",
20
+ "acceptance": ["greet('Ada') returns 'Hello, Ada!'", "npm test passes with a JUnit report"],
21
+ "role": "developer",
22
+ "repositoryId": "app",
23
+ "writeScope": ["src"],
24
+ "gates": ["unit"]
25
+ }
26
+ ]
27
+ }