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

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 (73) hide show
  1. package/README.md +48 -2
  2. package/docs/architecture.md +77 -3
  3. package/docs/contracts.md +100 -0
  4. package/docs/quickstart.md +1 -1
  5. package/docs/release-notes/0.1.0-alpha.14.md +69 -0
  6. package/docs/release-notes/0.1.0-alpha.15.md +62 -0
  7. package/docs/release-notes/0.1.0-alpha.16.md +36 -0
  8. package/docs/release-notes/0.1.0-alpha.17.md +86 -0
  9. package/docs/release-notes/0.1.0-alpha.18.md +48 -0
  10. package/docs/review-probes.md +9 -3
  11. package/docs/review-protocol.md +9 -0
  12. package/docs/upgrade.md +16 -14
  13. package/lib/agents/cli-adapter.js +14 -10
  14. package/lib/agents/review-probe.d.ts +9 -0
  15. package/lib/agents/review-probe.js +23 -1
  16. package/lib/agents/schemas.d.ts +206 -0
  17. package/lib/agents/schemas.js +37 -1
  18. package/lib/agents/usage.d.ts +22 -0
  19. package/lib/agents/usage.js +112 -6
  20. package/lib/contracts/checks.d.ts +119 -0
  21. package/lib/contracts/checks.js +64 -1
  22. package/lib/contracts/cli.d.ts +30 -0
  23. package/lib/contracts/diagnosis.d.ts +3 -1
  24. package/lib/contracts/diagnosis.js +2 -0
  25. package/lib/contracts/events.d.ts +12 -0
  26. package/lib/contracts/execution.d.ts +109 -5
  27. package/lib/contracts/execution.js +58 -0
  28. package/lib/contracts/failure.d.ts +17 -3
  29. package/lib/contracts/failure.js +4 -2
  30. package/lib/contracts/published.d.ts +67 -1
  31. package/lib/contracts/repair-policy.d.ts +7 -0
  32. package/lib/contracts/repair-policy.js +17 -0
  33. package/lib/contracts/review.d.ts +12 -0
  34. package/lib/contracts/review.js +19 -0
  35. package/lib/control/harness.d.ts +2 -2
  36. package/lib/control/harness.js +2 -1
  37. package/lib/control/recovery.d.ts +26 -1
  38. package/lib/engine/engine.d.ts +41 -14
  39. package/lib/engine/engine.js +92 -8
  40. package/lib/engine/failure.d.ts +1 -1
  41. package/lib/engine/failure.js +1 -1
  42. package/lib/engine/property-recovery.d.ts +1 -0
  43. package/lib/engine/property-recovery.js +4 -1
  44. package/lib/engine/repair.d.ts +26 -0
  45. package/lib/engine/repair.js +83 -1
  46. package/lib/engine/runner.d.ts +7 -0
  47. package/lib/engine/runner.js +207 -34
  48. package/lib/explain/state.js +4 -2
  49. package/lib/index.d.ts +6 -4
  50. package/lib/index.js +6 -4
  51. package/lib/projections/stop.d.ts +1 -1
  52. package/lib/projections/stop.js +16 -0
  53. package/lib/storage/document.d.ts +48 -8
  54. package/lib/verification/acceptance.js +2 -1
  55. package/lib/verification/already-satisfied.d.ts +44 -0
  56. package/lib/verification/already-satisfied.js +49 -0
  57. package/lib/verification/checks.d.ts +28 -0
  58. package/lib/verification/checks.js +270 -22
  59. package/lib/verification/corpus.d.ts +50 -0
  60. package/lib/verification/gate-scope.d.ts +36 -0
  61. package/lib/verification/gate-scope.js +97 -0
  62. package/lib/verification/gates-at.d.ts +67 -0
  63. package/lib/verification/gates-at.js +173 -0
  64. package/lib/verification/gates.d.ts +5 -0
  65. package/lib/verification/gates.js +20 -5
  66. package/lib/verification/property.d.ts +9 -0
  67. package/lib/verification/property.js +11 -2
  68. package/lib/verification/self-check.js +12 -12
  69. package/package.json +1 -1
  70. package/schemas/contract-document.schema.json +161 -0
  71. package/schemas/execution-document.schema.json +134 -0
  72. package/schemas/run-event.schema.json +4 -0
  73. package/schemas/stop-diagnosis.schema.json +2 -0
