Skip to content

fix(extractor): --fix-candidates no longer drops orphaned_awaitables (OWN053) - #387

Merged
PhysShell merged 1 commit into
mainfrom
fix/fix-candidates-additive-envelope
Oct 2, 2026
Merged

PhysShell merged 1 commit into
mainfrom
fix/fix-candidates-additive-envelope

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

--fix-candidates аддитивен: он добавляет fix-метаданные и ничего больше не меняет. На деле он удалял секцию: с флагом из фактов пропадал orphaned_awaitables, и вместе с ним каждый advisory OWN053.

тот же вход, та же сборка (main 7053439)
--flow-locals                     orphaned_awaitables: 5 сайтов  -> 5 OWN053
--flow-locals --fix-candidates    orphaned_awaitables: отсутствует -> 0 OWN053

Причина: экстрактор строил конверт фактов в трёх местах, выбирая по вложенному условию (--fix-candidates ? A : orphans ? B : C). Конверт под --fix-candidates написан до появления orphaned_awaitables и его так и не получил.

Исправлен механизм, а не пропущенная строка: конверт один, строится один раз, в прежнем порядке ключей; необязательная секция — одна условная строка. Второго конверта, в котором можно забыть следующую секцию, больше нет.

Контроль S0 этого не видел не потому, что инвариант был слабым (он уже был «flag-on минус S0-поля == flag-off, весь документ»), а потому, что слабым был документ: в FixCandidatesSample.cs есть подписки и больше ни одной секции, и CI сканирует его без --flow-locals. Теперь:

  • tests/fixtures/fix_candidates/AdditiveSections.cs делает непустой каждую top-level секцию, которую экстрактор умеет писать (оба семейства OWN053 — без внешних ссылок);
  • check_fix_candidates_facts.py --additive-sections держит на нём равенство, читает список секций из самого конверта экстрактора (facts["<key>"] = ...) и отклоняет пару, в которой секция не задействована — новая секция в экстракторе будет красной, пока фикстура её не покрывает; дополнительно требует, чтобы референс давал одинаковые находки для обоих документов;
  • шаг CI «S0 fix-candidates — extractor metadata (Part A)» это запускает.

Словарь и версия OwnIR не меняются (OWNIR_VERSION 1, LOWERED_VERSION 2). Правило OWN053, lowering state-протоколов, T0 и scripts/perf_baseline.py не тронуты. В spec/OwnIR.md §2 добавлено правило аддитивности на стороне производителя.

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.py
  • ruff check . и mypy
  • селфтесты затронутых скриптов (python scripts/<...>.py --selftest)

