Skip to content

Commit 14be6c3

Browse files
PhysShellclaude
andcommitted
docs(evidence): N3 pilot G0 pre-registration (scope identity)
Rules fixed before the G0 filter is written or run: test-project classification from .csproj, drop test registrations, walk from the registration's own implementation, proven lifetime only. Target: the 7/7 OrchardCore DI001 FPs of D11; controls: DiCaptiveSample and rrr-pi-rate#60; new-finding check over 27 saved holdout/sweep facts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFkNAFFQz9KoTzpPws8vYG
1 parent 76bc422 commit 14be6c3

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# N3 bounded pilot, gate G0 (scope identity) — pre-registration
2+
3+
Written 2026-10-20, before the G0 filter was written or run. What had been read before this file:
4+
5+
- the D11 triage of the 7 holdout DI001 findings (`evidence/holdout/ownnet/di_sample.tsv` of the direction
6+
research, private), which names the cause of each FP;
7+
- `ownlang/di.py` (`find_captive_dependencies`);
8+
- the field names of one `services` fact in the OrchardCore holdout facts. No DI001 result was recomputed.
9+
10+
Authority: owner decision `ACCEPT-PHASE0 / GO-N3-BOUNDED-PILOT / NO-PRODUCTION`, given on PR #414. G0 budget:
11+
0.25 agent-day. G1–G4 start only after a G0 PASS, and get one agent-day in total. The first failed mandatory gate
12+
stops the pilot (`KILL-N3-PILOT`). The budget is not extended.
13+
14+
## Question
15+
16+
Can product registrations be told apart from test registrations, and can each registration be resolved through its
17+
own implementation, using only the facts the extractor already emits and the repository's project files? The DI
18+
graph must not be rebuilt.
19+
20+
N3 needs this because its warning must rest on a proven scoped lifetime of a concrete object. If the registration
21+
graph cannot say "scoped" without the 7/7 FP of D11, a detached-work rule built on it inherits them.
22+
23+
## Known failure (the target)
24+
25+
7 of 7 product DI001 findings on OrchardCore (holdout, target `5ddc4594`, Own.NET `e7d33e33`) are FP. The D11 triage
26+
gives two causes:
27+
28+
- **test registrations merged in (6).** `IExtensionManager` (5 findings) and `IMediaTokenService` (1) are
29+
`AddSingleton` in product code. The `scoped` lifetime comes from test registrations (`StubExtensionManager`,
30+
`MediaTokenTests.cs`). The core keys the graph by service name, and the last registration wins.
31+
- **implementations merged by service name (1).** `IDeploymentTargetProvider`: the singleton implementation
32+
`FileDownloadDeploymentTargetProvider` takes only `IStringLocalizer`. The scoped dependency belongs to another
33+
implementation of the same interface. The core starts each walk from `by_name[s.name]`, not from the registration
34+
`s` itself.
35+
36+
## Method (fixed now)
37+
38+
A research script outside the product tree reads saved `facts.json` files and the repository sources. Production
39+
code, OwnIR, CI and goldens are not changed. The script applies four rules:
40+
41+
1. **Test project.** A registration belongs to the project whose `.csproj` is in the nearest ancestor directory of
42+
its `file`. The project is a test project when its `.csproj` sets `<IsTestProject>true</IsTestProject>` or
43+
references one of `Microsoft.NET.Test.Sdk`, `xunit`, `xunit.v3`, `NUnit`, `MSTest.TestFramework`, `MSTest`,
44+
`TUnit` or `BenchmarkDotNet`. Path names are not used for this decision. The path regex of the direction
45+
research (`sweep_summary.py`) is reported next to it, as a comparison only.
46+
2. **Drop test registrations.** Registrations in test projects are removed. A registration with no owning
47+
`.csproj` is kept and counted.
48+
3. **Own implementation at the root.** The walk for a singleton registration starts from that registration's own
49+
`deps`, not from the node of the same service name.
50+
4. **Proven lifetime only.** A dependency name counts as scoped only when every remaining registration of that
51+
name is scoped. It is followed as transient only when every remaining registration is transient. Mixed
52+
lifetimes make the name unknown, and unknown adds no edge. The number of mixed names is reported.
53+
54+
The DI001 rule itself (singleton → scoped, through transients) does not change.
55+
56+
## Inputs
57+
58+
- **Target.** OrchardCore holdout facts as saved by D11 (Own.NET `e7d33e33`). They are not re-extracted.
59+
- **Positive controls (must keep firing).**
60+
- `frontend/roslyn/samples/DiCaptiveSample.cs`: exactly four DI001 (`EmailSender -> AppDbContext`,
61+
`ReportService -> UnitOfWork -> AppDbContext`, `CacheService -> IRepo`, `PrimaryCtorService -> AppDbContext`)
62+
and silence on `Metrics -> Clock` and `PublicCtorOnly`, as its CI assertions pin today.
63+
- Corpus pair `rrr-pi-rate/StockPriceSheetPrintService#60` (C10): DI001 present on `base`, absent on `fix`, as in
64+
the D6 ledger.
65+
- **New-finding check.** The other 14 holdout repositories and the 13 design-B sweep repositories, all from saved
66+
facts. Rule 3 can add findings that the current core does not report, so every finding that the current core
67+
does not report is listed and triaged by reading the code.
68+
69+
## Verdict rules
70+
71+
`PASS-G0` needs all of:
72+
73+
- all 7 known OrchardCore FPs are gone, each by a rule named above;
74+
- both positive controls hold exactly;
75+
- every new finding is triaged, and none is a FP. An `UNCLEAR` counts as a FP;
76+
- elapsed work within 0.25 agent-day.
77+
78+
`KILL-N3-PILOT` at G0 if any of the following:
79+
80+
- a known FP survives the four rules and removing it needs another mechanism;
81+
- a positive control is lost;
82+
- a new finding is a FP or `UNCLEAR`;
83+
- the budget runs out before a verdict.
84+
85+
No rule may be added or changed after the first run. A fifth rule invented after the run is a G0 failure, not a
86+
fix.
87+
88+
## Out of scope for G0 (input to G1)
89+
90+
- Framework registrations (`AddDbContext`, `AddHttpClient`, controllers, Razor components). Their lifetime comes from
91+
the framework, not from an `Add*` call in the repo. G1 must say how N3 gets "scoped" for them; G0 does not test it.
92+
- Open generics, factory lambdas, assembly scanning: unknown, as today.

0 commit comments

Comments
 (0)