@@ -29,13 +29,19 @@ To run a declared gate, call run_check with exactly {"gate":"domain"}, {"gate":"
29
29
  3. Если путь отчёта зашит в argv (см. ниже) — не запускает, возвращает ошибку среды с рецептом.
30
30
  4. Создаёт каталог evidence `<каталог ревью>/probe-runs/<runId>/` (вне дерева, без перезаписи) и
31
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 движка.
32
+ 5. Выставляет `HARNESS_REPORT_PATH` и `DEVCONTOUR_REPORT_PATH` = `<scratch>/<id>.xml`,
33
+ `DEVCONTOUR_PROPERTY_REPORT` = `<scratch>/<id>.property.json` (то же имя, что у gate движка),
34
+ `CI=1`; у `test` gate ещё `TURBO_FORCE=true` и `GOFLAGS=… -count=1`, как у gate движка.
35
+ Проверка, которая пишет отчёт свойств по пути из переменной, под probe пишет его в scratch, а
36
+ не получает EPERM в дереве только для чтения. `DEVCONTOUR_SEED`, `DEVCONTOUR_PROPERTIES` и
37
+ `DEVCONTOUR_CORPUS` probe не выставляет: генеративные gate он не исполняет.
34
38
  6. Запускает команду в песочнице: запись — только в scratch и `TMPDIR`; дерево, `.git`,
35
39
  каталог controller и прежние `.reports/*.xml` закрыты на запись.
36
40
  7. Читает отчёт: symlink, жёсткая ссылка, путь вне scratch — `unsafe` (не читается); файл старше
37
41
  запуска — `stale`; не JUnit или без testcases — `corrupt`; нет файла — `missing`.
38
- 8. Сохраняет замаскированную копию отчёта (`<id>.xml`), журнал (`<id>.log`) и `run.json`, затем
42
+ 8. Сохраняет замаскированную копию отчёта (`<id>.xml`), отчёта свойств, если он записан
43
+ (`<id>.property.json`, `gate.propertyReport` = `{path, scope: "probe-only"}`: движок его не
44
+ читает), журнал (`<id>.log`) и `run.json`, затем
39
45
  удаляет scratch. Таймаут и отмена (`notifications/cancelled`) останавливают процессы проверки,
40
46
  evidence сохраняется тоже.
41
47
 
@@ -121,6 +121,15 @@ DevContour хранит `Evidence` как есть; все новые поля
121
121
  логического ревью). Непринятый результат содержит и записи невалидных
122
122
  вызовов (`review.protocol: invalid` с их причинами).
123
123
 
124
+ - `alreadySatisfied` — у записи ревью попытки без изменений (кандидат —
125
+ база): gates и основание, которыми reviewer подтвердил, что приёмка уже
126
+ выполнена. Поле ответа reviewer `alreadySatisfied` (`null` — нет
127
+ подтверждения) входит в схему только для такой попытки; обычное ревью
128
+ отвечает прежней схемой. Одобрение без годного подтверждения записывается
129
+ непрошедшим, отказ попытки — `empty-result`; без механического основания
130
+ (`basis`: договор проверки или зависимость, изменившая область записи) —
131
+ `needs-lead`.
132
+
124
133
  ## 6. Записи alpha.5 (legacy)
125
134
 
126
135
  Запись ревью без `reviewRound` — запись прежней версии. Её находки (у
package/docs/upgrade.md CHANGED
@@ -21,20 +21,22 @@
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 | Пишет поверх | Примечание |
25
- | ------------------------------------------------------------------------------ | ------------------------ | ------------------------ | ----------------------- | -------------------- | -------------------- | -------------------- | --------------------- | --------------------- | --------------------- | --------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
26
- | standalone `stateVersion: 1` версией alpha.1 | да | да | да | да | да | да | да | да | да | да | да | одна база SQLite: документ, события, локальные записи |
27
- | хост, центральная база | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | данные не переносятся; новые поля необязательны |
28
- | хост, базы компонентов | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | нужна копия всех баз при остановленных writers |
29
- | alpha.2 после штатной остановки controller | да | да | да | да | да | да | да | да | да | да | да | роль controller снята — нового поля в состоянии нет |
30
- | alpha.2 с живой или брошенной ролью controller | **нет**, отказ до записи | да | да | да | да | да | да | да | да | да | — | роль хранит `engineVersion`; строгая схема alpha.1 его не знает → откат через копию |
31
- | alpha.4 с совместной проверкой | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | — | проверка хранит `engineVersion`; прежние версии его не знают → откат через копию |
32
- | alpha.6 standalone с записями ревью нового протокола или доставкой `uncertain` | **нет**, отказ до записи | **нет**, отказ до записи | да | да | да | да | да | да | да | да | — | `reviewRound`, `findings[].id/status`, статус доставки `uncertain`; alpha.4/alpha.5 отказывают `STATE_VERSION_UNSUPPORTED` до записи → откат через копию |
33
- | alpha.6, хост DevContour (адаптер alpha.4) | — | — | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да (через адаптер) | да | адаптер DevContour alpha.4 открывает такое состояние без отказа и пишет роль controller и события; новые поля сохраняются → для отката всё равно копия |
34
- | alpha.7 с новым `inspection` ревью кода | не проверялось | не проверялось | **нет** (строгая схема) | да | да | да | да | да | да | да | — | новые значения `source`, `exitCode: null`, `origins`/`context`/`excluded`; строгая схема alpha.6 их не принимает, поведение прежних версий при открытии не проверялось → откат через копию |
35
- | неизвестная `stateVersion` | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет, отказ до записи | нет | `STATE_VERSION_UNSUPPORTED`, exit 4 |
36
-
37
- Столбец alpha.2 относится и к alpha.3; alpha.4 и alpha.5 читают все строки, кроме неизвестной `stateVersion` и строк alpha.6 с новыми полями (standalone — отказ до записи) и строки alpha.7. alpha.8, alpha.9, alpha.10, alpha.11, alpha.12 и alpha.13 формат состояния не меняют и читают всё, что читает alpha.7. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
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. Каждая следующая версия добавляет сюда свой столбец. Отказ неизвестного
38
40
  формата до записи проверяют тесты совместимости пакета
39
41
  <!-- repo-only -->(`tests/compatibility.test.ts`)<!-- /repo-only -->.
40
42
 
@@ -8,7 +8,7 @@ import { redactor } from "../environment/redaction.js";
8
8
  import { inspectReview } from "./inspect.js";
9
9
  import { probeCallMs, probeServer, probeTool } from "./review-probe.js";
10
10
  import { claudeResult, runtimeEvents } from "./runtime-events.js";
11
- import { parseUsage, reportedModel, runtimeVersion, unknownUsage } from "./usage.js";
11
+ import { runtimeVersion, unknownUsage, usageStream } from "./usage.js";
12
12
  /** Встроенные инструменты исполнителя claude, когда профиль их не называет; shell — в песочнице ОС. */
13
13
  export const claudeWriterTools = ['Read', 'Glob', 'Grep', 'Edit', 'Write', 'Bash'];
14
14
  const writerTools = (profile) => profile?.claudeTools ?? claudeWriterTools;
@@ -283,12 +283,17 @@ export function cliAdapter(runtime, options = {}) {
283
283
  // короткий секрет портит `item.completed`), текст полей маскируется
284
284
  // при разборе; сырой поток только в памяти и не сохраняется.
285
285
  let raw = '';
286
+ // Телеметрия — по всему потоку, независимо от пределов журнала и
287
+ // хвоста stdout: итоговое событие долгого вызова лежит за ними.
288
+ const telemetry = usageStream(runtime);
286
289
  try {
287
290
  result = await command(argv, r.cwd, {
288
291
  contain: { dirs: review ? [] : r.sandbox.writable },
289
- ...(review
290
- ? { onRawStdout: (text) => void (raw = (raw + text).slice(-2_000_000)) }
291
- : {}),
292
+ onRawStdout: (text) => {
293
+ telemetry.push(text);
294
+ if (review)
295
+ raw = (raw + text).slice(-2_000_000);
296
+ },
292
297
  onOutput: (stream, text) => {
293
298
  if (logged >= 10_000_000)
294
299
  return;
@@ -312,12 +317,11 @@ export function cliAdapter(runtime, options = {}) {
312
317
  return unavailable(`${runtime}: runtime не запускается: ${String(error)}`);
313
318
  throw error;
314
319
  }
315
- await writeFile(join(dir, 'runtime.json'), JSON.stringify(result.diagnostics, null, 2));
316
- const usage = parseUsage(runtime, result.stdout, result.code === 0 &&
317
- !result.timedOut &&
318
- !signal.aborted &&
319
- result.stdout.length < 2_000_000);
320
- const model = reportedModel(result.stdout);
320
+ const usage = telemetry.usage(result.code === 0 && !result.timedOut && !signal.aborted);
321
+ const model = telemetry.model();
322
+ // Сообщённые runtime счётчики и стоимость — и в диагностике вызова:
323
+ // runtime.log ограничен и итоговой строки может не содержать.
324
+ await writeFile(join(dir, 'runtime.json'), JSON.stringify({ ...result.diagnostics, usage }, null, 2));
321
325
  const bookkeeping = ['system', 'result', 'error', 'session.created', 'thread.started'];
322
326
  const started = result.timedOut ||
323
327
  runtimeEvents(result.stdout).events.some((e) => !bookkeeping.includes(String(e.type)));
@@ -118,6 +118,15 @@ export type ProbeGateRun = {
118
118
  /** Имена переменных, которые выставил controller (значения не пишутся). */
119
119
  controllerEnv: string[];
120
120
  report?: ProbeReport;
121
+ /**
122
+ * Копия отчёта свойств, если проверка записала его по пути
123
+ * `DEVCONTOUR_PROPERTY_REPORT` в scratch. Только evidence probe: движок
124
+ * этот отчёт не читает, и свойства по нему не засчитываются.
125
+ */
126
+ propertyReport?: {
127
+ path: string;
128
+ scope: 'probe-only';
129
+ };
121
130
  /** Каталог evidence запуска в каталоге ревью controller. */
122
131
  artifacts?: string;
123
132
  /** Временный каталог отчёта; убран после сохранения evidence. */
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
  import { z } from 'zod';
10
10
  import { idSchema, relativePathSchema, sha256, } from "../contracts/index.js";
11
11
  import { redactor } from "../environment/redaction.js";
12
- import { completedCheck, freshnessEnv, junitSummary, runCheck, sandboxBlockedHosts, } from "../verification/gates.js";
12
+ import { completedCheck, freshnessEnv, propertyReportEnv, junitSummary, runCheck, sandboxBlockedHosts, } from "../verification/gates.js";
13
13
  /**
14
14
  * Пробные проверки reviewer под пределами harness.
15
15
  *
@@ -112,6 +112,21 @@ export function fixedReportArgument(argv, cwd, report) {
112
112
  }
113
113
  return undefined;
114
114
  }
115
+ /**
116
+ * Копия отчёта свойств из scratch в evidence probe. Как и JUnit: symlink,
117
+ * жёсткая ссылка, файл вне scratch и слишком большой файл не читаются.
118
+ */
119
+ async function savePropertyReport(path, scratch, runDir, id, redact) {
120
+ const stat = await lstat(path).catch(() => undefined);
121
+ if (!stat?.isFile() || stat.nlink > 1 || stat.size > REPORT_LIMIT)
122
+ return undefined;
123
+ const real = await realpath(path);
124
+ if (!within(dirname(real), scratch))
125
+ return undefined;
126
+ const saved = join(runDir, `${id}.property.json`);
127
+ await writeFile(saved, redact(await readFile(real, 'utf8')), { flag: 'wx' });
128
+ return { path: saved, scope: 'probe-only' };
129
+ }
115
130
  /**
116
131
  * Отчёт запуска gate в scratch controller. Symlink, жёсткая ссылка и путь вне
117
132
  * scratch не читаются: так отчёт подменялся бы чужим файлом — прежним
@@ -458,12 +473,18 @@ export class ReviewProbes {
458
473
  }
459
474
  meta.scratch = scratch;
460
475
  const reportPath = join(scratch, `${gate.id}.xml`);
476
+ // Отчёт свойств — тоже в scratch, под тем же именем переменной, что и у
477
+ // gate движка (`propertyReportEnv`): проверка, пишущая отчёт свойств по
478
+ // прежнему пути в дереве, под probe получала бы EPERM. Seed, селектор и
479
+ // корпус probe не выставляет: генеративные gate он не исполняет.
480
+ const propertyPath = join(scratch, `${gate.id}.property.json`);
461
481
  const controllerEnv = {
462
482
  CI: '1',
463
483
  ...freshnessEnv(gate.kind, env),
464
484
  ...(gate.report
465
485
  ? { DEVCONTOUR_REPORT_PATH: reportPath, HARNESS_REPORT_PATH: reportPath }
466
486
  : {}),
487
+ [propertyReportEnv]: propertyPath,
467
488
  };
468
489
  meta.controllerEnv = Object.keys(controllerEnv);
469
490
  const started = Date.now();
@@ -493,6 +514,7 @@ export class ReviewProbes {
493
514
  const read = gate.report
494
515
  ? await inspectReport(reportPath, scratch, started, redact)
495
516
  : undefined;
517
+ meta.propertyReport = await savePropertyReport(propertyPath, scratch, runDir, gate.id, redact);
496
518
  if (read) {
497
519
  meta.report = read.report;
498
520
  if (read.xml !== undefined) {
@@ -164,6 +164,9 @@ export declare const reviewOutputSchema: {
164
164
  input: {
165
165
  type: string;
166
166
  };
167
+ inputClass: {
168
+ type: string[];
169
+ };
167
170
  expected: {
168
171
  type: string[];
169
172
  };
@@ -215,3 +218,206 @@ export declare const reviewOutputSchema: {
215
218
  };
216
219
  required: string[];
217
220
  };
221
+ /**
222
+ * Ответ reviewer попытки без изменений: тот же протокол и обязательное
223
+ * `alreadySatisfied` (`null` — подтверждения нет). Обычное ревью этого поля
224
+ * не получает: его вид ответа не меняется.
225
+ */
226
+ export declare const alreadySatisfiedReviewOutputSchema: {
227
+ properties: {
228
+ alreadySatisfied: {
229
+ anyOf: ({
230
+ type: string;
231
+ additionalProperties?: undefined;
232
+ properties?: undefined;
233
+ required?: undefined;
234
+ } | {
235
+ type: string;
236
+ additionalProperties: boolean;
237
+ properties: {
238
+ gates: {
239
+ type: string;
240
+ minItems: number;
241
+ maxItems: number;
242
+ items: {
243
+ type: string;
244
+ };
245
+ };
246
+ evidence: {
247
+ type: string;
248
+ };
249
+ };
250
+ required: string[];
251
+ })[];
252
+ };
253
+ execution: {
254
+ type: string;
255
+ additionalProperties: boolean;
256
+ properties: {
257
+ commands: {
258
+ type: string;
259
+ maxItems: number;
260
+ items: {
261
+ type: string;
262
+ additionalProperties: boolean;
263
+ properties: {
264
+ command: {
265
+ type: string;
266
+ };
267
+ exitCode: {
268
+ type: string;
269
+ };
270
+ summary: {
271
+ type: string;
272
+ };
273
+ };
274
+ required: string[];
275
+ };
276
+ };
277
+ noCommandsReason: {
278
+ type: string[];
279
+ };
280
+ };
281
+ required: string[];
282
+ };
283
+ approved: {
284
+ type: string;
285
+ };
286
+ summary: {
287
+ type: string;
288
+ };
289
+ discoveries: {
290
+ type: string;
291
+ maxItems: number;
292
+ items: {
293
+ type: string;
294
+ additionalProperties: boolean;
295
+ properties: {
296
+ kind: {
297
+ type: string;
298
+ enum: string[];
299
+ };
300
+ title: {
301
+ type: string;
302
+ };
303
+ path: {
304
+ type: string;
305
+ };
306
+ line: {
307
+ type: string;
308
+ minimum: number;
309
+ };
310
+ observation: {
311
+ type: string;
312
+ };
313
+ consequence: {
314
+ type: string;
315
+ };
316
+ reproduction: {
317
+ type: string;
318
+ };
319
+ };
320
+ required: string[];
321
+ };
322
+ };
323
+ findings: {
324
+ type: string;
325
+ items: {
326
+ type: string;
327
+ additionalProperties: boolean;
328
+ properties: {
329
+ severity: {
330
+ type: string;
331
+ enum: string[];
332
+ };
333
+ message: {
334
+ type: string;
335
+ };
336
+ path: {
337
+ type: string[];
338
+ };
339
+ line: {
340
+ type: string[];
341
+ minimum: number;
342
+ };
343
+ rule: {
344
+ type: string[];
345
+ };
346
+ consequence: {
347
+ type: string[];
348
+ };
349
+ evidence: {
350
+ type: string[];
351
+ };
352
+ reproduction: {
353
+ anyOf: ({
354
+ type: string;
355
+ additionalProperties?: undefined;
356
+ properties?: undefined;
357
+ required?: undefined;
358
+ } | {
359
+ type: string;
360
+ additionalProperties: boolean;
361
+ properties: {
362
+ property: {
363
+ type: string[];
364
+ };
365
+ input: {
366
+ type: string;
367
+ };
368
+ inputClass: {
369
+ type: string[];
370
+ };
371
+ expected: {
372
+ type: string[];
373
+ };
374
+ actual: {
375
+ type: string[];
376
+ };
377
+ command: {
378
+ type: string[];
379
+ items: {
380
+ type: string;
381
+ };
382
+ };
383
+ executed: {
384
+ type: string;
385
+ };
386
+ run: {
387
+ type: string[];
388
+ };
389
+ };
390
+ required: string[];
391
+ })[];
392
+ };
393
+ };
394
+ required: string[];
395
+ };
396
+ };
397
+ previous: {
398
+ type: string;
399
+ items: {
400
+ type: string;
401
+ additionalProperties: boolean;
402
+ properties: {
403
+ id: {
404
+ type: string;
405
+ pattern: string;
406
+ };
407
+ status: {
408
+ type: string;
409
+ enum: ("open" | "closed" | "withdrawn")[];
410
+ };
411
+ basis: {
412
+ type: string;
413
+ pattern: string;
414
+ };
415
+ };
416
+ required: string[];
417
+ };
418
+ };
419
+ };
420
+ required: string[];
421
+ type: string;
422
+ additionalProperties: boolean;
423
+ };
@@ -80,13 +80,23 @@ export const reviewOutputSchema = {
80
80
  properties: {
81
81
  property: { type: ['string', 'null'] },
82
82
  input: { type: 'string' },
83
+ inputClass: { type: ['string', 'null'] },
83
84
  expected: { type: ['string', 'null'] },
84
85
  actual: { type: ['string', 'null'] },
85
86
  command: { type: ['array', 'null'], items: { type: 'string' } },
86
87
  executed: { type: 'boolean' },
87
88
  run: { type: ['string', 'null'] },
88
89
  },
89
- required: ['property', 'input', 'expected', 'actual', 'command', 'executed', 'run'],
90
+ required: [
91
+ 'property',
92
+ 'input',
93
+ 'inputClass',
94
+ 'expected',
95
+ 'actual',
96
+ 'command',
97
+ 'executed',
98
+ 'run',
99
+ ],
90
100
  },
91
101
  ],
92
102
  },
@@ -122,3 +132,29 @@ export const reviewOutputSchema = {
122
132
  },
123
133
  required: ['approved', 'summary', 'findings', 'discoveries', 'execution', 'previous'],
124
134
  };
135
+ /**
136
+ * Ответ reviewer попытки без изменений: тот же протокол и обязательное
137
+ * `alreadySatisfied` (`null` — подтверждения нет). Обычное ревью этого поля
138
+ * не получает: его вид ответа не меняется.
139
+ */
140
+ export const alreadySatisfiedReviewOutputSchema = {
141
+ ...reviewOutputSchema,
142
+ properties: {
143
+ ...reviewOutputSchema.properties,
144
+ alreadySatisfied: {
145
+ anyOf: [
146
+ { type: 'null' },
147
+ {
148
+ type: 'object',
149
+ additionalProperties: false,
150
+ properties: {
151
+ gates: { type: 'array', minItems: 1, maxItems: 20, items: { type: 'string' } },
152
+ evidence: { type: 'string' },
153
+ },
154
+ required: ['gates', 'evidence'],
155
+ },
156
+ ],
157
+ },
158
+ },
159
+ required: [...reviewOutputSchema.required, 'alreadySatisfied'],
160
+ };
@@ -6,3 +6,25 @@ export declare function parseUsage(runtime: 'codex' | 'claude', stdout: string,
6
6
  export declare function runtimeVersion(executable: string, env?: NodeJS.ProcessEnv): Promise<string | null>;
7
7
  /** Модель, которую runtime выбрал на самом деле: событие system у claude, ключи modelUsage у codex. */
8
8
  export declare function reportedModel(stdout: string): string | null;
9
+ /** Строка потока длиннее — не разбирается и делает телеметрию неполной. */
10
+ export declare const LINE_LIMIT = 8000000;
11
+ /**
12
+ * Телеметрия, собранная по ходу потока runtime, а не из хвоста вывода. Хвост
13
+ * stdout и журнал вызова ограничены, и у долгого вызова итоговое событие
14
+ * `result` (стоимость и счётчики claude) или ранние `turn.completed` codex
15
+ * оказывались за пределом: стоимость, сообщённая провайдером, терялась или
16
+ * учитывалась неполной. Здесь каждая строка разбирается сразу; хранятся
17
+ * только поля телеметрии — последнее событие `result`, события
18
+ * `turn.completed` и первая сообщённая модель, — а не текст вывода.
19
+ */
20
+ export declare function usageStream(runtime: 'codex' | 'claude'): {
21
+ /**
22
+ * Очередной фрагмент stdout runtime (без маски: берутся только числа).
23
+ * Вызывается из обработчика вывода процесса и не бросает: сбой разбора —
24
+ * неполная телеметрия, а не падение controller.
25
+ */
26
+ push(text: string): void;
27
+ /** Телеметрия всего потока; `completed` — вызов завершился штатно. */
28
+ usage(completed: boolean): Usage;
29
+ model: () => string | null;
30
+ };