На закоммиченной ветке, свежий Linux-клон (dotnet 8.0.422 и 9.0.315, ruff 0.15.8, mypy 1.19.1), и на Windows.

  • Выход без флага не изменился: 256 выводов против сборки main (каждый C#-вход, который сканирует CI, и две фикстуры OWN053; с --flow-locals и без; флаг включён и выключен) — 254 побайтово идентичны, 2 исправлены (получили ровно ту секцию, которую теряли), 0 прочих.
  • Инвариант после исправления: 128 пар ON/OFF, 0 отличий после удаления S0-полей (на main — 1, ключом orphaned_awaitables).
  • OWN053 через оба движка: orphaned_awaitables одинаков с флагом и без (2 сайта на новой фикстуре, 5 на Orphan.cs); Python и Rust печатают одинаковый вывод для обоих документов и совпадают между собой (код выхода, stdout, stderr).
  • Весь job C# leak extractor (Roslyn) -> OwnIR -> core, шаги как написаны в ci.yml: все exit 0 (включая S0 A/B, S1, S2 rewriter, patch bundle, self-gate, stale preimage).
  • python tests/run_tests.py — exit 0; 15 реестров эталонов в синхроне; cargo fmt --check, cargo clippy --all-targets — exit 0; cargo test --no-fail-fast — 288/0; паритет CLI на typestate_* — 37/37; protocol_gate.py --rust — 0 провалов.
  • T0: digest инструмента прежний (1a26aa63fd5f), mergegate_ci.py на симулированном merge — allowed.

Отрицательные контроли:

  • на экстракторе main новая проверка красная: «orphaned_awaitables is in the flag-off facts and MISSING from the flag-on facts», 3 находки с флагом против 5 без;
  • в этом дереве, если убрать секцию под флагом, она красная так же, после возврата — зелёная;
  • ключ конверта, который фикстура не задействует, валит проверку по имени;
  • старый образец в новом режиме отклоняется как не задействующий services, functions, orphaned_awaitables.

Не проверено: остальные job CI локально не гонялись (job C# leak extractor существует только на ubuntu; на Windows экстрактор и новая проверка запускались вручную).

Связанные issue

Отдельного issue нет. Дефект замечен при сведении #385 с #386.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет)
  • README/docs обновлены при необходимости
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

🤖 Generated with Claude Code

…(OWN053)

`--fix-candidates` is additive: it adds fix metadata and changes nothing else.
It removed a section. The same input, the same build:

    --flow-locals                     orphaned_awaitables: 5 sites
    --flow-locals --fix-candidates    orphaned_awaitables: absent

so every OWN053 advisory disappeared from a run that asked for fix candidates
(corpus/ownership-lab/h29/fx/Orphan.cs: 5 advisories without the flag, 0 with
it, on both engines).

Root cause: the extractor built its envelope in three places, picked by a nested
conditional — `--fix-candidates` ? A : orphans ? B : C. The `--fix-candidates`
envelope was written before `orphaned_awaitables` existed and was never given
it. Each additive section had to be remembered in every envelope, and one was
not.

The fix is the mechanism, not the missing line: ONE envelope, built once, in the
key order the facts have always had; a section that is not always there is one
conditional line. There is no second envelope for the next section to be
forgotten in. Output without the flag is unchanged byte for byte, and so is
every flag-on output that had nothing to lose: 256 outputs compared with main's
build (every C# input CI scans and the two OWN053 fixtures, with and without
--flow-locals, flag on and off) — 254 byte-identical, 2 repaired (they gain
exactly the section they used to drop), 0 other.

Why the S0 control did not see it. The invariant it holds was already the right
one — flag-on minus the S0 fields == flag-off, the whole document. The document
was the problem: FixCandidatesSample.cs has subscriptions and no other section,
and CI scans it without --flow-locals, so nothing the flag could drop was ever
there. Now:

  * tests/fixtures/fix_candidates/AdditiveSections.cs makes every top-level
    section the extractor can write non-empty under --flow-locals (both OWN053
    families included, with no reference directory);
  * `check_fix_candidates_facts.py --additive-sections` holds the equality on
    it, reads the section list off the extractor's own envelope
    (`facts["<key>"] = ...`) and refuses a pair that leaves a section out — so a
    section added to the extractor is red until the fixture exercises it — and
    requires the reference to give the same findings for both documents;
  * the CI step "S0 fix-candidates — extractor metadata (Part A)" runs it.

Negative controls: on main's extractor the new check fails (`orphaned_awaitables`
is in the flag-off facts and MISSING from the flag-on facts; 3 findings with the
flag, 5 without); with the section dropped under the flag in this tree it fails
the same way and passes again once restored; an envelope key the fixture does
not exercise fails it by name; and the old sample, given to the new mode, is
refused as not exercising `services`, `functions` and `orphaned_awaitables`.

No OwnIR vocabulary or version change (OWNIR_VERSION 1, LOWERED_VERSION 2), no
change to the OWN053 rule, to the state-protocol lowering or to the measurement
instrument. spec/OwnIR.md §2 states the producer-side rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@PhysShell
PhysShell merged commit a08b733 into main Oct 2, 2026
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant