|
| 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