|
| 1 | +# N1 competitor gate — pre-registration |
| 2 | + |
| 3 | +Written 2026-10-20, 21:11 +05. The four fix diffs have been read in order to build the reproductions. No analyzer |
| 4 | +has been run on any reproduction. |
| 5 | + |
| 6 | +Authority: owner decision `GO-N1-COMPETITOR-GATE`, given after `KILL-N3-PILOT` was accepted. This gate is not a GO |
| 7 | +on an N1 PoC. |
| 8 | + |
| 9 | +- **Budget.** 0.25 agent-day. |
| 10 | +- **Rules of the work.** No third-party project is built and no package is downloaded. The analyzers come from the |
| 11 | + .NET SDK and from the local NuGet cache, the same versions as direction-research Phase 5. |
| 12 | + |
| 13 | +## Question |
| 14 | + |
| 15 | +N1: a disposable is acquired, a step that can throw runs before ownership is handed over (return, field or |
| 16 | +collection store, wrapping), and no dispose runs on the throw path. |
| 17 | + |
| 18 | +Do existing analyzers miss the four confirmed N1 defects of Phase 0 once they are configured to look? The question |
| 19 | +matters most for CA2000 with exception paths on (`dispose_analysis_kind = AllPaths`), because that is the |
| 20 | +documented setting for exactly this case. |
| 21 | + |
| 22 | +## Why the earlier "not detected" does not answer it |
| 23 | + |
| 24 | +Phase 5 ran the analyzers over loose source trees. Those runs had about 35,000 compile errors (whizbang: 34,859 on |
| 25 | +base), so the disposable types were mostly unresolved, and CA2000 ran with its default kind. Those misses therefore |
| 26 | +say little about what CA2000 can do. |
| 27 | + |
| 28 | +## Pairs (fixed now) |
| 29 | + |
| 30 | +| Id | Fix | Buggy method | Leaked resource and acquisition | Throwing step before hand-over | Hand-over on success | |
| 31 | +|---|---|---|---|---|---| |
| 32 | +| W | whizbang-lib/whizbang#1204 | `AzureServiceBusConnectionRetry.CreateClientWithRetryAsync` | `new ServiceBusClient(cs)` (`IAsyncDisposable`), inside a retry loop's `try` | `await VerifyNamespaceReachableAsync(…)` | `return client;` | |
| 33 | +| R | MarimerLLC/rockbot#638 (record 638-2) | `McpBridgeService.ConnectServerAsync` | `await McpClient.CreateAsync(transport, …)`, an async factory (`IAsyncDisposable`) | `await newClient.ListToolsAsync(…)`, prompt listing | `_clients[name] = newClient;` (dictionary field) | |
| 34 | +| X | NexusFFXIV/NexusKit#34 (record 34-2) | `PluginHostBuilder.BuildAsync` | `services.BuildServiceProvider(validateScopes: false)`, a factory (`ServiceProvider`, `IDisposable` + `IAsyncDisposable`) | `await DbInitializer.InitializeAsync(…)`, service resolution | `return new PluginHost(provider);` (wrapping) | |
| 35 | +| B | Wizard-Software/BareWire#73 | `RabbitMqTransportAdapter.ConsumeAsync` (an `async` iterator) | `await _connection.CreateChannelAsync(…)`, an async factory (`IChannel`, `IAsyncDisposable` + `IDisposable`) | `await consumerChannel.BasicQosAsync(…)` | `_activeConsumerChannels[id] = consumerChannel;` (dictionary field) | |
| 36 | + |
| 37 | +The four fixes are by different owners. |
| 38 | + |
| 39 | +## Reproductions |
| 40 | + |
| 41 | +For each pair: one file `repro/<Id>.cs` with two self-contained classes, `<Id>Buggy` and `<Id>Fixed`. The |
| 42 | +acquisition line carries the marker comment `// ACQ:<Id>`. |
| 43 | + |
| 44 | +- **Kept from the real code.** |
| 45 | + - the acquisition expression form: `new`, factory call, or awaited async factory; |
| 46 | + - the throwing step, as an awaited call that can throw; |
| 47 | + - the success hand-over (return, wrap, or dictionary store); |
| 48 | + - the enclosing control flow: the retry loop with `try`/`catch` in W, the `try` around the attempt in R, and the |
| 49 | + `async` iterator in B; |
| 50 | + - each fix's exact cleanup shape: W `try`/`catch` + `DisposeAsync` + rethrow, with acquisition through a |
| 51 | + `Func<…>` seam; R a nullable local, a `published` flag and cleanup on the failure path; X `try`/`catch` + wrap + |
| 52 | + `DisposeAsync` + rethrow; B early registration into the dictionary, then `try`/`catch` + release. |
| 53 | +- **Third-party types are stubs** with the real disposable interfaces and the members used: `ServiceBusClient`, |
| 54 | + `McpClient`, `IChannel`/`IConnection`. `ServiceCollection`/`ServiceProvider` are the real types from the local |
| 55 | + ASP.NET Core reference pack. |
| 56 | +- **Lines not on the resource's path are dropped**: logging, metrics, unrelated locals. |
| 57 | +- The reproductions must compile with **0 errors**. An analyzer that sees a compile error proves nothing. |
| 58 | + |
| 59 | +## Analyzers and configurations |
| 60 | + |
| 61 | +One SDK-style project, `net10.0`, with no `PackageReference`. Analyzers are referenced by path: |
| 62 | + |
| 63 | +- the .NET SDK `Microsoft.CodeAnalysis.NetAnalyzers` (CA2000); |
| 64 | +- `IDisposableAnalyzers` 4.0.8 (IDISP001–IDISP026); |
| 65 | +- `SonarAnalyzer.CSharp` 10.35.0.4138 (S2930 and others). |
| 66 | + |
| 67 | +Every rule of every analyzer is set to warning (`dotnet_analyzer_diagnostic.severity = warning`). Configurations: |
| 68 | + |
| 69 | +| K | `dispose_analysis_kind` | ownership transfer at constructor | ownership transfer at method call | |
| 70 | +|---|---|---|---| |
| 71 | +| K1 | default (`NonExceptionPaths`) | default (false) | default (false) | |
| 72 | +| K2 | `AllPaths` | false | false | |
| 73 | +| K3 | `AllPaths` | **true** | false | |
| 74 | +| K4 | `AllPaths` | false | **true** | |
| 75 | +| K5 | `AllPathsOnlyNotDisposed` | false | false | |
| 76 | + |
| 77 | +**Harness control.** A two-method file, `Control.cs`: |
| 78 | + |
| 79 | +- `new StreamReader(path)`, then a call that can throw, then `Dispose()`. CA2000 must report it under K2 and must |
| 80 | + not report it under K1. |
| 81 | +- the same with `using`: silent in every configuration. |
| 82 | + |
| 83 | +If this control fails, the harness is invalid and the gate stops as `INVALID`, not as KILL. |
| 84 | + |
| 85 | +## Scoring (fixed now) |
| 86 | + |
| 87 | +- **Relevant diagnostic.** A diagnostic whose message is about disposal or ownership, located on the leaked |
| 88 | + resource's acquisition statement or its declared variable, in the buggy method. |
| 89 | +- **Pair verdict.** |
| 90 | + - **`CAUGHT`:** at least one configuration gives a relevant diagnostic on the buggy reproduction. This holds even |
| 91 | + when the fixed reproduction also gets one: a flag on the buggy site counts against N1's novelty. Whether the |
| 92 | + flag also distinguishes the fix is recorded separately. |
| 93 | + - **`MISSED`:** no relevant diagnostic on the buggy reproduction in any of K1–K5, from any of the three analyzers. |
| 94 | + |
| 95 | +**Decision.** |
| 96 | +- **`GO-CANDIDATE-N1`** (for an owner decision, not a PoC GO) if at least **2** pairs are `MISSED`. |
| 97 | +- **`KILL-N1`** otherwise. |
| 98 | + |
| 99 | +Fidelity risk: a stub or a dropped line can change an analyzer's result. Every `MISSED` is therefore re-checked by |
| 100 | +listing the analyzer's documented limits for that shape: a factory, an `async` iterator or a dictionary store. A |
| 101 | +`MISSED` that is explained only by a reproduction deviation from the real code becomes `UNRESOLVED` and does not |
| 102 | +count toward GO. |
0 commit comments