devcontour-harness 0.1.0-alpha.18 → 0.1.0-alpha.19

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 (51) hide show
  1. package/README.md +12 -2
  2. package/docs/architecture.md +48 -0
  3. package/docs/contracts.md +3 -0
  4. package/docs/quickstart.md +1 -1
  5. package/docs/release-notes/0.1.0-alpha.19.md +96 -0
  6. package/docs/upgrade.md +17 -16
  7. package/lib/cli.js +7 -1
  8. package/lib/contracts/cli.d.ts +30 -2
  9. package/lib/contracts/events.d.ts +21 -0
  10. package/lib/contracts/events.js +7 -0
  11. package/lib/contracts/execution.d.ts +24 -3
  12. package/lib/contracts/execution.js +14 -2
  13. package/lib/contracts/planning.js +2 -2
  14. package/lib/contracts/policy.d.ts +1 -1
  15. package/lib/contracts/ports.d.ts +6 -0
  16. package/lib/contracts/primitives.d.ts +13 -0
  17. package/lib/contracts/primitives.js +15 -0
  18. package/lib/contracts/profiles.js +2 -2
  19. package/lib/contracts/published.d.ts +23 -2
  20. package/lib/contracts/review.d.ts +2 -2
  21. package/lib/contracts/review.js +2 -2
  22. package/lib/contracts/work.js +3 -3
  23. package/lib/control/base-move.js +2 -2
  24. package/lib/control/harness.d.ts +1 -1
  25. package/lib/control/harness.js +7 -2
  26. package/lib/control/workflow.js +6 -0
  27. package/lib/engine/budget.d.ts +1 -1
  28. package/lib/engine/engine.d.ts +14 -2
  29. package/lib/engine/engine.js +40 -1
  30. package/lib/engine/runner.d.ts +11 -1
  31. package/lib/engine/runner.js +82 -11
  32. package/lib/environment/git-trust.d.ts +36 -0
  33. package/lib/environment/git-trust.js +174 -0
  34. package/lib/environment/isolation.d.ts +103 -1
  35. package/lib/environment/isolation.js +340 -5
  36. package/lib/environment/process.d.ts +5 -0
  37. package/lib/environment/process.js +20 -1
  38. package/lib/index.d.ts +2 -0
  39. package/lib/index.js +4 -0
  40. package/lib/integrations/connector/config.js +2 -2
  41. package/lib/storage/document.d.ts +10 -2
  42. package/lib/verification/confirm.js +2 -2
  43. package/lib/verification/gates.d.ts +12 -1
  44. package/lib/verification/gates.js +81 -8
  45. package/lib/verification/requirements.js +6 -1
  46. package/package.json +1 -1
  47. package/schemas/execution-document.schema.json +27 -5
  48. package/schemas/plan-draft.schema.json +2 -2
  49. package/schemas/run-event.schema.json +22 -0
  50. package/schemas/work-graph.schema.json +2 -2
  51. package/schemas/work-order.schema.json +2 -2
package/README.md CHANGED
@@ -5,7 +5,7 @@ Headless-движок исполнения инженерных поручени
5
5
  интеграция с принятой веткой через compare-and-swap и машинный результат с SHA
6
6
  и доказательствами.
7
7
 
8
- > Статус: `0.1.0-alpha.18` — предварительный выпуск (alpha). Договоры
8
+ > Статус: `0.1.0-alpha.19` — предварительный выпуск (alpha). Договоры
9
9
  > `v1alpha1` могут измениться между alpha-версиями; закрепляйте точную версию.
10
10
  > Файл → ревью плана → кандидат → интеграция → принятие → результат JSON, с
11
11
  > ограниченным восстановлением, повторным использованием кандидата, бюджетами,
@@ -28,7 +28,7 @@ Headless-движок исполнения инженерных поручени
28
28
  Из npm — точная версия (воспроизводимо) или последняя alpha по тегу:
29
29
 
30
30
  ```sh
31
- npm install --save-exact devcontour-harness@0.1.0-alpha.18 # точная версия
31
+ npm install --save-exact devcontour-harness@0.1.0-alpha.19 # точная версия
32
32
  # или: npm install --save-exact devcontour-harness@alpha # текущая alpha
33
33
  npx harness --version
34
34
  ```
@@ -123,6 +123,16 @@ PASS» нет. Адаптеры хоста реализуют порты `Execut
123
123
  `devcontour-harness/schemas/<имя>.schema.json`. Декларации используют типы
124
124
  Node.js: TypeScript-потребителю нужен `@types/node` (или `skipLibCheck`).
125
125
 
126
+ ## Выпуск 0.1.0-alpha.19
127
+
128
+ Провал gate интеграции после зелёного кандидата повторяется один раз
129
+ (`recheck`; контрпример свойства или корпуса — нет); области gates до
130
+ `GATE_SCOPE_LIMIT`; дешевле такты очереди. Песочница: каталоги IDE внутри
131
+ `node_modules` открыты, запреты `.git` явные, git хоста сверяет checkout
132
+ (`hostGitArgs`) и снимает исполняемые настройки, защищённые пути сверяются
133
+ после gate, класс `environment` — только по журналу ядра. Подробно и с
134
+ известными пробелами — [заметки выпуска](docs/release-notes/0.1.0-alpha.19.md).
135
+
126
136
  ## Выпуск 0.1.0-alpha.18
127
137
 
128
138
  `runGatesAt` исполняет gates с предпосылками профиля: явный `dependsOn` или,
@@ -546,6 +546,16 @@ executable. Live smoke — отдельный opt-in с явно заданны
546
546
  прежнего владельца, истёкший lease и второй controller ничего не меняют.
547
547
  - **Точные SHA.** PASS gate и ревью относится к конкретному candidate или
548
548
  integration SHA; доказательство другой фазы или другого SHA не засчитывается.
549
+ - **Повтор провала интеграции.** Если собственный gate поручения прошёл на
550
+ кандидате, но упал на integration, controller один раз на кандидата повторяет
551
+ его на том же integration SHA до того, как засчитать отказ `gate`. Обе записи
552
+ остаются; у повтора `recheck: {of, outcome}` — ссылка на первую запись и
553
+ исход: `flaky` (повтор прошёл, первый провал вызван средой, например бюджетом
554
+ времени под нагрузкой) или `confirmed` (отказ засчитывается как прежде). Лог
555
+ повтора — в `integration/recheck/`; замер — стадия `integration-recheck:<gate>`.
556
+ Провал, в отчёте которого названо проваленное свойство или случай корпуса,
557
+ не повторяется: это найденный контрпример, и удачный второй запуск
558
+ недетерминированной проверки его не отменяет.
549
559
  - **Атомарная приёмка.** Продвижение target ref — compare-and-swap внутри
550
560
  транзакции приёмки. Чужой коммит в target ref не теряется: попытка начнётся
551
561
  от новой базы. Падение между update-ref и записью принятия восстанавливается
@@ -555,6 +565,44 @@ executable. Live smoke — отдельный opt-in с явно заданны
555
565
  - **Изоляция.** Режим `os` реально ограничивает процессы writer, reviewer и
556
566
  gates; без механизма песочницы команда не запускается — тихого перехода к
557
567
  запуску без неё нет. Зависшие процессы останавливаются вместе с потомками.
568
+ Обязательные запреты записи sandbox-runtime (конфигурация shell и git,
569
+ `.git/hooks`, `.mcp.json`, `.claude/commands|agents`, каталоги IDE)
570
+ действуют на весь checkout проверки, а не только на её рабочий каталог.
571
+ Исключение одно: каталоги IDE (`.idea`, `.vscode`) внутри `node_modules`
572
+ открыты — пакеты их публикуют, а IDE дерево зависимостей как конфигурацию
573
+ проекта не открывает. На Linux srt пропускает `node_modules` сам; на macOS
574
+ srt запускается из каталога проверки, а запреты задаются явно: каталоги IDE
575
+ закрыты в корне и в каждом подкаталоге существующих каталогов до глубины 3
576
+ (кроме `node_modules`), прочие имена — на любой глубине. Новый каталог
577
+ глубже существующего уровня этим не закрыт — граница подхода (srt на Linux
578
+ закрывает только уже существующие пути). Файл `.git` рабочего дерева,
579
+ перенаправления обычного `.git` (`commondir`, `gitdir`, `config.worktree`) и
580
+ вложенные `.git` на любой глубине тоже закрыты (на Linux — только
581
+ существующий файл `.git`). Дерево больше предела перечисления — отказ
582
+ проверки классом `environment`, а не молча урезанные запреты.
583
+ - **Сверка защищённых путей.** Запреты песочницы проверяют только путь
584
+ операции: каталог, собранный в `node_modules` и перенесённый в дерево
585
+ проекта, ими не закрыт. До и после каждого gate и вокруг работы исполнителя
586
+ сравнивается снимок защищённых имён вне `node_modules` (каталоги IDE,
587
+ конфигурация shell и git, `.claude`, `.git`); новое или изменённое — класс
588
+ `scope-violation` с путями в сообщении.
589
+ - **Классификация отказов песочницы.** Отказ — класс `environment` только
590
+ тогда, когда журнал ядра macOS подтвердил отказ обязательного запрета внутри
591
+ дерева проверки: команда начинается с уникального токена, и записи srt этой
592
+ проверки отличаются от чужих. Текст вывода свидетельством не служит, запись
593
+ вне дерева — ошибка кода (`gate`). На Linux такого свидетельства нет, и
594
+ отказ остаётся `gate`.
595
+ - **Git хоста над деревом проверки.** Каждый вызов git хоста сначала сверяет,
596
+ куда git придёт: файл `.git` дерева, созданного `worktree add`, не изменён;
597
+ gitdir и общий каталог лежат вне дерева и согласованы; `commondir`/`gitdir`
598
+ в обычном `.git` и вложенный репозиторий внутри известного дерева — отказ
599
+ (`scope-violation` в gate). Поверх — `-c core.fsmonitor=false -c
600
+ core.hooksPath=/dev/null -c protocol.ext.allow=never`: hooks репозитория при
601
+ коммитах harness не исполняются.
602
+ - **Чего ещё нет.** Регрессионных тестов в песочнице для запретов `.git`, для
603
+ сверки после переноса каталога и для отказа по пределу перечисления пока
604
+ нет: эти пути покрыты только тестами SBX01–SBX05 и модульными тестами
605
+ `git-trust`.
558
606
  - **Секреты.** Значения секретов маскируются во всём сохраняемом: логах,
559
607
  артефактах ревью, evidence, событиях и результате.
560
608
  - **Бюджеты.** Проверяются до вызова модели в той же транзакции, что и запись
package/docs/contracts.md CHANGED
@@ -615,6 +615,9 @@ manifestDigest, impact?, evidence[], error?}`. Смена любого SHA де
615
615
  `moveBase`/`fencedRefMove`, `runGate`/`runCheck` (и разбор отчёта
616
616
  `junitSummary`, `prepareReportPath`, `sandboxBlockedHosts`), `unprovenReason`,
617
617
  `ReviewProbes`, маскирование (`redactor`, `redactDeep`, `redactError`),
618
+ git хоста над checkout с чужим кодом (`hostGitArgs`, `assertTrustedCheckout`,
619
+ `UntrustedCheckoutError`, `gitHardening`, учёт деревьев `rememberWorktree`/
620
+ `forgetWorktree`/`worktreeKey`/`worktreeAddPath`, `resetGitTrust` для тестов),
618
621
  корпус (`CorpusStore`, подтверждение случая `submitCase` с `ConfirmContext`),
619
622
  ограниченный разбор по свойству (`analyzeRecovery`, `applyCases`,
620
623
  `recoveryBrief`; назначение хоста — модель и профиль ведущего — передаётся
@@ -13,7 +13,7 @@
13
13
  ```sh
14
14
  mkdir harness-demo && cd harness-demo
15
15
  npm init -y >/dev/null
16
- npm install --save-exact devcontour-harness@0.1.0-alpha.18
16
+ npm install --save-exact devcontour-harness@0.1.0-alpha.19
17
17
  npx harness --version
18
18
  npx harness doctor
19
19
  ```
@@ -0,0 +1,96 @@
1
+ # devcontour-harness 0.1.0-alpha.19
2
+
3
+ Меньше трения в цикле исполнения и жёстче граница песочницы: провал
4
+ интеграции, вызванный средой, повторяется один раз, установка зависимостей
5
+ не падает на каталогах IDE внутри `node_modules`, а git хоста не исполняет
6
+ конфигурацию, которую записал чужой код. Включает всё из
7
+ [0.1.0-alpha.18](0.1.0-alpha.18.md). `maxAttempts`, правила находок и
8
+ принятие target ref не менялись.
9
+
10
+ ## Что нового
11
+
12
+ ### Цикл исполнения
13
+
14
+ - **Ограниченный повтор провала интеграции.** Собственный gate поручения,
15
+ прошедший на кандидате и упавший на integration, повторяется один раз на
16
+ кандидата на том же integration SHA, прежде чем засчитать отказ `gate`.
17
+ Обе записи остаются; у повтора `recheck: {of, outcome}` — ссылка на первую
18
+ запись и исход `flaky` (повтор прошёл) или `confirmed` (провал
19
+ засчитывается как прежде). Лог повтора — в `integration/recheck/`, замер —
20
+ стадия `integration-recheck:<gate>`. Удачный повтор принимает кандидата без
21
+ новой реализации. Кандидат, переиспользованный следующей попыткой, второго
22
+ повтора не получает; движок отклоняет второй повтор в попытке и исход,
23
+ не согласованный с записью.
24
+ - **Контрпример не повторяется.** Провал, в отчёте которого названо
25
+ проваленное свойство или случай корпуса, не повторяется: удачный второй
26
+ запуск недетерминированной проверки не отменяет найденного входа.
27
+ - **Шире области gates.** Область gates задачи, поручения, потребителя и
28
+ основания ревью вмещает до `GATE_SCOPE_LIMIT` (100, предел gates профиля)
29
+ вместо 20; превышение — понятный отказ с величиной и исправлением.
30
+ Константа экспортируется.
31
+ - **Дешевле такты.** `expire()` без истёкших попыток не открывает транзакцию
32
+ записи; такт очереди читает активные попытки через необязательный быстрый
33
+ путь `ExecutionView.activeAttempts`. Завершённое задание workflow сообщает
34
+ израсходованные попытки стадий, а не сброшенный счётчик.
35
+
36
+ ### Песочница
37
+
38
+ - **Каталоги IDE внутри `node_modules` открыты.** Пакеты публикуют `.idea/`,
39
+ и установка в чистом дереве падала с EPERM при любом коде. На macOS srt
40
+ запускается из служебного каталога проверки, а обязательные запреты
41
+ задаются явно на весь checkout: конфигурация shell, git, MCP и Claude,
42
+ `.git/hooks`, `.git/config` — на любой глубине; `.idea`/`.vscode` — в
43
+ корне и в каждом существующем подкаталоге до глубины 3, кроме
44
+ `node_modules`. Linux не меняется (srt пропускает `node_modules` сам).
45
+ - **Явные запреты `.git`.** На macOS закрыты файл `.git` рабочего дерева,
46
+ перенаправления обычного `.git` (`commondir`, `gitdir`, `config.worktree`)
47
+ и вложенные `.git` на любой глубине; на Linux — существующий файл `.git`.
48
+ Дерево больше предела перечисления — отказ класса `environment`, а не
49
+ молча урезанные запреты.
50
+ - **Git хоста над деревом проверки (`git-trust`).** Каждый вызов git хоста
51
+ сначала сверяет, куда git придёт: файл `.git` дерева, созданного
52
+ `worktree add`, не изменён; gitdir и общий каталог лежат вне дерева и
53
+ согласованы. Перенаправленный `gitdir`/`commondir` в обычном `.git` и
54
+ вложенный репозиторий внутри известного дерева — отказ
55
+ (`UntrustedCheckoutError`, в gate — `scope-violation`). Поверх —
56
+ `-c core.fsmonitor=false -c core.hooksPath=/dev/null -c protocol.ext.allow=never`:
57
+ hooks репозитория при коммитах harness не исполняются. Экспорт для хоста:
58
+ `hostGitArgs`, `assertTrustedCheckout`, `UntrustedCheckoutError`,
59
+ `gitHardening`, `rememberWorktree`, `forgetWorktree`, `worktreeKey`,
60
+ `worktreeAddPath`, `resetGitTrust` (для тестов).
61
+ - **Сверка защищённых путей после gate.** Запреты песочницы видят только
62
+ путь операции, и каталог, собранный в `node_modules` и перенесённый в
63
+ дерево проекта, ими не закрыт. Вокруг каждого gate и работы исполнителя
64
+ сравнивается снимок защищённых имён вне `node_modules`; новое или
65
+ изменённое — `scope-violation` с путями в сообщении.
66
+ - **Класс `environment` — только по журналу ядра.** Отказ песочницы
67
+ классифицируется как сбой среды, только когда журнал ядра macOS подтвердил
68
+ отказ обязательного запрета внутри дерева проверки (команда проверки
69
+ помечена уникальным токеном). Текст вывода (EPERM) свидетельством не
70
+ служит; запись вне дерева и любой отказ на Linux остаются `gate`.
71
+
72
+ ## Известные пробелы
73
+
74
+ - Регрессионные тесты в настоящей песочнице для запретов `.git`, для сверки
75
+ защищённых путей после переноса каталога и для отказа по пределу
76
+ перечисления не написаны (решение владельца проекта). Эти пути покрыты
77
+ тестами SBX01–SBX05 и модульными тестами `git-trust`.
78
+ - Запреты на Linux и запуск/остановка наблюдения за журналом ядра тестами не
79
+ покрыты.
80
+ - Поэтому два мутанта этих путей (D1, D2) выживают.
81
+
82
+ ## Совместимость
83
+
84
+ Формат состояния: новое необязательное поле `recheck` у evidence gate и в
85
+ событии `attempt.gate`, области gates длиннее 20. Прежние версии
86
+ отклоняют такое состояние строгой схемой → откат через копию. Хост, у
87
+ которого хуки gate или writer меняют защищённые имена, получит
88
+ `scope-violation`. Матрица — [upgrade](../upgrade.md).
89
+
90
+ ## Проверка выпуска
91
+
92
+ Затронутые тесты (`runner`, `engine`, `repair`, `planning-schema`,
93
+ `cli-h3-contract`, `probe-gates`, `review-inspection-runner`, `git-trust`,
94
+ `sandbox-install`, `boundaries`), lint, typecheck и `schemas:check`. Полный
95
+ `npm run check` перед публикацией выполняет ведущий. Живые модели не
96
+ вызывались.
package/docs/upgrade.md CHANGED
@@ -21,22 +21,23 @@
21
21
 
22
22
  ## Матрица форматов
23
23
 
24
- | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Читает 0.1.0-alpha.7 | Читает 0.1.0-alpha.8 | Читает 0.1.0-alpha.9 | Читает 0.1.0-alpha.10 | Читает 0.1.0-alpha.11 | Читает 0.1.0-alpha.12 | Читает 0.1.0-alpha.13 | Читает 0.1.0-alpha.14 | Читает 0.1.0-alpha.15 | Читает 0.1.0-alpha.16 | Читает 0.1.0-alpha.17 | Читает 0.1.0-alpha.18 | Пишет поверх | Примечание |
25
- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------ | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | --------------------- | --------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
- | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
- | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
- | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
- | alpha.2 после штатной остановки controller | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
- | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
- | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
- | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
- | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
- | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | да | да | да | да | да | да | да | да | да | да | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
- | alpha.14 с договором проверки `reference` или `gates` обязательства | не проверялось | не проверялось | не проверялось | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | да | да | да | да | да | — | контракт хранится строгой схемой (`contract-documents`): прежние версии новых полей `gates`, `verification`, `reference` не знают → откат через копию |
36
- | alpha.17 с `interrupted`, `inputClasses`, `classified`, `neutralFailures`, `alreadySatisfied` в evidence ревью или классом отказа `unfinished`/`needs-lead` | не проверялось | не проверялось | не проверялось | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | да | да | — | попытка (`interrupted`, `inputClasses`, `classified`), поручение (`neutralFailures`), `alreadySatisfied` evidence ревью и классы отказа `unfinished`, `needs-lead` — новые значения строгих схем; прежние версии их отклоняют → откат через копию |
37
- | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
38
-
39
- Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. alpha.8, alpha.9, alpha.10, alpha.11, alpha.12 и alpha.13 формат состояния не меняют и читают всё, что читает alpha.7; alpha.14 и alpha.15 (локальные, не опубликованы) и alpha.16 читают всё, что читает alpha.13, и добавляют необязательные поля договора проверки (строка alpha.14). alpha.17 читает всё, что читает alpha.16, и добавляет необязательные поля попытки, поручения и evidence ревью и классы отказа `unfinished` и `needs-lead` (строка alpha.17); alpha.18 формат состояния не меняет и читает всё, что читает alpha.17. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
24
+ | Состояние, записанное… | Читает 0.1.0-alpha.1 | Читает 0.1.0-alpha.2 | Читает 0.1.0-alpha.6 | Читает 0.1.0-alpha.7 | Читает 0.1.0-alpha.8 | Читает 0.1.0-alpha.9 | Читает 0.1.0-alpha.10 | Читает 0.1.0-alpha.11 | Читает 0.1.0-alpha.12 | Читает 0.1.0-alpha.13 | Читает 0.1.0-alpha.14 | Читает 0.1.0-alpha.15 | Читает 0.1.0-alpha.16 | Читает 0.1.0-alpha.17 | Читает 0.1.0-alpha.18 | Читает 0.1.0-alpha.19 | Пишет поверх | Примечание |
25
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------ | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | ----------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
+ | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
+ | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
+ | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
+ | alpha.2 после штатной остановки controller | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
+ | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
+ | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
+ | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | да | да | да | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
+ | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
+ | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | да | да | да | да | да | да | да | да | да | да | да | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
+ | alpha.14 с договором проверки `reference` или `gates` обязательства | не проверялось | не проверялось | не проверялось | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | да | да | да | да | да | да | — | контракт хранится строгой схемой (`contract-documents`): прежние версии новых полей `gates`, `verification`, `reference` не знают → откат через копию |
36
+ | alpha.17 с `interrupted`, `inputClasses`, `classified`, `neutralFailures`, `alreadySatisfied` в evidence ревью или классом отказа `unfinished`/`needs-lead` | не проверялось | не проверялось | не проверялось | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | да | да | да | — | попытка (`interrupted`, `inputClasses`, `classified`), поручение (`neutralFailures`), `alreadySatisfied` evidence ревью и классы отказа `unfinished`, `needs-lead` — новые значения строгих схем; прежние версии их отклоняют → откат через копию |
37
+ | alpha.19 с `recheck` в evidence gate или областью gates длиннее 20 | не проверялось | не проверялось | не проверялось | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | **нет** (строгая схема) | да | — | evidence gate и событие `attempt.gate` (`recheck`), области gates поручения, задачи, потребителя и `alreadySatisfied` до 100 — новые значения строгих схем; прежние версии их отклоняют → откат через копию |
38
+ | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
39
+
40
+ Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. alpha.8, alpha.9, alpha.10, alpha.11, alpha.12 и alpha.13 формат состояния не меняют и читают всё, что читает alpha.7; alpha.14 и alpha.15 (локальные, не опубликованы) и alpha.16 читают всё, что читает alpha.13, и добавляют необязательные поля договора проверки (строка alpha.14). alpha.17 читает всё, что читает alpha.16, и добавляет необязательные поля попытки, поручения и evidence ревью и классы отказа `unfinished` и `needs-lead` (строка alpha.17); alpha.18 формат состояния не меняет и читает всё, что читает alpha.17. alpha.19 читает всё, что читает alpha.18, и добавляет необязательное поле `recheck` evidence gate и области gates до 100 (строка alpha.19). Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
40
41
  формата до записи проверяют тесты совместимости пакета
41
42
  <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
42
43
 
package/lib/cli.js CHANGED
@@ -18,6 +18,7 @@ import { planCheck, planRequestOf } from "./planning/check.js";
18
18
  import { checkJsonBytes } from "./catalog/bytes.js";
19
19
  import { isSharedOperation, sharedOperations } from "./catalog/shared.js";
20
20
  import { harnessCapabilities } from "./catalog/harness.js";
21
+ import { hostGitArgs } from "./environment/process.js";
21
22
  import { FileContextSource, FileResultSink, FileWorkSource } from "./connectors/files.js";
22
23
  const usage = `harness ${HARNESS_VERSION}
23
24
 
@@ -124,7 +125,12 @@ const reachableIn = (b, repositoryId, sha) => {
124
125
  if (!repo)
125
126
  return false;
126
127
  try {
127
- execFileSync('git', ['merge-base', '--is-ancestor', sha, repo.targetRef], {
128
+ execFileSync('git', hostGitArgs(resolve(b.baseDir, repo.path), [
129
+ 'merge-base',
130
+ '--is-ancestor',
131
+ sha,
132
+ repo.targetRef,
133
+ ]), {
128
134
  cwd: resolve(b.baseDir, repo.path),
129
135
  stdio: 'ignore',
130
136
  });
@@ -598,8 +598,8 @@ export declare const statusDataSchema: z.ZodObject<{
598
598
  }, z.core.$strict>>;
599
599
  budgets: z.ZodOptional<z.ZodObject<{
600
600
  onUnknown: z.ZodEnum<{
601
- stop: "stop";
602
601
  continue: "continue";
602
+ stop: "stop";
603
603
  }>;
604
604
  budgets: z.ZodArray<z.ZodObject<{
605
605
  scope: z.ZodObject<{
@@ -710,6 +710,13 @@ export declare const eventsDataSchema: z.ZodObject<{
710
710
  passed: z.ZodBoolean;
711
711
  evidenceId: z.ZodString;
712
712
  consumer: z.ZodOptional<z.ZodString>;
713
+ recheck: z.ZodOptional<z.ZodObject<{
714
+ of: z.ZodString;
715
+ outcome: z.ZodEnum<{
716
+ flaky: "flaky";
717
+ confirmed: "confirmed";
718
+ }>;
719
+ }, z.core.$strict>>;
713
720
  }, z.core.$strict> | z.ZodObject<{
714
721
  phase: z.ZodEnum<{
715
722
  candidate: "candidate";
@@ -965,6 +972,13 @@ export declare const eventsDataSchema: z.ZodObject<{
965
972
  passed: z.ZodBoolean;
966
973
  evidenceId: z.ZodString;
967
974
  consumer: z.ZodOptional<z.ZodString>;
975
+ recheck: z.ZodOptional<z.ZodObject<{
976
+ of: z.ZodString;
977
+ outcome: z.ZodEnum<{
978
+ flaky: "flaky";
979
+ confirmed: "confirmed";
980
+ }>;
981
+ }, z.core.$strict>>;
968
982
  }, z.core.$strict> | z.ZodObject<{
969
983
  phase: z.ZodEnum<{
970
984
  candidate: "candidate";
@@ -2273,8 +2287,8 @@ export declare const cliDataSchemas: {
2273
2287
  }, z.core.$strict>>;
2274
2288
  budgets: z.ZodOptional<z.ZodObject<{
2275
2289
  onUnknown: z.ZodEnum<{
2276
- stop: "stop";
2277
2290
  continue: "continue";
2291
+ stop: "stop";
2278
2292
  }>;
2279
2293
  budgets: z.ZodArray<z.ZodObject<{
2280
2294
  scope: z.ZodObject<{
@@ -2385,6 +2399,13 @@ export declare const cliDataSchemas: {
2385
2399
  passed: z.ZodBoolean;
2386
2400
  evidenceId: z.ZodString;
2387
2401
  consumer: z.ZodOptional<z.ZodString>;
2402
+ recheck: z.ZodOptional<z.ZodObject<{
2403
+ of: z.ZodString;
2404
+ outcome: z.ZodEnum<{
2405
+ flaky: "flaky";
2406
+ confirmed: "confirmed";
2407
+ }>;
2408
+ }, z.core.$strict>>;
2388
2409
  }, z.core.$strict> | z.ZodObject<{
2389
2410
  phase: z.ZodEnum<{
2390
2411
  candidate: "candidate";
@@ -2640,6 +2661,13 @@ export declare const cliDataSchemas: {
2640
2661
  passed: z.ZodBoolean;
2641
2662
  evidenceId: z.ZodString;
2642
2663
  consumer: z.ZodOptional<z.ZodString>;
2664
+ recheck: z.ZodOptional<z.ZodObject<{
2665
+ of: z.ZodString;
2666
+ outcome: z.ZodEnum<{
2667
+ flaky: "flaky";
2668
+ confirmed: "confirmed";
2669
+ }>;
2670
+ }, z.core.$strict>>;
2643
2671
  }, z.core.$strict> | z.ZodObject<{
2644
2672
  phase: z.ZodEnum<{
2645
2673
  candidate: "candidate";
@@ -79,6 +79,13 @@ export declare const eventPayloads: {
79
79
  passed: z.ZodBoolean;
80
80
  evidenceId: z.ZodString;
81
81
  consumer: z.ZodOptional<z.ZodString>;
82
+ recheck: z.ZodOptional<z.ZodObject<{
83
+ of: z.ZodString;
84
+ outcome: z.ZodEnum<{
85
+ flaky: "flaky";
86
+ confirmed: "confirmed";
87
+ }>;
88
+ }, z.core.$strict>>;
82
89
  }, z.core.$strict>;
83
90
  readonly 'attempt.review': z.ZodObject<{
84
91
  phase: z.ZodEnum<{
@@ -381,6 +388,13 @@ export declare const runEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
381
388
  passed: z.ZodBoolean;
382
389
  evidenceId: z.ZodString;
383
390
  consumer: z.ZodOptional<z.ZodString>;
391
+ recheck: z.ZodOptional<z.ZodObject<{
392
+ of: z.ZodString;
393
+ outcome: z.ZodEnum<{
394
+ flaky: "flaky";
395
+ confirmed: "confirmed";
396
+ }>;
397
+ }, z.core.$strict>>;
384
398
  }, z.core.$strict> | z.ZodObject<{
385
399
  phase: z.ZodEnum<{
386
400
  candidate: "candidate";
@@ -636,6 +650,13 @@ export declare const runEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
636
650
  passed: z.ZodBoolean;
637
651
  evidenceId: z.ZodString;
638
652
  consumer: z.ZodOptional<z.ZodString>;
653
+ recheck: z.ZodOptional<z.ZodObject<{
654
+ of: z.ZodString;
655
+ outcome: z.ZodEnum<{
656
+ flaky: "flaky";
657
+ confirmed: "confirmed";
658
+ }>;
659
+ }, z.core.$strict>>;
639
660
  }, z.core.$strict> | z.ZodObject<{
640
661
  phase: z.ZodEnum<{
641
662
  candidate: "candidate";
@@ -54,6 +54,13 @@ export const eventPayloads = {
54
54
  passed: z.boolean(),
55
55
  evidenceId: z.string().min(1).max(200),
56
56
  consumer: idSchema.optional(),
57
+ /** Повтор провала интеграции: повторённая запись и исход. */
58
+ recheck: z
59
+ .strictObject({
60
+ of: z.string().min(1).max(200),
61
+ outcome: z.enum(['flaky', 'confirmed']),
62
+ })
63
+ .optional(),
57
64
  }),
58
65
  'attempt.review': z.strictObject({
59
66
  phase,
@@ -276,6 +276,13 @@ export declare const evidenceRecordSchema: z.ZodObject<{
276
276
  digest: z.ZodString;
277
277
  summary: z.ZodString;
278
278
  consumer: z.ZodOptional<z.ZodString>;
279
+ recheck: z.ZodOptional<z.ZodObject<{
280
+ of: z.ZodString;
281
+ outcome: z.ZodEnum<{
282
+ flaky: "flaky";
283
+ confirmed: "confirmed";
284
+ }>;
285
+ }, z.core.$strict>>;
279
286
  inspection: z.ZodOptional<z.ZodObject<{
280
287
  mode: z.ZodEnum<{
281
288
  commands: "commands";
@@ -367,9 +374,9 @@ export declare const evidenceRecordSchema: z.ZodObject<{
367
374
  issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
368
375
  path: z.ZodString;
369
376
  reason: z.ZodEnum<{
377
+ schema: "schema";
370
378
  unknown: "unknown";
371
379
  missing: "missing";
372
- schema: "schema";
373
380
  duplicate: "duplicate";
374
381
  "not-open": "not-open";
375
382
  "approved-with-blocking": "approved-with-blocking";
@@ -837,6 +844,13 @@ export declare const attemptStateSchema: z.ZodObject<{
837
844
  digest: z.ZodString;
838
845
  summary: z.ZodString;
839
846
  consumer: z.ZodOptional<z.ZodString>;
847
+ recheck: z.ZodOptional<z.ZodObject<{
848
+ of: z.ZodString;
849
+ outcome: z.ZodEnum<{
850
+ flaky: "flaky";
851
+ confirmed: "confirmed";
852
+ }>;
853
+ }, z.core.$strict>>;
840
854
  inspection: z.ZodOptional<z.ZodObject<{
841
855
  mode: z.ZodEnum<{
842
856
  commands: "commands";
@@ -928,9 +942,9 @@ export declare const attemptStateSchema: z.ZodObject<{
928
942
  issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
929
943
  path: z.ZodString;
930
944
  reason: z.ZodEnum<{
945
+ schema: "schema";
931
946
  unknown: "unknown";
932
947
  missing: "missing";
933
- schema: "schema";
934
948
  duplicate: "duplicate";
935
949
  "not-open": "not-open";
936
950
  "approved-with-blocking": "approved-with-blocking";
@@ -3131,6 +3145,13 @@ export declare const executionDocumentSchema: z.ZodObject<{
3131
3145
  digest: z.ZodString;
3132
3146
  summary: z.ZodString;
3133
3147
  consumer: z.ZodOptional<z.ZodString>;
3148
+ recheck: z.ZodOptional<z.ZodObject<{
3149
+ of: z.ZodString;
3150
+ outcome: z.ZodEnum<{
3151
+ flaky: "flaky";
3152
+ confirmed: "confirmed";
3153
+ }>;
3154
+ }, z.core.$strict>>;
3134
3155
  inspection: z.ZodOptional<z.ZodObject<{
3135
3156
  mode: z.ZodEnum<{
3136
3157
  commands: "commands";
@@ -3222,9 +3243,9 @@ export declare const executionDocumentSchema: z.ZodObject<{
3222
3243
  issues: z.ZodOptional<z.ZodArray<z.ZodObject<{
3223
3244
  path: z.ZodString;
3224
3245
  reason: z.ZodEnum<{
3246
+ schema: "schema";
3225
3247
  unknown: "unknown";
3226
3248
  missing: "missing";
3227
- schema: "schema";
3228
3249
  duplicate: "duplicate";
3229
3250
  "not-open": "not-open";
3230
3251
  "approved-with-blocking": "approved-with-blocking";
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { STATE_VERSION } from "./version.js";
3
- import { digestSchema, gitShaSchema, idSchema, isoTimeSchema, relativePathSchema, runtimeNameSchema, trimmedText, } from "./primitives.js";
3
+ import { digestSchema, gitShaSchema, idSchema, isoTimeSchema, relativePathSchema, runtimeNameSchema, trimmedText, gateScopeSchema, } from "./primitives.js";
4
4
  import { workGraphSchema, workOrderSchema } from "./work.js";
5
5
  import { contextPinSchema, contextSnapshotSchema } from "./context.js";
6
6
  import { deliveryStateSchema, jointActiveSchema, jointFailedSchema, jointPassedSchema, } from "./result.js";
@@ -241,6 +241,17 @@ export const evidenceRecordSchema = z.strictObject({
241
241
  summary: z.string(),
242
242
  /** Проверка потребителя: компонент, в дереве которого она выполнена. */
243
243
  consumer: idSchema.optional(),
244
+ /**
245
+ * Повтор провала gate интеграции на том же SHA: `of` — ID повторённой
246
+ * записи; `flaky` — повтор прошёл (первый провал вызван средой), `confirmed`
247
+ * — провал подтвердился.
248
+ */
249
+ recheck: z
250
+ .strictObject({
251
+ of: z.string().min(1).max(200),
252
+ outcome: z.enum(['flaky', 'confirmed']),
253
+ })
254
+ .optional(),
244
255
  inspection: reviewInspectionSchema.optional(),
245
256
  findings: z.array(evidenceFindingSchema).optional(),
246
257
  /** Раунд ревью кода: область истории, вызов, протокол, переходы (docs/review-protocol.md). */
@@ -262,7 +273,7 @@ export const evidenceRecordSchema = z.strictObject({
262
273
  */
263
274
  alreadySatisfied: z
264
275
  .strictObject({
265
- gates: z.array(z.string().min(1).max(200)).min(1).max(20),
276
+ gates: gateScopeSchema(z.string().min(1).max(200)).min(1),
266
277
  evidence: z.string().min(1).max(4000),
267
278
  /** Механическое основание, найденное controller (`alreadySatisfiedVerdict`). */
268
279
  basis: z
@@ -875,6 +886,7 @@ export const workflowJobSchema = z.strictObject({
875
886
  owner: z.string().optional(),
876
887
  inputDigest: digestSchema,
877
888
  stage: z.number().int().min(0),
889
+ /** Попытки текущей стадии; у завершённого задания — все израсходованные попытки стадий. */
878
890
  attempts: z.number().int().min(0),
879
891
  status: z.enum(['queued', 'running', 'failed', 'stale', 'completed']),
880
892
  repairs: z.number().int().min(0).optional(),
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { errorCodes, HarnessError } from "./errors.js";
3
- import { digestSchema, gateRefSchema, idSchema, relativePathPattern, roleIdSchema, } from "./primitives.js";
3
+ import { digestSchema, gateRefSchema, idSchema, relativePathPattern, roleIdSchema, gateScopeSchema, } from "./primitives.js";
4
4
  import { consumerSchema, propertyIdSchema, workGraphSchema } from "./work.js";
5
5
  /**
6
6
  * Draft плана: то, что ведущий агент (или хост) предлагает до проверки и
@@ -35,7 +35,7 @@ export const planDraftTaskShape = {
35
35
  relatedRepositories: z.array(idSchema).optional(),
36
36
  contextPacks: z.array(idSchema).max(30).optional(),
37
37
  writePaths: z.array(draftPathSchema).min(1).max(100).optional(),
38
- gates: z.array(gateRefSchema).max(20).optional(),
38
+ gates: gateScopeSchema(gateRefSchema).optional(),
39
39
  properties: z.array(propertyIdSchema).max(30).optional(),
40
40
  resources: z.array(idSchema).max(20).optional(),
41
41
  repositoryId: z.string().default('main'),
@@ -986,8 +986,8 @@ export declare const executionPolicySchema: z.ZodObject<{
986
986
  timeMs: z.ZodNumber;
987
987
  }, z.core.$strict>]>>;
988
988
  onUnknown: z.ZodDefault<z.ZodEnum<{
989
- stop: "stop";
990
989
  continue: "continue";
990
+ stop: "stop";
991
991
  }>>;
992
992
  }, z.core.$strict>>;
993
993
  prices: z.ZodDefault<z.ZodArray<z.ZodObject<{