Skip to content

fix(bridge+extractor): #294 tolerant-door contract + #305 teardown-predicate soundness (audit → red → green) - #306

Merged
PhysShell merged 4 commits into
mainfrom
claude/complex-project-tasks-viyycs
Jul 25, 2026
Merged

fix(bridge+extractor): #294 tolerant-door contract + #305 teardown-predicate soundness (audit → red → green)#306
PhysShell merged 4 commits into
mainfrom
claude/complex-project-tasks-viyycs

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

Две закрытые soundness-линии одной ветки. #294 (мост, Python-first): толерантная дверь (check_facts в обход load()) молча маршрутизировала неизвестный resource kind как subscription (OD-2) и падала на не-int line на части путей (OD-3) — теперь IR4-everywhere fail-loud (_route_resource, текст ошибки байт-в-байт с load()) и _as_int на всех путях построения находок; фикстура tolerant_unknown_kind стала общим rust_replay-кейсом (Python-only снимков не осталось). #305 (экстрактор): адверсариальный аудит teardown-предикатов #293/#302 (матрица аксиом AX1–AX6, 6 семейств дыр, 11 выдержанных атак — docs/notes/teardown-predicate-adversarial-audit.md) нашёл две P1-дыры: параметрический guard, записанный ранним return, кредитовался (близнец D7/INF-S3 моста), и -=/.Stop() в else-ветке канонического if (disposing) кредитовался вопреки собственной финализаторной доктрине. Фикс: IsParamGuardedByEarlyReturn + branch-aware каноническое исключение (SiteOnNegativeBranch), свёрнуты в общий IsParamGuardedRelease — подписки и таймеры наследуют вместе; 4 новых корпусных кейса (2 subscription + 2 timer twins) red→caught.

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

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

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

  • python tests/run_tests.py (exit 0; wpf-корпус 28/28)
  • ruff check . и mypy
  • python scripts/benchmark.py --selftest; cfg/diag parity регенерированы (аддитивно), cargo test весь workspace зелёный (Rust-паритет на выросшем корпусе)
  • Интеграционный CI на head f6bf31c (run #1594, push): 18/18 jobs success; corpus benchmark — все 4 новых before.cscaught: OWN001, все after.csclean, итог 61/62 caught · 62/62 fixes clean · 0 FP (единственный miss viewmodel-escapes-to-app — пре-существующий recall-floor элемент; фикс только добавляет условия демоции и по построению не может создать новый miss)
  • Внешний арбитраж: code review атак A/B — ACCEPT; финальный вердикт ACCEPTED / APPROVED FOR MERGE на неизменившемся f6bf31c

Связанные issue

Closes #294. Closes #305. Refs #293, #302, #304 (общий post-cutover ответ), P-036.

Чеклист

  • изменение покрыто тестом/селфтестом (тесты толерантной двери в test_ownir.py; 4 red→green корпусных кейса; guard-и replay-тестов Rust обновлены)
  • README/docs обновлены при необходимости (spec/Bridge.md §9 OD-2/OD-3 resolved, BridgeBehaviorMatrix.md, аудиторская нота, notes.md кейсов)
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

🤖 Generated with Claude Code

https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M


Generated by Claude Code

claude added 4 commits July 25, 2026 17:38
… line coercion

The bridge's tolerant door (direct `check_facts`/`to_module`/`to_own`, `load()`
bypassed) diverged from the strict door in two accidental ways tracked by #294:

- OD-2: a present-but-unknown `resource` kind was silently routed as
  `subscription` (`_RESOURCES.get(rkind, default)`), contradicting IR4 and the
  unknown-flow-op guard that already fail loud on both doors. New
  `_route_resource` raises the same `OwnIRError` text as `load()`; an absent
  `resource` still defaults to `subscription`.
- OD-3: finding construction used strict `int(sub.get("line", 0))` on the
  token/capture anchor paths but `_as_int` elsewhere, so a non-int `line` on the
  tolerant door crashed one path and degraded the other. All paths now use
  `_as_int` (degrade to 0); `load()` still validates `line` on the strict door.

Rust `own-bridge` already failed loud on an unknown kind, so OD-2 landing lets
`tolerant_unknown_kind` become a shared `rust_replay` case: the Rust error text
is aligned to Python's authoritative message and the fixture's `Rejected` golden
now pins both sides byte-for-byte. No `rust_replay: false` snapshots remain.

Tests: red→green in tests/test_ownir.py (tolerant-door unknown-kind raise +
non-int line degradation, with absent-kind and valid-int controls); Rust
replay/round-trip guards updated to assert the empty Python-only set. Docs:
spec/Bridge.md §9 OD-2/OD-3 marked resolved, BridgeBehaviorMatrix + own-lowered
doc refreshed. OD-1 (whether Rust exposes the tolerant door) stays for #259.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
…cates

Adversarial code reading of the landed teardown-context/guard predicates
(enumerate the implicit axioms, attack each, verify against Program.cs).
Full attack matrix — six confirmed hole families, eleven survived attacks,
doctrine assessment, bounded fix directions — in
docs/notes/teardown-predicate-adversarial-audit.md.

Two P1 holes (silently swallowed leaks, the #238 doctrine violation) are
pinned as red corpus fixtures; the CI corpus benchmark is the empirical
arbiter (before.cs MISSED expected — recall floor is an absolute count;
after.cs reuses proven-silent shapes):

- subscription-teardown-early-return-guard: the SectorTS flag guard rewritten
  from `if (!flag) { -= }` to `if (flag) return; -=` — semantically identical,
  invisible to IsParamGuardedRelease (ancestors-only walk), while the symbol
  closure credits the helper regardless of argument values. The C# twin of the
  bridge's D7/INF-S3 defect.

- subscription-disposing-else-branch-release: a `-=` in the ELSE of the
  canonical `if (disposing)` is credited (the exception classifies the
  parameter's use in the condition, never the branch holding the site) — yet
  it runs only on the finalizer path the extractor's own doctrine declares
  unreachable while the subscription is live.

Both .own reductions are caught by the branch-sensitive core (wpf corpus
26/26) — extractor gaps, not core gaps. cfg/diag parity fixtures regenerated
(additive); Rust parity green on the grown corpus. WPF002 Stop() shares the
predicate, so the holes apply verbatim — timer twins land with the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
… canonical disposing is branch-aware

The green half of the adversarial audit (previous commit was the red):

- IsParamGuardedByEarlyReturn (attack A): a `return` lexically preceding the
  release site inside the enclosing callable, guarded by a condition that
  references a parameter, demotes the release — the caller-controlled skip
  spelled as a sibling instead of an ancestor. The one exemption is the
  canonical INVERTED disposing exit (`if (!disposing) return;`), which
  guarantees the site runs on every Dispose() call. Nested lambdas / local
  functions are not scanned (their returns do not exit the frame); unguarded
  and field/local-guarded returns keep today's crediting.

- SiteOnNegativeBranch (attack B): the canonical positive-`disposing`
  exemption now covers only a site on the POSITIVE side of the guard; a `-=`
  or `.Stop()` in the ELSE of `if (disposing)` (or the when-false arm of a
  ternary) demotes like any parameter guard — that branch is the finalizer
  path the predicate's own doctrine declares unreachable while the
  subscription/timer pins the owner.

Both rules are folded into the shared IsParamGuardedRelease, so `-=` and
timer `.Stop()` inherit them together (one predicate, one context model).
IsCanonicalDisposingGuardUse is split into IsCanonicalDisposingParam +
IsNegatedGuardUse so the enclosing-guard and early-return rules can invert
the canonical polarity without duplicating the use-position analysis.

Corpus: the two red subscription fixtures flip to caught; timer twins added
(timer-stop-early-return-guard, timer-stop-disposing-else-branch). wpf corpus
28/28 on the .own side; cfg/diag parity regenerated (additive); Rust parity
green. The canonical THEN-branch shapes (WinFormsDisposalSample,
subscription-param-guarded-unregister after.cs family) keep their crediting —
no sample carries the demoted shapes, so extractor goldens stay put; the CI
corpus benchmark is the empirical arbiter for the C# side.

Worst case of both rules is a kept warning, never a swallowed leak (#238).
The general argument-value-aware answer remains #304; this is the bounded
pre-cutover floor.

Closes #305.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d7f5b02-2f70-4a27-b225-eae2ee8862d8

📥 Commits

Reviewing files that changed from the base of the PR and between ad8df31 and f6bf31c.

📒 Files selected for processing (34)
  • corpus/wpf/subscription-disposing-else-branch-release/after.cs
  • corpus/wpf/subscription-disposing-else-branch-release/before.cs
  • corpus/wpf/subscription-disposing-else-branch-release/case.own
  • corpus/wpf/subscription-disposing-else-branch-release/expected-diagnostics.txt
  • corpus/wpf/subscription-disposing-else-branch-release/notes.md
  • corpus/wpf/subscription-teardown-early-return-guard/after.cs
  • corpus/wpf/subscription-teardown-early-return-guard/before.cs
  • corpus/wpf/subscription-teardown-early-return-guard/case.own
  • corpus/wpf/subscription-teardown-early-return-guard/expected-diagnostics.txt
  • corpus/wpf/subscription-teardown-early-return-guard/notes.md
  • corpus/wpf/timer-stop-disposing-else-branch/after.cs
  • corpus/wpf/timer-stop-disposing-else-branch/before.cs
  • corpus/wpf/timer-stop-disposing-else-branch/case.own
  • corpus/wpf/timer-stop-disposing-else-branch/expected-diagnostics.txt
  • corpus/wpf/timer-stop-disposing-else-branch/notes.md
  • corpus/wpf/timer-stop-early-return-guard/after.cs
  • corpus/wpf/timer-stop-early-return-guard/before.cs
  • corpus/wpf/timer-stop-early-return-guard/case.own
  • corpus/wpf/timer-stop-early-return-guard/expected-diagnostics.txt
  • corpus/wpf/timer-stop-early-return-guard/notes.md
  • docs/notes/teardown-predicate-adversarial-audit.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • ownlang/ownir.py
  • rust/crates/own-bridge/src/lower.rs
  • rust/crates/own-bridge/tests/replay.rs
  • rust/crates/own-lowered/src/lib.rs
  • rust/crates/own-lowered/tests/replay.rs
  • spec/Bridge.md
  • spec/BridgeBehaviorMatrix.md
  • tests/fixtures/cfg_parity.json
  • tests/fixtures/diag_parity.json
  • tests/fixtures/lowered/manifest.json
  • tests/fixtures/lowered/tolerant_unknown_kind.golden.json
  • tests/test_ownir.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/complex-project-tasks-viyycs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PhysShell
PhysShell merged commit 36d6834 into main Jul 25, 2026
39 of 40 checks passed
PhysShell pushed a commit that referenced this pull request Jul 26, 2026
…inus the dominator tree

The runtime-witness half of the flagship demo, resurrected as the minimal
core rather than a rebase of the geological layer: Heap.cs (mark-from-roots
census, sampled ranked retainers with the field named at every hop,
stack/finalizer vs real retention) + Program.cs (census / roots,
--pid|--dump, --type, --sample/--max-hops limits, --out runtime.json) +
the csproj. Deliberately NOT taken: Dominators.cs (444 lines of separate
proof surface the hero demo does not need), the pre-#278/#305/#306 corpus
slice, and the old runtime-contract docs.

Adapted to the A3 contract:
- verdict vocabulary — RETAINED (a root path was established, exit 1) |
  OBSERVED_ONLY (instances exist, no path established — never called a
  proven leak, exit 0) | ABSENT; usage documents it;
- the JSON artifact carries verdict + collector identity (mode
  attach/dump, target, runtime, OS — no timestamps: identical heaps must
  yield identical artifacts);
- a failed read stays exit 2, distinct from clean(0)/retained(1);
- net8.0 cross-platform (ClrMD 3.x live-attach works on Linux and
  Windows; legacy net472 heaps are dump-only from here — the PR #280
  live-attach build can be resurrected the day a live legacy target
  matters).

Gate A builds the project and smokes the honest-failure surface on both
platforms: bare usage exits 2 with the verdict vocabulary documented, and
a failed attach exits 2 — never a clean scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants