devcontour-harness 0.1.0-alpha.5 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -2
- package/docs/compatibility-versions.md +56 -0
- package/docs/compatibility.md +2 -2
- package/docs/connectors.md +323 -0
- package/docs/contracts.md +33 -5
- package/docs/integrations/hindsight.md +148 -0
- package/docs/quickstart.md +1 -1
- package/docs/release-notes/0.1.0-alpha.6.md +238 -0
- package/docs/release-notes/0.1.0-alpha.7.md +71 -0
- package/docs/review-probes.md +199 -0
- package/docs/review-protocol.md +251 -0
- package/docs/toolchains.md +167 -0
- package/docs/turbo-starter.md +269 -0
- package/docs/upgrade.md +77 -24
- package/examples/README.md +1 -0
- package/examples/toolchains/README.md +32 -0
- package/examples/toolchains/go/bad/label/label.go +9 -0
- package/examples/toolchains/go/good/label/label.go +9 -0
- package/examples/toolchains/go/policy.json +79 -0
- package/examples/toolchains/go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/go/repo/_gitignore +1 -0
- package/examples/toolchains/go/repo/go.mod +5 -0
- package/examples/toolchains/go/repo/go.sum +2 -0
- package/examples/toolchains/go/repo/label/label.go +9 -0
- package/examples/toolchains/go/repo/label/label_test.go +27 -0
- package/examples/toolchains/go/repo/label/version.go +9 -0
- package/examples/toolchains/go/task.json +25 -0
- package/examples/toolchains/mixed-go/bad/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/good/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/policy.json +126 -0
- package/examples/toolchains/mixed-go/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-go/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-go/repo/backend/go.mod +3 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label.go +9 -0
- package/examples/toolchains/mixed-go/repo/backend/label/label_test.go +30 -0
- package/examples/toolchains/mixed-go/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-go/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-go/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-go/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-go/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-go/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-go/task.json +25 -0
- package/examples/toolchains/mixed-python/bad/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/bad/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/good/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/good/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/policy.json +121 -0
- package/examples/toolchains/mixed-python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/mixed-python/repo/_gitignore +6 -0
- package/examples/toolchains/mixed-python/repo/backend/.python-version +1 -0
- package/examples/toolchains/mixed-python/repo/backend/pyproject.toml +19 -0
- package/examples/toolchains/mixed-python/repo/backend/src/fx_backend/__init__.py +6 -0
- package/examples/toolchains/mixed-python/repo/backend/tests/test_label.py +11 -0
- package/examples/toolchains/mixed-python/repo/backend/uv.lock +108 -0
- package/examples/toolchains/mixed-python/repo/contract/check.mjs +17 -0
- package/examples/toolchains/mixed-python/repo/contract/label.json +9 -0
- package/examples/toolchains/mixed-python/repo/frontend/package.json +15 -0
- package/examples/toolchains/mixed-python/repo/frontend/pnpm-lock.yaml +22 -0
- package/examples/toolchains/mixed-python/repo/frontend/src/label.js +4 -0
- package/examples/toolchains/mixed-python/repo/frontend/test/label.test.js +11 -0
- package/examples/toolchains/mixed-python/task.json +25 -0
- package/examples/toolchains/npm/bad/src/label.js +4 -0
- package/examples/toolchains/npm/good/src/label.js +4 -0
- package/examples/toolchains/npm/policy.json +82 -0
- package/examples/toolchains/npm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/npm/repo/_gitignore +4 -0
- package/examples/toolchains/npm/repo/package-lock.json +22 -0
- package/examples/toolchains/npm/repo/package.json +13 -0
- package/examples/toolchains/npm/repo/src/label.js +4 -0
- package/examples/toolchains/npm/repo/test/label.test.js +13 -0
- package/examples/toolchains/npm/task.json +25 -0
- package/examples/toolchains/pnpm/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/policy.json +85 -0
- package/examples/toolchains/pnpm/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/pnpm/repo/_gitignore +4 -0
- package/examples/toolchains/pnpm/repo/package.json +10 -0
- package/examples/toolchains/pnpm/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/pnpm/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/pnpm/repo/packages/label/package.json +14 -0
- package/examples/toolchains/pnpm/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/pnpm/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/pnpm/repo/pnpm-lock.yaml +29 -0
- package/examples/toolchains/pnpm/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/pnpm/task.json +25 -0
- package/examples/toolchains/python/bad/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/good/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/policy.json +80 -0
- package/examples/toolchains/python/repo/.python-version +1 -0
- package/examples/toolchains/python/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/python/repo/_gitignore +5 -0
- package/examples/toolchains/python/repo/pyproject.toml +19 -0
- package/examples/toolchains/python/repo/src/fx_label/__init__.py +6 -0
- package/examples/toolchains/python/repo/tests/test_label.py +13 -0
- package/examples/toolchains/python/repo/uv.lock +108 -0
- package/examples/toolchains/python/task.json +25 -0
- package/examples/toolchains/turbo/bad/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/good/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/policy.json +110 -0
- package/examples/toolchains/turbo/repo/REQUIREMENTS.md +9 -0
- package/examples/toolchains/turbo/repo/_gitignore +4 -0
- package/examples/toolchains/turbo/repo/package.json +13 -0
- package/examples/toolchains/turbo/repo/packages/checker/check.mjs +5 -0
- package/examples/toolchains/turbo/repo/packages/checker/package.json +9 -0
- package/examples/toolchains/turbo/repo/packages/label/package.json +14 -0
- package/examples/toolchains/turbo/repo/packages/label/src/label.js +4 -0
- package/examples/toolchains/turbo/repo/packages/label/test/label.test.js +13 -0
- package/examples/toolchains/turbo/repo/pnpm-lock.yaml +94 -0
- package/examples/toolchains/turbo/repo/pnpm-workspace.yaml +2 -0
- package/examples/toolchains/turbo/repo/turbo.json +7 -0
- package/examples/toolchains/turbo/task.json +25 -0
- package/examples/turbo-starter/README.md +16 -0
- package/examples/turbo-starter/changes/issued-at-consumer-only.patch +12 -0
- package/examples/turbo-starter/changes/issued-at.patch +130 -0
- package/examples/turbo-starter/cleanup.mjs +58 -0
- package/examples/turbo-starter/graph.json +32 -0
- package/examples/turbo-starter/harness/acceptance/db-isolation.test.mjs +97 -0
- package/examples/turbo-starter/harness/acceptance/env-probe.test.mjs +75 -0
- package/examples/turbo-starter/harness/acceptance/generation.test.mjs +54 -0
- package/examples/turbo-starter/harness/acceptance/issued-at.test.mjs +43 -0
- package/examples/turbo-starter/harness/acceptance/lifecycle-probe.test.mjs +47 -0
- package/examples/turbo-starter/harness/compose-bridge.mjs +593 -0
- package/examples/turbo-starter/harness/compose.acceptance.yml +23 -0
- package/examples/turbo-starter/harness/env/backend.env.template +7 -0
- package/examples/turbo-starter/harness/env/compose.env.template +6 -0
- package/examples/turbo-starter/policy.json +109 -0
- package/examples/turbo-starter/policy.lifecycle.json +114 -0
- package/examples/turbo-starter/setup.mjs +352 -0
- package/lib/agents/cli-adapter.js +10 -1
- package/lib/agents/inspect.d.ts +56 -2
- package/lib/agents/inspect.js +264 -14
- package/lib/agents/review-probe.d.ts +229 -7
- package/lib/agents/review-probe.js +830 -72
- package/lib/agents/schemas.d.ts +3 -1
- package/lib/agents/schemas.js +7 -3
- package/lib/cli.js +167 -7
- package/lib/contracts/cli.d.ts +820 -11
- package/lib/contracts/cli.js +243 -4
- package/lib/contracts/errors.d.ts +3 -1
- package/lib/contracts/errors.js +2 -0
- package/lib/contracts/events.d.ts +37 -5
- package/lib/contracts/events.js +9 -0
- package/lib/contracts/execution.d.ts +389 -15
- package/lib/contracts/execution.js +76 -4
- package/lib/contracts/failure.js +5 -0
- package/lib/contracts/index.d.ts +1 -0
- package/lib/contracts/index.js +1 -0
- package/lib/contracts/policy.d.ts +15 -0
- package/lib/contracts/policy.js +10 -0
- package/lib/contracts/ports.d.ts +50 -0
- package/lib/contracts/profiles.d.ts +14 -0
- package/lib/contracts/profiles.js +4 -0
- package/lib/contracts/published.d.ts +279 -8
- package/lib/contracts/result.d.ts +171 -0
- package/lib/contracts/result.js +39 -1
- package/lib/contracts/review.d.ts +231 -0
- package/lib/contracts/review.js +326 -2
- package/lib/contracts/toolchain.d.ts +96 -0
- package/lib/contracts/toolchain.js +89 -0
- package/lib/control/doctor.d.ts +16 -1
- package/lib/control/doctor.js +45 -3
- package/lib/control/harness.d.ts +52 -6
- package/lib/control/harness.js +28 -1
- package/lib/control/recovery.d.ts +3 -1
- package/lib/control/review.js +23 -11
- package/lib/engine/budget.d.ts +3 -3
- package/lib/engine/classify.js +1 -0
- package/lib/engine/engine.d.ts +67 -17
- package/lib/engine/prompt.d.ts +29 -1
- package/lib/engine/prompt.js +57 -0
- package/lib/engine/property-recovery.js +2 -0
- package/lib/engine/result.js +52 -0
- package/lib/engine/reuse.js +6 -1
- package/lib/engine/runner.d.ts +25 -0
- package/lib/engine/runner.js +500 -123
- package/lib/environment/lifecycle.js +6 -0
- package/lib/environment/live-calls.js +3 -2
- package/lib/environment/os-tools.d.ts +16 -0
- package/lib/environment/os-tools.js +35 -0
- package/lib/environment/process.d.ts +8 -0
- package/lib/environment/process.js +13 -6
- package/lib/integrations/connector/commands.d.ts +358 -0
- package/lib/integrations/connector/commands.js +428 -0
- package/lib/integrations/connector/config.d.ts +215 -0
- package/lib/integrations/connector/config.js +214 -0
- package/lib/integrations/connector/delivery.d.ts +107 -0
- package/lib/integrations/connector/delivery.js +591 -0
- package/lib/integrations/connector/fake.d.ts +291 -0
- package/lib/integrations/connector/fake.js +786 -0
- package/lib/integrations/connector/history.d.ts +124 -0
- package/lib/integrations/connector/history.js +497 -0
- package/lib/integrations/connector/index.d.ts +16 -0
- package/lib/integrations/connector/index.js +16 -0
- package/lib/integrations/connector/redact.d.ts +19 -0
- package/lib/integrations/connector/redact.js +91 -0
- package/lib/integrations/connector/registry.d.ts +15 -0
- package/lib/integrations/connector/registry.js +43 -0
- package/lib/integrations/connector/snapshot.d.ts +131 -0
- package/lib/integrations/connector/snapshot.js +422 -0
- package/lib/integrations/connector/source.d.ts +37 -0
- package/lib/integrations/connector/source.js +73 -0
- package/lib/integrations/connector/steps.d.ts +71 -0
- package/lib/integrations/connector/steps.js +188 -0
- package/lib/integrations/connector/store.d.ts +140 -0
- package/lib/integrations/connector/store.js +277 -0
- package/lib/integrations/connector/summary.d.ts +38 -0
- package/lib/integrations/connector/summary.js +59 -0
- package/lib/integrations/connector/testing-worker.d.ts +1 -0
- package/lib/integrations/connector/testing-worker.js +81 -0
- package/lib/integrations/connector/testing.d.ts +205 -0
- package/lib/integrations/connector/testing.js +1615 -0
- package/lib/integrations/connector/transport.d.ts +240 -0
- package/lib/integrations/connector/transport.js +28 -0
- package/lib/integrations/memory/context.d.ts +120 -0
- package/lib/integrations/memory/context.js +239 -0
- package/lib/integrations/memory/index.d.ts +8 -0
- package/lib/integrations/memory/index.js +8 -0
- package/lib/integrations/memory/provider.d.ts +79 -0
- package/lib/integrations/memory/provider.js +22 -0
- package/lib/integrations/memory/sink.d.ts +56 -0
- package/lib/integrations/memory/sink.js +116 -0
- package/lib/integrations/memory/testing.d.ts +44 -0
- package/lib/integrations/memory/testing.js +61 -0
- package/lib/integrations/relay-v1/index.d.ts +122 -0
- package/lib/integrations/relay-v1/index.js +860 -0
- package/lib/integrations/relay-v2/index.d.ts +145 -0
- package/lib/integrations/relay-v2/index.js +752 -0
- package/lib/integrations/relay-v2/process.d.ts +32 -0
- package/lib/integrations/relay-v2/process.js +112 -0
- package/lib/integrations/relay-v2/protocol.d.ts +172 -0
- package/lib/integrations/relay-v2/protocol.js +142 -0
- package/lib/profiles/resolve.d.ts +5 -1
- package/lib/profiles/resolve.js +31 -0
- package/lib/storage/document.d.ts +116 -16
- package/lib/toolchains/index.d.ts +9 -0
- package/lib/toolchains/index.js +9 -0
- package/lib/toolchains/inspect.d.ts +105 -0
- package/lib/toolchains/inspect.js +831 -0
- package/lib/toolchains/manifests.d.ts +67 -0
- package/lib/toolchains/manifests.js +238 -0
- package/lib/toolchains/probe.d.ts +28 -0
- package/lib/toolchains/probe.js +54 -0
- package/lib/toolchains/recipes.d.ts +33 -0
- package/lib/toolchains/recipes.js +51 -0
- package/lib/verification/acceptance.d.ts +16 -0
- package/lib/verification/acceptance.js +16 -0
- package/lib/verification/gates.d.ts +38 -0
- package/lib/verification/gates.js +108 -26
- package/package.json +22 -1
- package/profiles/go-gotestsum.json +21 -0
- package/profiles/npm-ci.json +22 -0
- package/profiles/pnpm-frozen.json +24 -0
- package/profiles/python-uv.json +26 -0
- package/schemas/execution-document.schema.json +345 -4
- package/schemas/execution-policy.schema.json +48 -0
- package/schemas/result-envelope.schema.json +213 -1
- package/schemas/run-event.schema.json +93 -0
- package/schemas/run-result.schema.json +207 -0
- package/schemas/stack-profile.schema.json +43 -0
- package/templates/controller-mode/AGENTS.controller.md +58 -0
- package/templates/controller-mode/README.md +44 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Проверки reviewer (`run_check`) и объявленные gates
|
|
2
|
+
|
|
3
|
+
Reviewer кода, которому профиль разрешает запускать команды, получает MCP-инструмент
|
|
4
|
+
`devcontour_probe.run_check`. Его исполняет controller: песочница ОС, исходники только для
|
|
5
|
+
чтения, предел на команду, общий бюджет проверок и резерв времени на заключение.
|
|
6
|
+
|
|
7
|
+
## Два вида вызова
|
|
8
|
+
|
|
9
|
+
| Вызов | Что исполняется | Куда можно писать |
|
|
10
|
+
| ------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------- |
|
|
11
|
+
| `{"argv": ["node", "-e", "…"]}` | исследовательская команда reviewer, без shell, в корне проверяемого дерева | только собственный `TMPDIR` |
|
|
12
|
+
| `{"gate": "domain"}` | объявленный gate из эффективной политики: его `command`, `cwd`, окружение фазы | только scratch отчёта controller и `TMPDIR` |
|
|
13
|
+
|
|
14
|
+
Других полей нет: `env`, `cwd`, `reportPath`, `write`, `scope` и любые неизвестные поля —
|
|
15
|
+
ошибка протокола (`-32602`), ничего не запускается. Можно передать `timeoutMs`, но он только
|
|
16
|
+
уменьшает предел controller. Неизвестный ID и генеративный gate (`property`) — результат
|
|
17
|
+
`rejected`, бюджет не тратится.
|
|
18
|
+
|
|
19
|
+
Запрос reviewer содержит готовый вызов для каждого объявленного gate, например:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
To run a declared gate, call run_check with exactly {"gate":"domain"}, {"gate":"api"} (one gate per call).
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Что делает controller при `{"gate": "<id>"}`
|
|
26
|
+
|
|
27
|
+
1. Берёт gate из спецификации probe (копия gates поручения из эффективной политики).
|
|
28
|
+
2. Проверяет `cwd`: реальный путь внутри проверяемого дерева, иначе `rejected`.
|
|
29
|
+
3. Если путь отчёта зашит в argv (см. ниже) — не запускает, возвращает ошибку среды с рецептом.
|
|
30
|
+
4. Создаёт каталог evidence `<каталог ревью>/probe-runs/<runId>/` (вне дерева, без перезаписи) и
|
|
31
|
+
уникальный scratch `dc-probe-report-*` во временном каталоге системы.
|
|
32
|
+
5. Выставляет `HARNESS_REPORT_PATH` и `DEVCONTOUR_REPORT_PATH` = `<scratch>/<id>.xml`, `CI=1`;
|
|
33
|
+
у `test` gate ещё `TURBO_FORCE=true` и `GOFLAGS=… -count=1`, как у gate движка.
|
|
34
|
+
6. Запускает команду в песочнице: запись — только в scratch и `TMPDIR`; дерево, `.git`,
|
|
35
|
+
каталог controller и прежние `.reports/*.xml` закрыты на запись.
|
|
36
|
+
7. Читает отчёт: symlink, жёсткая ссылка, путь вне scratch — `unsafe` (не читается); файл старше
|
|
37
|
+
запуска — `stale`; не JUnit или без testcases — `corrupt`; нет файла — `missing`.
|
|
38
|
+
8. Сохраняет замаскированную копию отчёта (`<id>.xml`), журнал (`<id>.log`) и `run.json`, затем
|
|
39
|
+
удаляет scratch. Таймаут и отмена (`notifications/cancelled`) останавливают процессы проверки,
|
|
40
|
+
evidence сохраняется тоже.
|
|
41
|
+
|
|
42
|
+
Результат и запись в `probes.jsonl` содержат `runId`, `status`, фактический `argv`, `gate.id`,
|
|
43
|
+
`gate.phase`, `gate.sha`, `gate.cwd`, `gate.controllerEnv` (только имена), `gate.report`
|
|
44
|
+
(`status`, `path`, `digest` — SHA-256 сохранённых байтов, `tests`, `failures`, `skipped`),
|
|
45
|
+
`gate.scratchRemoved` и `log` — журнал controller. `review.json` переносит эти записи в
|
|
46
|
+
`probes`; `reproduction.run` находки ссылается на `runId`, а через него — на журнал.
|
|
47
|
+
|
|
48
|
+
## Бюджет проверок и авария controller
|
|
49
|
+
|
|
50
|
+
Бюджет проверок общий на логическое ревью (область, фаза, SHA), включая повторы reviewer и
|
|
51
|
+
рестарт controller. Учёт — журнал `probes.jsonl` каждого вызова:
|
|
52
|
+
|
|
53
|
+
1. До запуска команды controller резервирует её предел и синхронно пишет строку
|
|
54
|
+
`{"type":"start","runId","reservedMs","startedAt","pid"}` с fsync. Не удалось записать —
|
|
55
|
+
`error`/`environment`, команда не запускается.
|
|
56
|
+
2. Команда и все её потомки получают `HARNESS_PROBE_RUN=<runId>`.
|
|
57
|
+
3. Строка исхода (обычная запись запуска) разрешает резерв фактической длительностью.
|
|
58
|
+
4. Новый controller перед следующим вызовом reviewer считает расход: завершённые — по
|
|
59
|
+
длительности, начатые без исхода — **весь резерв** (время до гибели не известно точно и не
|
|
60
|
+
больше резерва; засчитывается верхняя граница). Пережившие процессы такого запуска (по метке
|
|
61
|
+
и их группам процессов) останавливаются, в журнал дописывается
|
|
62
|
+
`{"type":"abandoned","runId","chargedMs"}`; повторное восстановление расход не меняет, а
|
|
63
|
+
строка исхода, пришедшая позже, заменяет списание фактом без двойного счёта.
|
|
64
|
+
5. Следующий вызов получает бюджет `budget − spent` и прежний срок логического ревью; остаток
|
|
65
|
+
меньше секунды — вызов без инструмента проверок.
|
|
66
|
+
|
|
67
|
+
Ограничение: на macOS `ps -E` не показывает окружение системных бинарников (например,
|
|
68
|
+
`/bin/sleep`); такой процесс останавливается, если он в группе процессов запуска.
|
|
69
|
+
|
|
70
|
+
## Статусы
|
|
71
|
+
|
|
72
|
+
| `status` | `failure` | Смысл |
|
|
73
|
+
| ------------------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `passed` | — | код 0 и (для gate с отчётом) свежий JUnit без failures и skips |
|
|
75
|
+
| `failed` | — | код ≠ 0 или упали testcases: наблюдение о коде |
|
|
76
|
+
| `failed` | `report` | код 0, но отчёт `missing`/`stale`/`corrupt`/`unsafe`: не прохождение и не находка в коде |
|
|
77
|
+
| `error` | `environment` | путь отчёта зашит в argv, отказ записи (EPERM/EACCES) мимо `HARNESS_REPORT_PATH`, отказ scratch, сеть закрыта песочницей, нет песочницы |
|
|
78
|
+
| `timeout`, `cancelled` | — | команда остановлена вместе с потомками |
|
|
79
|
+
| `budget-exhausted`, `rejected` | — | команда не запускалась |
|
|
80
|
+
|
|
81
|
+
Ошибка среды и ошибка отчёта — не находка в коде; находка, сославшаяся на запуск с
|
|
82
|
+
`failure: environment`, не получает `observed`.
|
|
83
|
+
|
|
84
|
+
**Probe — наблюдение reviewer.** Обязательные gates candidate и integration запускает и
|
|
85
|
+
оценивает движок; probe не записывает evidence gate, не ставит технический PASS и не двигает
|
|
86
|
+
target ref.
|
|
87
|
+
|
|
88
|
+
## Итог `inspection`: что reviewer действительно выполнил
|
|
89
|
+
|
|
90
|
+
`review.json.inspection` и `EvidenceRecord.inspection` ревью кода — один и тот же объект. Его
|
|
91
|
+
собирает controller для **одного вызова reviewer** из двух наблюдений: событий runtime этого
|
|
92
|
+
вызова (Codex `item.completed`/`command_execution`) и журнала проверок `probes.jsonl` этого
|
|
93
|
+
вызова. Самоотчёт модели (`execution.commands`, `reproduction.executed`, runId в тексте) ничего
|
|
94
|
+
не подтверждает.
|
|
95
|
+
|
|
96
|
+
- `mode: commands` — подтверждена хотя бы одна выполненная команда; `reason: null`. Ненулевой
|
|
97
|
+
код, таймаут или отмена после старта и ошибка среды после старта выполнение не отменяют.
|
|
98
|
+
- `mode: diff-only` — подтверждённых выполнений нет; `commands: []`, `reason` объясняет.
|
|
99
|
+
- `commands` — не PASS: тесты могли упасть, gates и вердикт оценивает движок. Принятие по-прежнему
|
|
100
|
+
зависит только от обязательных gates и валидного вердикта; `commands` не лечит невалидный ответ,
|
|
101
|
+
изменённое дерево или проваленный gate, а неуспешный probe сам не создаёт находку.
|
|
102
|
+
|
|
103
|
+
### Граница старта
|
|
104
|
+
|
|
105
|
+
Запуск probe засчитывается по признаку `started` из `runCheck`: без песочницы — процесс порождён
|
|
106
|
+
(событие `spawn`), в песочнице — обёртка создала метку старта перед `exec` (запуск самой обёртки
|
|
107
|
+
не считается). Признак отмечается на границе и переживает исключение после неё.
|
|
108
|
+
|
|
109
|
+
| Событие | В `commands` |
|
|
110
|
+
| ----------------------------------------------------------------------------------------- | ------------ |
|
|
111
|
+
| Команда завершилась с любым кодом | да |
|
|
112
|
+
| `timeout`/`cancelled` после старта (код `null`, частичный вывод) | да |
|
|
113
|
+
| `error`/`environment` после старта (сеть закрыта песочницей, EPERM, сбой записи evidence) | да |
|
|
114
|
+
| `failed`/`report`: код 0, JUnit отсутствует или испорчен | да |
|
|
115
|
+
| Неизвестный gate, бюджет исчерпан, нет executable, отказ песочницы или scratch до старта | нет |
|
|
116
|
+
| Вызов отменён до запуска (`cancelled`, команда не исполнялась) | нет |
|
|
117
|
+
| Только резерв `start`, `abandoned`, битая строка, объект без формы исхода | нет |
|
|
118
|
+
|
|
119
|
+
Незасчитанные записи перечислены в `inspection.excluded` с причиной; полная диагностика — в
|
|
120
|
+
`review.json.probes` и журнале.
|
|
121
|
+
|
|
122
|
+
### Форма
|
|
123
|
+
|
|
124
|
+
| Смысл | JSON path | Тип и значения |
|
|
125
|
+
| ------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
126
|
+
| Источник итога | `source` | `codex-events`, `unavailable` (прежние), `controller-probes`, `runtime-and-controller` |
|
|
127
|
+
| Вызов reviewer | `context` | `{attempt, phase: candidate\|integration, sha, call}`; `attempt` — ID попытки, не runId probe |
|
|
128
|
+
| Все подтверждённые операции | `total` | целое ≥ 0 |
|
|
129
|
+
| Происхождение команды | `commands[].origins[]` | `{type: "runtime", runtime, item: string\|null}` или `{type: "controller", runId}` |
|
|
130
|
+
| Код выхода | `commands[].exitCode` | целое или `null` (неизвестен: таймаут, отмена, старая проекция без кода) |
|
|
131
|
+
| Исход | `commands[].status` | `completed`/`failed` (runtime); `passed`/`failed`/`timeout`/`cancelled`/`error` (probe) |
|
|
132
|
+
| Неуспех не в коде | `commands[].failure` | `environment`, `report` |
|
|
133
|
+
| Gate | `commands[].gate` | ID объявленного gate |
|
|
134
|
+
| Пояснение controller | `commands[].note` | строка ≤ 2000 |
|
|
135
|
+
| Не засчитано | `excluded[]` | `{runId, status, reason}`, `reason`: `not-started`, `legacy-unconfirmed`, `foreign-scope`, `unknown-status`, `unfinished`, `abandoned`; не более 100, полное число — `excludedTotal` |
|
|
136
|
+
| Строки журнала без формы исхода | `invalid` | целое ≥ 0 |
|
|
137
|
+
|
|
138
|
+
Пределы: не более 100 команд; `command` ≤ 12000 символов; `output` ≤ 4000 (stdout и stderr через
|
|
139
|
+
перевод строки, при нехватке места — хвосты обоих примерно поровну); `note` ≤ 2000. Потоки probe
|
|
140
|
+
в журнале по-прежнему ограничены хвостом 8000 с маркером `…`. Порядок: команды runtime в порядке
|
|
141
|
+
потока, затем запуски probe в порядке журнала; при превышении сохраняются последние 100, а
|
|
142
|
+
`mode` вычисляется по всему набору. Пользовательский текст маскируется до обрезки; перечисления и
|
|
143
|
+
идентификаторы controller не маскируются.
|
|
144
|
+
|
|
145
|
+
### Идентичность и область
|
|
146
|
+
|
|
147
|
+
Операция одна только при надёжной идентичности: один runId probe; один item ID в потоке runtime;
|
|
148
|
+
явная структурированная связь наблюдения runtime с runId probe (origin `controller` у команды
|
|
149
|
+
runtime) — тогда у операции оба происхождения. Одинаковый argv с разными runId — две операции;
|
|
150
|
+
совпадение текста, вывода или замаскированных аргументов не склеивает.
|
|
151
|
+
|
|
152
|
+
Каждый исход в журнале несёт `scope: {attempt, phase, sha, call}`. Запись с явно чужими
|
|
153
|
+
попыткой, фазой, SHA или вызовом (или gate с чужими `phase`/`sha`) не засчитывается
|
|
154
|
+
(`excluded`, `foreign-scope`). `call-2` не получает команд `call-1`, integration — команд
|
|
155
|
+
candidate; общий бюджет проверок логического ревью по-прежнему считается по журналам всех вызовов.
|
|
156
|
+
|
|
157
|
+
### Старые записи
|
|
158
|
+
|
|
159
|
+
Прежние `inspection` (без новых полей, в том числе противоречивые `diff-only` с командой) и
|
|
160
|
+
evidence без `inspection` читаются как есть; чтение не пересчитывает итог, digest и решение.
|
|
161
|
+
Для отдельной агрегации старых данных: завершённые события runtime засчитываются как раньше;
|
|
162
|
+
записи probe без `started` со статусом `passed`/`failed` — выполнение (код и вывод, если их нет,
|
|
163
|
+
остаются неизвестными: `exitCode: null`, пустой `output`); `error`/`timeout`/`cancelled` без
|
|
164
|
+
подтверждения старта не засчитываются (`legacy-unconfirmed`) — это ограничение старых данных.
|
|
165
|
+
Явный `started: false` главнее правила старых записей.
|
|
166
|
+
|
|
167
|
+
## Рецепт: путь отчёта из переменной controller
|
|
168
|
+
|
|
169
|
+
Gate пригоден для probe, если команда пишет JUnit в `HARNESS_REPORT_PATH` (или
|
|
170
|
+
`DEVCONTOUR_REPORT_PATH`). Gate движка получает ту же переменную с путём `report.path` внутри
|
|
171
|
+
дерева, поэтому рецепт с прежним путём по умолчанию не меняет поведение gate движка.
|
|
172
|
+
|
|
173
|
+
Controller не ищет и не заменяет подстроки в argv. Если элемент argv целиком или после `=`
|
|
174
|
+
(`--junitxml=…`) совпадает с `report.path` относительно `cwd` gate, probe возвращает
|
|
175
|
+
`error`/`environment` с номером аргумента и рецептом, не запуская команду. Если путь зашит в
|
|
176
|
+
скрипт (package script), запуск идёт, запись в дерево получает EPERM, и результат —
|
|
177
|
+
та же ошибка среды с рецептом.
|
|
178
|
+
|
|
179
|
+
| Инструмент | Сейчас в профилях/примерах | Пригодно для probe |
|
|
180
|
+
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
181
|
+
| demo runner (`test/run-tests.mjs`) | читает `DEVCONTOUR_REPORT_PATH`/`HARNESS_REPORT_PATH`, иначе `.reports/<suite>.xml` | да, как есть |
|
|
182
|
+
| npm (`profiles/npm-ci.json`, `examples/toolchains/npm`) | `npm run test` → `--test-reporter-destination=.reports/junit.xml` в package script | `"test": "node --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=${HARNESS_REPORT_PATH:-.reports/junit.xml} test/*.test.js"` |
|
|
183
|
+
| pnpm (`profiles/pnpm-frozen.json`, `examples/toolchains/pnpm`, `turbo`) | тот же путь в package script пакета | то же в script пакета; для вложенного пакета — абсолютный `HARNESS_REPORT_PATH` не зависит от каталога |
|
|
184
|
+
| Go (`profiles/go-gotestsum.json`) | `["gotestsum", "--junitfile", ".reports/junit.xml", "--", "-count=1", "./..."]` | `["sh", "-c", "GOCACHE=\"${GOCACHE:-$TMPDIR/go-build}\" exec gotestsum --junitfile \"${HARNESS_REPORT_PATH:-.reports/junit.xml}\" -- -count=1 ./..."]` |
|
|
185
|
+
| Python (`profiles/python-uv.json`) | `["uv", "run", "--no-sync", "pytest", "--junitxml=.reports/junit.xml"]` | `["sh", "-c", "PYTHONDONTWRITEBYTECODE=1 UV_CACHE_DIR=\"${UV_CACHE_DIR:-$TMPDIR/uv}\" exec uv run --no-sync pytest -p no:cacheprovider --junitxml=\"${HARNESS_REPORT_PATH:-.reports/junit.xml}\""]` |
|
|
186
|
+
| mixed-python (`cwd: backend`) | `--junitxml=../.reports/backend.xml` | `--junitxml="${HARNESS_REPORT_PATH:-../.reports/backend.xml}"` через `sh -c` |
|
|
187
|
+
|
|
188
|
+
Рецепты npm, Go и Python проверены на настоящих инструментах под песочницей ОС
|
|
189
|
+
<!-- repo-only -->(`tests/integration/probe-recipes.test.ts`)<!-- /repo-only -->: команда профиля даёт `error`/`environment` (для
|
|
190
|
+
|
|
191
|
+
npm — после EPERM записи из package script), рецепт проходит в probe со свежим JUnit вне
|
|
192
|
+
дерева и в gate движка с отчётом в дереве. Под `isolation.mode: os` cache Go и uv по умолчанию
|
|
193
|
+
лежит вне доступного на запись, поэтому рецепт направляет `GOCACHE`/`UV_CACHE_DIR` в `TMPDIR`
|
|
194
|
+
(если они не заданы), а pytest — без `.pytest_cache` и байткода в дереве. pnpm и turbo
|
|
195
|
+
используют тот же механизм package script, что и npm; отдельным прогоном не проверялись.
|
|
196
|
+
|
|
197
|
+
Встроенные профили не меняются (их байты закреплены digest). Для них probe по ID даёт
|
|
198
|
+
проверяемую диагностику вместо EPERM; gate движка работает как прежде. Новый профиль с рецептом —
|
|
199
|
+
новый файл и новый ID.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Протокол ревью кода: находки, переходы и ошибки протокола
|
|
2
|
+
|
|
3
|
+
Документ задаёт правила истории находок независимого ревью кода (candidate и
|
|
4
|
+
integration) и отделяет ошибку формата ответа reviewer от содержательного
|
|
5
|
+
отказа. Правила записаны до реализации и являются договором: тесты
|
|
6
|
+
`tests/review-*.test.ts` проверяют именно их.
|
|
7
|
+
|
|
8
|
+
## 1. Область истории
|
|
9
|
+
|
|
10
|
+
История находок ведётся **на поручение**, а не на попытку, фазу или SHA:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
scope = digestOf({ kind: 'code-review', repositoryId, workId })
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Новая попытка writer, переход candidate → integration и рестарт процесса не
|
|
17
|
+
начинают историю заново: история каждый раз заново собирается из evidence
|
|
18
|
+
всех попыток поручения (`ExecutionView.attempts(workId)`) в порядке записи.
|
|
19
|
+
- Отдельного хранилища нет: состояние находок — это записи ревью в
|
|
20
|
+
`EvidenceRecord` попыток (см. §5). Нового обязательного метода
|
|
21
|
+
`ExecutionStore`, второй базы или нового пространства локальных записей нет.
|
|
22
|
+
- SHA не является идентичностью истории: после исправления SHA меняется, а
|
|
23
|
+
находка — нет.
|
|
24
|
+
- Находка другой области (другое поручение или репозиторий) в этой истории
|
|
25
|
+
неизвестна: ссылка на неё — ошибка протокола `unknown`.
|
|
26
|
+
|
|
27
|
+
## 2. Идентификатор находки
|
|
28
|
+
|
|
29
|
+
ID выдаёт controller в момент записи валидного раунда; reviewer его не
|
|
30
|
+
придумывает и ссылается только на выданный. Формат прежний — SHA-256 hex
|
|
31
|
+
(`^[0-9a-f]{64}$`), без ослабления до произвольной строки.
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
id = digestOf({ v: 1, scope, runId, phase, sha, round, index, severity, message })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`round` — номер валидного раунда области, `index` — позиция находки в ответе.
|
|
38
|
+
Повторно найденный дефект с тем же текстом в новом раунде получает **новый**
|
|
39
|
+
ID: дедупликации по тексту нет, поэтому повторно найденный на integration
|
|
40
|
+
дефект не теряется.
|
|
41
|
+
|
|
42
|
+
Находка хранит: severity, сообщение, путь/строку, статус (`open`, `closed`,
|
|
43
|
+
`withdrawn`), origin (runId, фаза, SHA, раунд, согласованная постановка
|
|
44
|
+
`spec` на момент записи) и ряд переходов (раунд, runId, фаза, SHA, from → to,
|
|
45
|
+
основание).
|
|
46
|
+
|
|
47
|
+
## 3. Переходы
|
|
48
|
+
|
|
49
|
+
Для каждой прежней **открытой** находки валидный ответ называет ровно одну
|
|
50
|
+
запись в `previous` с основанием (`basis`, не короче трёх непробельных
|
|
51
|
+
символов):
|
|
52
|
+
|
|
53
|
+
| Из | В | Смысл |
|
|
54
|
+
| -------------------- | ----------- | -------------------------------------------------------------------------------------- |
|
|
55
|
+
| `open` | `closed` | исправление подтверждено на проверяемом SHA |
|
|
56
|
+
| `open` | `withdrawn` | находка ошибочна или потеряла предмет; основание объясняет почему |
|
|
57
|
+
| `open` | `open` | дефект остаётся |
|
|
58
|
+
| `closed`/`withdrawn` | `open` | явное повторное открытие (reopen): дефект снова есть, например в интеграционном дереве |
|
|
59
|
+
|
|
60
|
+
Закрытые и снятые находки передаются reviewer как история и не требуют
|
|
61
|
+
повторного закрытия. Указать для них `closed`/`withdrawn` повторно —
|
|
62
|
+
ошибка `not-open`.
|
|
63
|
+
|
|
64
|
+
Ответ невалиден (ошибка протокола), если:
|
|
65
|
+
|
|
66
|
+
| reason | path | условие |
|
|
67
|
+
| ------------------------ | ---------------------------------- | ----------------------------------------------------------------------------- |
|
|
68
|
+
| `schema` | путь Zod, например `previous.0.id` | ответ вне схемы: не SHA-256 ID, пустое основание, неизвестный статус, поля |
|
|
69
|
+
| `unknown` | `previous.N.id` | ID нет в истории области (в том числе ID чужого поручения/репозитория) |
|
|
70
|
+
| `duplicate` | `previous.N.id` | ID назван второй раз (в том числе с другим статусом) |
|
|
71
|
+
| `not-open` | `previous.N.status` | закрытой/снятой находке назван не `open` |
|
|
72
|
+
| `missing` | `previous` | прежняя открытая находка не названа |
|
|
73
|
+
| `approved-with-blocking` | `approved` | `approved: true` при новой blocking-находке или blocking, оставшейся открытой |
|
|
74
|
+
|
|
75
|
+
`note`, оставленная открытой явным переходом, не мешает `approved: true`.
|
|
76
|
+
|
|
77
|
+
**Атомарность.** Ответ проверяется целиком до любой записи. Невалидный ответ
|
|
78
|
+
не закрывает ни одной находки, не создаёт новых и не даёт PASS: он
|
|
79
|
+
сохраняется только как диагностика (сырой ответ — в своём `review.json`,
|
|
80
|
+
причины — в evidence). Валидный раунд записывается одной транзакцией
|
|
81
|
+
`engine.evidence` под fencing-токеном попытки: попытка, потерявшая владение,
|
|
82
|
+
переходов не сохраняет.
|
|
83
|
+
|
|
84
|
+
## 4. Фазы, повторное открытие и новая редакция
|
|
85
|
+
|
|
86
|
+
- Закрытие на candidate не означает одобрения integration: integration —
|
|
87
|
+
отдельный раунд той же области. Reviewer integration получает открытые
|
|
88
|
+
находки и историю закрытых; найденный там снова дефект блокирует приёмку
|
|
89
|
+
либо явным reopen прежнего ID, либо новой находкой с новым ID.
|
|
90
|
+
- Приёмка по-прежнему требует последней записи ревью каждой фазы на точном SHA
|
|
91
|
+
с PASS (`engine.finish`, `findLast`).
|
|
92
|
+
- **Новая редакция постановки/контрактов** (новое согласование того же
|
|
93
|
+
поручения) не начинает историю заново и не удаляет её. Открытые находки
|
|
94
|
+
переносятся в новую редакцию открытыми; в их origin видна редакция, под
|
|
95
|
+
которой они записаны (`spec`). Reviewer новой редакции обязан назвать их
|
|
96
|
+
состояние, как любые прежние открытые (например, `withdrawn` с основанием
|
|
97
|
+
«требование удалено редакцией …»). Закрытые остаются историей. Одобрение
|
|
98
|
+
прежней редакции новую не подтверждает: новая редакция исполняется новой
|
|
99
|
+
попыткой, и приёмка учитывает только evidence этой попытки.
|
|
100
|
+
- Формулы host digests (`orderDigest`, `policyDigest`, `scopeDigest`,
|
|
101
|
+
основание реализации и эпизода) не меняются: находки в них не входят.
|
|
102
|
+
|
|
103
|
+
## 5. Хранение: аддитивные поля EvidenceRecord
|
|
104
|
+
|
|
105
|
+
DevContour хранит `Evidence` как есть; все новые поля необязательны, старые
|
|
106
|
+
записи читаются без них.
|
|
107
|
+
|
|
108
|
+
- `findings[].id`, `findings[].status` — ID и статус находки, записанной этим
|
|
109
|
+
раундом.
|
|
110
|
+
- `reviewRound` — сведения раунда ревью кода:
|
|
111
|
+
`version: 1`, `scope`, `round`, `call` (номер вызова reviewer в логическом
|
|
112
|
+
ревью), `calls` (предел вызовов), `protocol: valid | invalid`, `expected`
|
|
113
|
+
(ID открытых находок, выданных reviewer), `issues` (path, reason, message —
|
|
114
|
+
у невалидного), `transitions` (id, from, to, basis — у валидного), `spec`.
|
|
115
|
+
|
|
116
|
+
Результат (`RunResult`) получает аддитивно: `findings[].id/status` и у ссылок
|
|
117
|
+
на evidence ревью — `review` (round, call, protocol, issues, transitions).
|
|
118
|
+
Принятый результат несёт только решающие записи: невалидные вызовы в него не
|
|
119
|
+
входят отдельными ссылками, их причины видны у валидного вердикта повтора
|
|
120
|
+
(`review.call > 1`, `review.issues` — причины предыдущих вызовов того же
|
|
121
|
+
логического ревью). Непринятый результат содержит и записи невалидных
|
|
122
|
+
вызовов (`review.protocol: invalid` с их причинами).
|
|
123
|
+
|
|
124
|
+
## 6. Записи alpha.5 (legacy)
|
|
125
|
+
|
|
126
|
+
Запись ревью без `reviewRound` — запись прежней версии. Её находки (у
|
|
127
|
+
отклонившего ревью) получают детерминированный ID из неизменяемого origin:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
id = digestOf({ v: 'legacy-1', scope, evidence: e.id, runId, phase, sha, index, severity, message })
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
После рестарта ID тот же. Находки открыты, пока reviewer явно не назовёт их
|
|
134
|
+
состояние — это явное повторное ревью вместо пустой истории. Принятые
|
|
135
|
+
попытки и их результаты не переписываются: новые поля у них не появляются, и
|
|
136
|
+
исход не меняется задним числом.
|
|
137
|
+
|
|
138
|
+
## 7. Ошибка протокола и ограниченный повтор reviewer
|
|
139
|
+
|
|
140
|
+
| Исход | Поведение |
|
|
141
|
+
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
142
|
+
| Валидный `approved: false` | `REVIEW_REJECTED`, обычный путь исправления writer |
|
|
143
|
+
| Нарушение схемы/переходов/противоречивый approved | повтор reviewer; после исчерпания — `REVIEW_PROTOCOL_INVALID` (класс infrastructure, exit 4) |
|
|
144
|
+
| Reviewer изменил дерево/HEAD, утрачено владение, отмена, смена согласования | соответствующий отказ без протокольного повтора |
|
|
145
|
+
| Runtime недоступен, авторизация, timeout | прежняя инфраструктурная причина, без протокольного повтора |
|
|
146
|
+
|
|
147
|
+
- Политика: необязательное поле `reviewProtocolRetries` (0–2). Нет поля —
|
|
148
|
+
один дополнительный вызов (не более двух вызовов на логическое ревью);
|
|
149
|
+
`0` отключает повтор. Отсутствующее поле не подставляется в политику, её
|
|
150
|
+
digest и pins не меняются.
|
|
151
|
+
- Логическое ревью — (область, фаза, SHA). Повтор идёт на том же SHA, с тем же
|
|
152
|
+
diff, контекстом, назначением reviewer и набором открытых находок; к запросу
|
|
153
|
+
добавляется только структурированная диагностика (path, reason, message) и
|
|
154
|
+
требование вернуть полный валидный вердикт. ID не подменяются «похожими»,
|
|
155
|
+
`previous` не выбрасывается, `approved` не восстанавливается догадкой.
|
|
156
|
+
- Writer не вызывается, номер попытки не растёт. Каждый вызов проходит
|
|
157
|
+
`measuredInvoke` (бюджет до вызова, учёт usage/стоимости, неизвестная
|
|
158
|
+
стоимость по `budgets.onUnknown`). Повтор не получает нового предела
|
|
159
|
+
времени фазы и нового бюджета probe: общий срок и остаток бюджета probe
|
|
160
|
+
переходят от первого вызова.
|
|
161
|
+
- Каждый вызов пишет свои артефакты: первый — `<phase>/review/`, следующие —
|
|
162
|
+
`<phase>/review/call-<n>/` (`review.json`, журнал runtime, probe). Каждый
|
|
163
|
+
невалидный вызов записывается своим evidence (`passed: false`,
|
|
164
|
+
`reviewRound.protocol: invalid`, без находок); итоговое evidence ссылается
|
|
165
|
+
на байты валидного вердикта.
|
|
166
|
+
- Рестарт не обнуляет счётчик. Вызов засчитывается при **начале**: запись
|
|
167
|
+
учёта вызова (`usage`, `CallRecord` со `stage: <фаза>-review`, `runId`)
|
|
168
|
+
создаётся транзакцией `measuredInvoke` до внешнего invoke, поэтому вызов,
|
|
169
|
+
прерванный падением controller, засчитан так же, как завершённый (после
|
|
170
|
+
рестарта он помечается `abandoned`). Начатые вызовы того же (область, фаза,
|
|
171
|
+
SHA) из попыток, прерванных до вердикта (истёкших или с отказом окружения),
|
|
172
|
+
засчитываются продолжающей попытке (кандидат переиспользуется); для записей
|
|
173
|
+
без учёта вызовов — по невалидным evidence. Исчерпанный счётчик — остановка
|
|
174
|
+
`REVIEW_PROTOCOL_INVALID` без вызова. Номер вызова — номер фактически
|
|
175
|
+
начатого вызова. Логическое ревью закрывают валидный вердикт или иное
|
|
176
|
+
завершение попытки (в том числе остановка `REVIEW_PROTOCOL_INVALID`).
|
|
177
|
+
- Срок и бюджет проверок после рестарта не выдаются заново: срок логического
|
|
178
|
+
ревью — `runTimeoutMs` от начала его первого вызова (по записи учёта);
|
|
179
|
+
истёкший срок — явная остановка `REVIEW_PROTOCOL_INVALID` (класс `review-protocol`, без автоматического повтора) без вызова, а каждому вызову достаётся
|
|
180
|
+
только остаток срока. Расход бюджета проверок — по журналам `probes.jsonl`
|
|
181
|
+
прежних вызовов этого логического ревью. Резерв каждой проверки (её предел)
|
|
182
|
+
записывается в журнал строкой `start` с fsync **до** запуска команды; без
|
|
183
|
+
этой записи проверка не запускается. Завершённая проверка списывает
|
|
184
|
+
фактическое время, начатая без строки исхода (controller или сервер проверок
|
|
185
|
+
погиб посреди команды) — весь резерв, никогда не меньше. Новый controller
|
|
186
|
+
останавливает пережившие процессы такой проверки (метка `HARNESS_PROBE_RUN`
|
|
187
|
+
и их группы) и дописывает строку `abandoned`; остаток меньше секунды — вызов
|
|
188
|
+
без инструмента проверок (docs/review-probes.md). Integration SHA после рестарта
|
|
189
|
+
создаётся заново (новое слияние), поэтому это новое логическое ревью; его
|
|
190
|
+
повторы ограничены правилами повтора попытки.
|
|
191
|
+
- Класс отказа попытки `review-protocol`: автоматического повтора нет
|
|
192
|
+
(`repairPolicy` — stop), для эпизода восстановления по свойству он
|
|
193
|
+
нейтрален (не увеличивает и не обнуляет счёт), код остановки результата —
|
|
194
|
+
`REVIEW_PROTOCOL_INVALID`. Кандидат такой попытки переиспользуется
|
|
195
|
+
повтором, назначенным человеком, без нового вызова writer.
|
|
196
|
+
|
|
197
|
+
## 8. План и контракт
|
|
198
|
+
|
|
199
|
+
`technicalReview` (ревью плана и контракта) применяет то же правило
|
|
200
|
+
атомарности: невалидный ответ (вне схемы или с нарушением `reviewIssues`)
|
|
201
|
+
не применяет переходы и не добавляет находок; раунд записывается
|
|
202
|
+
неодобренным с причинами. Повторно найденная закрытая находка с тем же ID
|
|
203
|
+
открывается снова, а не отбрасывается как дубликат. Формат `ReviewRecord`
|
|
204
|
+
не меняется.
|
|
205
|
+
|
|
206
|
+
## 9. Адаптеры без протокола переходов (совместимость хостов)
|
|
207
|
+
|
|
208
|
+
Строгие правила §3 действуют, когда адаптер reviewer объявляет
|
|
209
|
+
`RuntimeCapabilities.reviewProtocol: 1`. Это необязательное поле; нового
|
|
210
|
+
обязательного метода нет. `cliAdapter` (codex, claude, в том числе fixture
|
|
211
|
+
executables `HARNESS_*_BIN`) поле объявляет, поэтому пилот, live и demo
|
|
212
|
+
работают строго. Runner читает поле асинхронно до вызова reviewer, вне
|
|
213
|
+
транзакций хранилища, и кэширует его на runtime. Отказ `capabilities()`
|
|
214
|
+
трактуется как адаптер прежней версии.
|
|
215
|
+
|
|
216
|
+
Адаптер хоста, который поля не объявляет (например, собственные
|
|
217
|
+
runtime-обёртки DevContour), получает семантику переходов alpha.5:
|
|
218
|
+
|
|
219
|
+
- в запросе нет раздела выданных ID, и в ответе переходы не требуются:
|
|
220
|
+
ответ проверяется без прежних открытых находок, как прежний
|
|
221
|
+
`reviewIssues(result, [])`. Ссылка на прежний ID — `unknown`, а
|
|
222
|
+
`approved: true` с новой blocking-находкой — `approved-with-blocking`;
|
|
223
|
+
- находки записываются в историю открытыми с выданными ID, раунд помечен
|
|
224
|
+
`reviewRound.mode: 'legacy'`. Закрытия не выдумываются: одобрение не
|
|
225
|
+
закрывает прежние находки, они остаются в истории открытыми. Одобрение
|
|
226
|
+
при таких находках — поведение alpha.5 для этого адаптера: прежний адаптер
|
|
227
|
+
не умеет называть их состояние;
|
|
228
|
+
- разделение H2 сохраняется: ответ вне схемы или противоречивый `approved`
|
|
229
|
+
даёт ошибку протокола с ограниченным повтором reviewer и
|
|
230
|
+
`REVIEW_PROTOCOL_INVALID`, а не содержательный отказ;
|
|
231
|
+
- writer такого поручения не получает раздела «открытых» находок: их
|
|
232
|
+
некому закрыть.
|
|
233
|
+
|
|
234
|
+
Если позже ревью ведёт адаптер с протоколом, открытые находки legacy-раундов
|
|
235
|
+
требуют явного перехода, как записи alpha.5 (§6).
|
|
236
|
+
|
|
237
|
+
### Что реализует собственный адаптер с протоколом
|
|
238
|
+
|
|
239
|
+
1. `capabilities()` возвращает `reviewProtocol: 1`.
|
|
240
|
+
2. Для `task: 'review-code'` запрос несёт `RuntimeRequest.findings`
|
|
241
|
+
(`{scope, open[], closed[]}`; у находки — `id`, `severity`, `message`, `path`,
|
|
242
|
+
`line`, `status`, `origin {runId, phase, sha, round}`, `basis?`). Это те же
|
|
243
|
+
данные, что раздел запроса `Open review findings issued by the controller`,
|
|
244
|
+
поэтому адаптеру без модели не нужно разбирать текст.
|
|
245
|
+
3. Ответ (`RuntimeResponse.output`, схема `reviewResultSchema` / wire
|
|
246
|
+
`reviewOutputSchema`) содержит в `previous` ровно одну запись
|
|
247
|
+
`{id, status: 'open' | 'closed' | 'withdrawn', basis}` на каждую находку
|
|
248
|
+
`open`. `id` — выданный (SHA-256 hex), `basis` — не меньше трёх непробельных
|
|
249
|
+
символов. Для `closed` допустимо только `{id, status: 'open', basis}`
|
|
250
|
+
(повторное открытие). `approved: true` невозможно при blocking-находке,
|
|
251
|
+
оставленной открытой, или при новой blocking-находке.
|