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

fix(rules-napi): порожній fix-батч не-full-scope концерну — гучна помилка - #517

Merged
vitaliytv merged 1 commit into
mainfrom
fix/napi-empty-batch
Aug 28, 2026
Merged

vitaliytv merged 1 commit into
mainfrom
fix/napi-empty-batch

Conversation

@vitaliytv

Copy link
Copy Markdown
Member

#513 додав full-scope fallback, бо whole-batch концерн діставав порожній FixRequest::files і гість затирав конфіги замість merge.

Але умова фіксу — scope == Full. Гілка _ => Vec::new() і далі мовчки віддавала порожній батч будь-якому не-full-scope концерну з file-less діагностиками. Другий незалежний прояв того самого класу.

Контракт мовчить — і це головна відповідь задачі

diagnostic.file: option<string> ніде не обумовлений scope. Тож агрегована діагностика без file для per-file концерну — легітимний результат detect().

Але full-scope glob-обхід тут не безпечний: він розширив би fix за межі дельти, порушивши scope, який концерн сам заявив.

Отже ні «помилка концерну», ні «розширити fallback» не є правильними беззастережно. Замість вгадування — типізована помилка саме на неоднозначному стані: target_files порожній, diagnostics не порожній, концерн не full. Порожні diagnostics і далі дають порожній план без помилки.

Відкрите питання винесено у WIT-доккомент біля record diagnostic, із двома явними варіантами для того, хто вирішуватиме: заборонити file: none для per-file, або дати спосіб нести per-file-дельту без per-diagnostic атрибуції.

Деградація, не падіння прогону

JS-бік ловить помилку в computeWasmFixPlan — console.error і порожній план. Той самий graceful-degrade контур, що вже несуть loadT0Patterns/runConcernDetector: падіння одного концерну не вбиває весь fix-прогін.

Виконавець спростував два твердження з мого брифу

rust/check НЕ є другим кандидатом на цю ваду — він scope = "full", тобто вже йшов безпечною гілкою. Перевірив особисто в plugin-lang-rust/plugin.toml. Це виправляє й опис #514, де я повторив цю помилку.

Посилання на §2.49/§2.51 він відмовився приймати за факт — їх немає на main, вони живуть у ще не змержених PR. Правильна обережність: я процитував їх так, ніби вони вже там.

Тести

rules-napi 10 (з новою guest-фікстурою test/guest-fix-full-scope), wasm-fix-e2e + parity lang-js разом 265.

🤖 Generated with Claude Code

…илка

#513 додав full-scope fallback, бо whole-batch концерн діставав порожній
`FixRequest::files` і гість затирав конфіги замість merge. Але умова фіксу —
`scope == Full`, тож гілка `_ => Vec::new()` і далі мовчки віддавала порожній
батч будь-якому не-full-scope концерну з file-less діагностиками. Другий
незалежний прояв того самого класу.

Контракт МОВЧИТЬ про цей випадок, і це головна відповідь задачі:
`diagnostic.file: option<string>` ніде не обумовлений `scope`, тож агрегована
діагностика без `file` для per-file концерну — легітимний результат `detect()`.
Але full-scope glob-обхід тут НЕ безпечний: він розширив би fix за межі
дельти, порушивши scope, який концерн сам заявив.

Отже ні «помилка концерну», ні «розширити fallback» не є правильними
беззастережно — і замість вгадування додано типізовану помилку саме на
неоднозначному стані: `target_files` порожній, `diagnostics` НЕ порожній,
концерн не `full`. Порожні `diagnostics` і далі дають порожній план без
помилки.

Відкрите питання винесено у WIT-доккомент біля `record diagnostic` із двома
явними варіантами для того, хто вирішуватиме: заборонити `file: none` для
per-file, або дати спосіб нести per-file-дельту без per-diagnostic атрибуції.

JS-бік ловить помилку в `computeWasmFixPlan` — `console.error` і деградація до
порожнього плану, той самий graceful-degrade контур, що вже несуть
`loadT0Patterns`/`runConcernDetector`: падіння одного концерну не вбиває
весь fix-прогін.

Виконавець спростував твердження з мого брифу: `rust/check` НЕ є другим
кандидатом на цю ваду — він `scope = "full"`, тобто вже йшов безпечною гілкою.
Перевірено особисто в `plugin-lang-rust/plugin.toml`.

Він також відмовився приймати за факт мої посилання на §2.49/§2.51 — їх немає
на main, вони живуть у ще не змержених PR. Правильна обережність.

Тести: rules-napi 10 (з фікстурою `test/guest-fix-full-scope`), wasm-fix-e2e
і parity lang-js разом 265.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vitaliytv
vitaliytv force-pushed the fix/napi-empty-batch branch from 952b20d to b7f216b Compare August 28, 2026 07:59
@vitaliytv
vitaliytv merged commit e908982 into main Aug 28, 2026
1 of 6 checks passed
vitaliytv added a commit that referenced this pull request Aug 28, 2026
…о питання #517 (#519)

#517 зробив неоднозначний порожній fix-батч гучною помилкою і лишив розвилку
у WIT-доккоменті: заборонити `file: none` для `per-file` (а) чи дати спосіб
нести дельту без per-diagnostic атрибуції (б). Обрано (б).

Корінь був не в тому, що «контракт мовчить», а в асиметрії двох napi-сигнатур:
`run_wasm_concern` ПРИЙМАЄ дельту явним параметром, `run_wasm_concern_fix`
реконструює її з `diagnostics[].file`. Інформація в системі була — JS тримає
`item.files` і сусіднім викликом віддає її в `detect`. Прогалина проводки, не
знання.

Розширювати WIT не довелось: `fix-request.files` існує з 3.0.0 як окреме поле,
не похідне від `diagnostics`. Додано шостий опційний `delta_files` у napi й
проводку `item.files` → `loadT0Patterns` → `wasmFixPattern`. Жодного ABI-руху,
guest-и не чіпані.

Порядок джерел `FixRequest::files`: `diagnostics[].file` → full-scope glob для
`scope: full` → явна дельта → типізована помилка. Помилка #517 лишається
backstop-ом на проводку; порожня дельта за відповідь не приймається.

Знімає «Перешкоду 1» з розбору порту `python/ruff` (#516) — той лишається
відкладеним тільки через межу exec-tool.

Тести: cargo rules-napi 10 → 13, новий fix-delta-files.test.mjs (2) на
host-проводку. Обидва якорі перевірено дією — нейтралізація гілки валить рівно
rust-тест, зняття аргументу — рівно JS-тест.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
vitaliytv added a commit that referenced this pull request Aug 28, 2026
…а) (#520)

JS-міст fix-плану (nativeFixPattern/wasmFixPattern у run-fix.mjs) робив
голий unlink() на delete-правку в try{}catch{} — на директорію unlink
падає (EPERM/EISDIR), а catch{} ковтав будь-яку помилку, не лише
"шлях уже відсутній". Гість, що емітить delete на теку (напр. .firebase/,
.yarn), лишався зеленим і безрезультатним — той самий клас вади, що #513/#517.

Rust-бік (t0.rs::to_edit_plan) уже вміє розгортати Delete-теку в пофайлові
Delete + прибирати спорожнілі теки після commit. Обидва майже однакові
цикли злиті у спільний applyPlanEdit(): delete тепер іде через
fs.rm(recursive:true) — той самий кінцевий стан, без потреби у per-file
журнальній машинерії Rust-боку. Ловиться лише ENOENT (задокументований
best-effort); будь-яка інша помилка кидає Error(cause) і зупиняє прогін.

Заразом виправлено застарілий доккомент fix_abie_security.rs, що
описував уже видалений apply_plan і стверджував, що виконавець не вміє
видаляти директорії.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant