Skip to content

Commit ac094bd

Browse files
PhysShellclaude
andcommitted
docs(evidence): N1-F0 — KILL-N1 at F0-B (may-throw model, not ownership)
F0-A pass: R, X, B stay missed under every CA2000 interprocedural kind with all disposal diagnostics in the method collected. F0-B fail: the frozen oracle establishes ownership on all three real base files via repository-internal disposal (no IDisposable/Create*/await cue) and is silent on N1-N9, but flags the BareWire fix because the registered "any invocation may throw" rule counts Guid.NewGuid() before the early hand-over. F0-C: existing acquire/release/throw-edge facts suffice, but the existing StatementMayThrow is the same coarse model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG
1 parent 081b68e commit ac094bd

5 files changed

Lines changed: 155 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# N1-F0 — ownership feasibility gate — report
2+
3+
**Result: `KILL-N1`. The first failing part is F0-B, and the failure is in the may-throw model, not in ownership.**
4+
5+
| Part | Verdict | Short reason |
6+
|---|---|---|
7+
| F0-A competitor check | **pass** | R, X, B stay `MISSED` under every `interprocedural_analysis_kind` and ownership-transfer setting, with every disposal-related diagnostic inside the method collected |
8+
| F0-B ownership oracle | **fail** | ownership is established on all three real base files without name/type/`await` heuristics, but the **BareWire fixed file is flagged**: `Guid.NewGuid().ToString("N")` counts as a may-throw call before the hand-over |
9+
| F0-C cost | pass (not decisive) | acquisition, hand-over and throw edges fit existing OwnIR ops; the existing throw model is the same coarse one that failed F0-B |
10+
11+
Rules: [`00-preregistration.md`](00-preregistration.md), committed in `66731cc2` before any F0 run. The oracle was
12+
frozen in `081b68ee`, after its control-only debugging and before the one scored run on real files. Time used:
13+
21:51–21:58 +05 on 2026-10-20 for the runs, inside the 0.25 agent-day budget.
14+
15+
No production, OwnIR, CI or golden change. Nothing third-party was built or downloaded.
16+
17+
## F0-A
18+
19+
[`scripts/run_f0a.py`](scripts/run_f0a.py) rebuilt the #417 reproductions under 11 configurations: A1–A6 cover
20+
`interprocedural_analysis_kind` `None` / `NonContextSensitive` / `ContextSensitive`, each with and without both
21+
ownership-transfer switches; K1–K5 repeat the #417 set. The collector takes every disposal-related diagnostic
22+
located inside the buggy method's line span (`data/f0a_spans.tsv`), not only on the `ACQ` line.
23+
24+
Result (`data/f0a_method_diagnostics.tsv`):
25+
26+
| Pair | Result |
27+
|---|---|
28+
| W | CA2000 on `new ServiceBusClient(…)` in A1–A6 and K2–K4 (`CAUGHT`) |
29+
| R | only CA2000 on `out var oldClient`, line 28, in all 11 configurations. That is another object, `ADJACENT`, so R stays `MISSED` |
30+
| X | nothing (`MISSED`) |
31+
| B | nothing (`MISSED`) |
32+
33+
3 pairs missed, against the required ≥ 2, so F0-A passes. No interprocedural setting changes anything.
34+
35+
## F0-B
36+
37+
**The oracle.** [`oracle/Program.cs`](oracle/Program.cs), a Roslyn scan, applies rules 1–3 as registered.
38+
Ownership needs a hand-over target that the repository itself disposes, and the borrow table only suppresses. Two
39+
details were fixed in the code header before the real run:
40+
41+
- **"Disposes r"** means any of: `Dispose`/`DisposeAsync`/`Close`/`CloseAsync` on `r`; the same call on an owning
42+
wrapper `new W(r)`; or `r` passed to a method of the same type that disposes its parameter.
43+
- **One control-driven fix.** A dispose of `r` inside the `catch` is not a throwing site (control N6).
44+
45+
**Controls** (`data/f0b_controls.tsv`). N1–N9 are silent: DI-resolved, borrowed parameter, shared and cached
46+
instances never disposed, hand-over first, `try`/`catch` dispose, `published` flag, `using`, returned. P-R, P-B and
47+
P-X are reported.
48+
49+
**Real files** (`data/f0b_real.tsv`). Single files at the pairs' SHAs (hashes in `data/f0b_inputs.sha256`), parsed
50+
with framework references only.
51+
52+
| File | Oracle | Ownership evidence it found |
53+
|---|---|---|
54+
| R base `McpBridgeService.cs` | reported at `McpClient.CreateAsync` (l. 561); throw at `await newClient.ListToolsAsync` (l. 564); hand-over `_clients[name]` (l. 577) | `_clients` element disposed: `oldClient.DisposeAsync()` (l. 573), with `oldClient` taken by `_clients.Remove(name, out var oldClient)` |
55+
| R fix | silent | **no owned acquisition**: the fix hands the client over through `new ConnectionResources(newClient)` assigned to a local and an object-initializer member, which rules 1a–1c do not cover. Silent, but not because the cleanup was recognised |
56+
| X base `PluginHostBuilder.cs` + `PluginHost.cs` | reported at `BuildServiceProvider` (l. 90); first throw at `provider.GetService<…>()` (l. 92); hand-over `return new PluginHost(provider)` (l. 136) | wrapper rule 1c: `PluginHost.mProvider` disposed at `PluginHost.cs:27` |
57+
| X fix | silent | the `catch` disposes `new PluginHost(provider)`, an owning wrapper |
58+
| B base `RabbitMqTransportAdapter.cs` | reported at `CreateChannelAsync` (l. 285); first throw listed at `Math.Min(…)` (l. 294), and `BasicQosAsync` lies in the same window; hand-over `_activeConsumerChannels[id]` (l. 321) | `channel.CloseAsync(…)` (l. 249) on a channel taken from `_activeConsumerChannels` |
59+
| **B fix** | **reported** (FP) | ownership is right, but the only calls between `CreateChannelAsync` (l. 286) and the early hand-over (l. 300) are `Guid.NewGuid().ToString("N")` (l. 299). The registered rule "any invocation can throw" counts them |
60+
61+
By the registered pass rule, which requires silence on every fix file, F0-B fails. The oracle was not changed after
62+
this run.
63+
64+
**What the failure means.**
65+
- **Confirmed.** The ownership half works on these inputs:
66+
- it found the disposing line for all three real targets;
67+
- it stayed silent on all nine negative controls without any `IDisposable`, `Create*` or `await` cue;
68+
- in X and B it established ownership across a wrapper and across a collection.
69+
- **Confirmed.** The registered may-throw rule is too coarse. One of three fixes is flagged only because of
70+
non-throwing BCL calls.
71+
- **Inference, not scored.** A may-throw model that exempts such calls would clear B fix while keeping B base,
72+
because `BasicQosAsync` lies inside its window. Nothing was run to show it.
73+
- **Limit.** The R fix shows a recall gap: a hand-over through a local wrapper or an object initializer establishes
74+
no ownership. A bug in that shape would be missed.
75+
76+
## F0-C
77+
78+
Read with line references in `main` (`38fb952d`):
79+
80+
1. **Acquisition from a call.** Already expressible: the flow-locals pass emits `acquire` for `new`, pool rents and a
81+
curated owning-factory table (`IsOwningFactory`, `frontend/roslyn/OwnSharp.Extractor/Program.cs:3832-3837`).
82+
For first-party factories the core can mint the acquire itself from a `call` op (P-005 D5.2,
83+
`Program.cs:3849`). An oracle-based acquire would be extractor-side only.
84+
2. **Hand-over.** The existing `release` op can discharge the obligation (`ownlang/ownir.py:343-347`,
85+
`_FLOW_OPS`). Today a local stored to a field "escapes" and is not flow-tracked at all (`--flow-locals` covers
86+
non-escaping locals only, `Program.cs:84-87`). Changing that is extractor-side work.
87+
3. **Throw edges.** These exist (`InjectThrowEdge`, `Program.cs:3569-3587`, `--body-throw-edges`). They use
88+
`StatementMayThrow` (`Program.cs:3590-3599`): any non-dispose call or `new` may throw. That is the same coarse
89+
model that failed F0-B, and the extractor itself calls this tier "the CA2000 firehose" (`Program.cs:100-103`).
90+
91+
None of the three needs a new OwnIR op, a version bump or a core interprocedural pass, so F0-C passes by its rule.
92+
It also shows that a precise may-throw model does not exist yet; it would be new extractor work.
93+
94+
Estimate, an inference: the oracle port plus escape tracking is roughly several days of extractor work. A may-throw
95+
model precise enough to clear cases like B fix has no estimate, because nothing like it exists in the repository.
96+
97+
## Files
98+
99+
- [`00-preregistration.md`](00-preregistration.md)
100+
- [`controls/OwnershipControls.cs`](controls/OwnershipControls.cs)
101+
- [`oracle/`](oracle/): the oracle, a Roslyn console, built like the #416 PoC.
102+
- [`scripts/run_f0a.py`](scripts/run_f0a.py)
103+
- `data/`:
104+
- `f0a_method_diagnostics.tsv`, `f0a_spans.tsv`: F0-A;
105+
- `f0b_controls.tsv`, `f0b_real.tsv`, `f0b_inputs.sha256`: F0-B.
106+
107+
The third-party source files are not committed; they are named by SHA and hash.

0 commit comments

Comments
 (0)