Skip to content
This repository was archived by the owner on Sep 11, 2026. It is now read-only.

Commit 39ee4e4

Browse files
authored
fix(lint-surface): test-gate veto — ловить in-file collateral, яку не бачить collateral-veto (#205)
collateral-veto.mjs детектує колатеральні правки rung-а лише ПОЗА target-set (інші файли), а canonical re-detect бачить лише той самий детектор/порушення. Обидва пропускають правки ВСЕРЕДИНІ вже-таргетованого файлу — реальний кейс: LLM-рунг видалив задокументований воркараунд Bun SQL-бага поряд із фіксованим порушенням у upsert-order.js, зламавши проєктний тест, і clean-вердикт все одно був прийнятий. Додає test-gate.mjs: якщо змінений target-set файл має сестринський тест-файл за конвенцією `<dir>/tests/<stem>.test.{mjs,js,ts}` (n-test.mdc), той тест виконується через bunx vitest як частина verify. Провал → той самий шлях rollback/feedback/телеметрія, що й collateral-veto (kind:"test-gate-veto"). Fail-open за дизайном: відсутній test-runner/таймаут/відсутність сестринського тесту не блокує ladder.
1 parent 7d1dbe5 commit 39ee4e4

7 files changed

Lines changed: 311 additions & 12 deletions

File tree

‎npm/scripts/lib/lint-surface/collateral-veto.mjs‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ export function realpathBestEffort(p) {
3737
}
3838
}
3939

40+
/**
41+
* Нормалізує target-set порушення у множину realpath-абсолютних шляхів — спільна
42+
* основа і для collateral-veto (файли ПОЗА target-set), і для test-gate
43+
* (файли ВСЕРЕДИНІ target-set, для яких перевіряються сестринські тести).
44+
* @param {string[]} targetFiles Файли порушення, відносні до cwd або абсолютні.
45+
* @param {string} cwd Робоча директорія для резолву відносних шляхів.
46+
* @returns {Set<string>} Нормалізовані абсолютні шляхи target-set (може бути порожньою).
47+
*/
48+
export function resolveTargetSet(targetFiles, cwd) {
49+
return new Set(targetFiles.map(f => realpathBestEffort(isAbsolute(f) ? f : resolve(cwd, f))))
50+
}
51+
4052
/**
4153
* Обчислює collateral-правки rung-а: наявні (на момент S1) файли, змінені поза
4254
* target-set порушення. Runner на непорожньому результаті відхиляє clean-вердикт
@@ -49,7 +61,7 @@ export function realpathBestEffort(p) {
4961
* veto не спрацював (нема collateral або target-set невідомий)
5062
*/
5163
export function findCollateralEdits({ modifiedExisting, targetFiles, cwd }) {
52-
const targets = new Set(targetFiles.map(f => realpathBestEffort(isAbsolute(f) ? f : resolve(cwd, f))))
64+
const targets = resolveTargetSet(targetFiles, cwd)
5365
if (targets.size === 0) return []
5466
return modifiedExisting.map(p => realpathBestEffort(p)).filter(abs => !targets.has(abs))
5567
}

‎npm/scripts/lib/lint-surface/docs/collateral-veto.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ type: JS Module
33
title: collateral-veto.mjs
44
resource: npm/scripts/lib/lint-surface/collateral-veto.mjs
55
docgen:
6-
crc: 16d3ef02
6+
crc: a0b41453
77
---
88

99
## Огляд
1010

11-
Semantic-collateral veto для verdict-фази fix-pipeline (spec pi-fix-engine-migration §12, addendum 2026-07-05). Закриває клас collateral слабких локальних моделей: «виправляючи» правило, модель робить семантичну правку у сторонньому файлі, яка не порушує жодного правила й тому проходить canonical re-detect (кейс App.vue: хардкод версії з коментарем «we simulate it being available» замість виклику `getVersion`).
11+
Semantic-collateral veto для verdict-фази fix-pipeline (spec pi-fix-engine-migration §12, addendum 2026-07-05). Закриває клас collateral слабких локальних моделей: «виправляючи» правило, модель робить семантичну правку у сторонньому файлі, яка не порушує жодного правила й тому проходить canonical re-detect (кейс App.vue: хардкод версії з коментарем «we simulate it being available» замість виклику `getVersion`). Правки ВСЕРЕДИНІ вже-таргетованого файлу цей модуль не ловить — цей клас (упущений workaround поряд із фіксом) покриває [test-gate.mjs](./test-gate.md) (addendum 2026-07-24).
1212

1313
## Поведінка
1414

@@ -20,6 +20,7 @@ Semantic-collateral veto для verdict-фази fix-pipeline (spec pi-fix-engin
2020
## Публічний API
2121

2222
- `findCollateralEdits({ modifiedExisting, targetFiles, cwd })` — нормалізовані абсолютні шляхи відхилених правок; порожньо — collateral немає або target-set невідомий.
23+
- `resolveTargetSet(targetFiles, cwd)` — нормалізована множина realpath-абсолютних шляхів target-set; спільна основа для collateral-veto (файли ПОЗА target-set) і test-gate (файли ВСЕРЕДИНІ target-set).
2324
- `realpathBestEffort(p)` — realpath з найкращих зусиль (наявний файл → повний realpath; неіснуючий → realpath батьківської теки + basename; інакше — як є).
2425

2526
## Гарантії поведінки

‎npm/scripts/lib/lint-surface/docs/run-fix.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ type: JS Module
33
title: run-fix.mjs
44
resource: npm/scripts/lib/lint-surface/run-fix.mjs
55
docgen:
6-
crc: 45965f7d
6+
crc: 8ea2e954
77
model: manual
88
---
99

@@ -18,6 +18,7 @@ fixConcern застосовує детерміновані патерни (T0),
1818
runFixPipeline керує повним циклом виправлення: він детектирує всі порушення, застосовує виправлення для кожного знайденого concern-а через `fixConcern`, і виводить фінальний звіт про нездоланні порушення.
1919
Per-tier timeout (ADR 260620-0556): кожен rung передає worker-у свій `timeoutMs` через `FixContext` (шлях до `runAgentFix opts.timeoutMs`, який abort-ить LLM-сесію), а сам виклик worker-а додатково огорнутий backstop-гонкою ×1.25 від `rung.timeoutMs` — worker, що ігнорує таймаут (зокрема зависла cloud-SSE), фейлить rung помилкою `fix timeout …` замість блокувати lint назавжди; така помилка класифікується як quality і ladder ескалює далі.
2020
Semantic-collateral veto (spec pi-fix-engine-migration §12, addendum 2026-07-05): clean-вердикт rung-а не приймається, якщо rung змінив наявні файли поза target-set порушення (`violations[].file ∪ item.files`, звірка через `collateral-veto.mjs` за `snapshot.modifiedExisting()`); наслідок — rollback S1, `🚫`-лог, feedback наступному rung-у й телеметрія `kind:"collateral-veto"` у глобальний llm-trace. Нові файли дозволені; порожній target-set → veto незастосовний (fail-open).
21+
Test-gate veto (addendum 2026-07-24): collateral-veto ловить лише правки ПОЗА target-set — правки ВСЕРЕДИНІ вже-таргетованого файлу (напр. видалений задокументований workaround поруч із фіксом порушення) не зачіпають жодного детектора й проходять і collateral-veto, і canonical re-detect. Коли `after.length === 0 && !error && collateral.length === 0`, `runRung` бере наявні файли з target-set, реально змінені rung-ом (`snapshot.modifiedExisting()` ∩ `resolveTargetSet`), і через `findBrokenSiblingTests` (`test-gate.mjs`) шукає й виконує їхні сестринські тест-файли (`<dir>/tests/<stem>.test.{mjs,js,ts}`, n-test.mdc). Провал такого тесту → той самий шлях rollback/feedback/телеметрія, що й collateral-veto, але `kind:"test-gate-veto"`. Fail-open за дизайном: відсутній test-runner, таймаут чи відсутність сестринського тесту — не блокує rung. `deps.testRunner` — override test-runner-а для тестів цього модуля (`runFixPipeline`/`fixConcern`/`runRung`).
2122
Evidence-гейт рунга (Фаза A1 run-harness, спека 2026-07-11): кожен rung отримує у `FixContext` `verify` — item-scoped canonical re-detect (той самий детектор, що й фінальний вердикт), і `verifyMax` per tier (local — 1, cloud — 2); worker прокидає їх у `runAgentFix`, де провал verify інʼєктиться фідбеком у ту саму pi-сесію. Зовнішній canonical re-detect після worker-а лишається єдиним вердиктом рунга; помилка детектора всередині verify ковтається у `{ok:false}` — зовнішній detect кине її штатно.
2223
Durable-write-и (issue nitra/cursor#16): worker отримує у `FixContext` поруч із `recordWrite` опційний `recordDurableWrite` — для записів, кожен з яких є самодостатнім кінцевим станом (doc-files: дока зі свіжим CRC). Такі файли переживають rollback провального rung-а і не входять у collateral-veto: частковий прогрес великого батчу не стирається, canonical re-detect наступного rung-а/прогону рахує лише те, що реально лишилось.
2324
MT-tail (Фаза B, спека 2026-07-11): коли лишився невиправлений хвіст (worst=1), `renderRemaining` повертає зібрані порушення, і вони матеріалізуються у вузли MT-графа через `materializeTail` (mt-tail.mjs). Єдиний гейт — onboarded-репо (наявність `.mt.json`); fail-open: MT недоступний або будь-яка помилка → лог, lint не падає.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
type: JS Module
3+
title: test-gate.mjs
4+
resource: npm/scripts/lib/lint-surface/test-gate.mjs
5+
docgen:
6+
crc: 212d387b
7+
model: manual
8+
---
9+
10+
## Огляд
11+
12+
Test-gate верифікація для non-T0 (LLM) fix-ladder rung-ів (spec addendum 2026-07-24, ladder-collateral-in-file). Закриває клас колатеральних правок, які [collateral-veto.mjs](./collateral-veto.md) не бачить: правки ВСЕРЕДИНІ вже-таргетованого файлу (напр. видалення задокументованого workaround поруч із фіксованим порушенням) не зачіпають жодного детектора й не торкаються файлів поза target-set, тож проходять і collateral-veto, і canonical re-detect. Якщо для зміненого файлу з target-set існує сестринський тест-файл за конвенцією `<dir>/tests/<stem>.test.{mjs,js,ts}` (n-test.mdc), той тест виконується як частина verify — провал тесту відхиляє clean-вердикт rung-а так само, як і collateral-veto.
13+
14+
## Поведінка
15+
16+
1. `findSiblingTestFiles(sourceAbsPath)` конструює кандидатів `<dir>/tests/<stem>.test.{mjs,js,ts}` для джерела з розширенням `.mjs`/`.js`/`.ts`/`.vue` і повертає лише наявні на диску файли.
17+
2. `runTestFile(testAbsPath, cwd)` виконує один тест-файл через `bunx vitest run --reporter=verbose <file>` (`spawnSync`, таймаут 30s). Fail-open: spawn-помилка, відсутній `bunx`/vitest, чи таймаут (`status === null`) — повертає `passed: true`, а не блокує rung.
18+
3. `findBrokenSiblingTests({ files, cwd, runTest })` перебирає файли (наявні, у target-set, реально змінені rung-ом), для кожного шукає сестринські тести й запускає перший знайдений; перший провал зупиняє пошук і повертається як veto-кандидат. `runTest` — override test-runner-а для юніт-тестів цього модуля (типово `runTestFile`).
19+
20+
## Публічний API
21+
22+
- `findSiblingTestFiles(sourceAbsPath)` — наявні сестринські тест-файли джерела (може бути порожньо).
23+
- `runTestFile(testAbsPath, cwd)` — `{ passed, output }`, fail-open на інфраструктурну помилку.
24+
- `findBrokenSiblingTests({ files, cwd, runTest? })` — `{ file, testFile, output } | null`, перший зафіксований провал.
25+
26+
## Гарантії поведінки
27+
28+
- Fail-open за дизайном: відсутній test-runner, таймаут чи відсутність сестринського тесту ніколи не блокує fix-ladder.
29+
- Не редагує жодних файлів — лише читає (пошук тест-файлів) і запускає сторонній процес (vitest).

0 commit comments

Comments
 (0)