Working notes from an external architecture review (read-only pass over the repo: README, ROADMAP, P-001/P-004/P-005/P-006, the OwnIR bridge and the Roslyn extractor). The review's verdict and the cheap fixes are recorded here so the deferred items do not evaporate; the cheap fixes were applied directly (see below), the rest are explicitly deferred with rationale.
The direction is right: one core checker, Roslyn extracts facts only, OwnIR is
the seam, P0 is bug-driven (events / timers / IDisposable / DI / pool), and
lifetime regions reached OWN014 through real C# facts. The skeleton is sound.
The standing advice — "strengthen the form, don't expand the dream" — is taken.
One honest caveat about the review itself: it was a read-only pass (tests not run), so it is weighted toward what you see reading (naming, file size, schema) and blind to what you only see running (does Own.NET catch real bugs others miss?). What is still unproven is value, not form — no rename or file-split adds a single caught bug. So the highest-leverage next move is not on this list: it is running the differentiation oracle on more real repos.
- Doc drift fixed. ROADMAP "frontend does NOT touch closures/interprocedural" now carves out the two honest exceptions that already exist: syntactic lambda-event-handler classification, and bounded modular interprocedural (contracts/inference) in the core, not the frontend.
- Positioning sharpened. ROADMAP framing now states plainly: Own.NET is one
resource/lifetime analyzer with profiles, WPF is the first profile, the
engine emits core
OWNxxxcodes + a[resource: …]kind tag (a dedicated[profile: …]label is itself one of the deferred items below). - Naming convention noted in
docs/lifetimes.md:WPFxxxare pattern-catalog IDs, not emitted codes.
Ordered by the review; annotated with the real cost/benefit and the trigger that should pull each off the shelf.
- Why. The component-level field is historically named
subscriptionsbut holds any owned-resource record (subscription / timer / disposable / capture). - Reality check. Milder than the review implies:
servicesandfunctionsare already separate top-level fields; only the component resource list is misnamed, and aresourcekind discriminator already makes it work. - Cost. Breaking
v0 → v1: bridge + every fixture + the C# extractor + tests. Pure rename churn, zero new caught bugs. - Decision. Do NOT do as a standalone churn PR. Fold into the next PR that already touches the schema (e.g. when a new resource kind needs first-class fields), with a transition window where the bridge reads both names.
- Why.
OwnSharp.Extractor/Program.cscarries input discovery, event/timer/ disposable extraction, self-owned/handler/source-lifetime classifiers, lambda detection and IDisposable flow lowering in one file. - Cost. Behaviour-preserving C# refactor; but it is the frontend, which is CI-validated only (no local dotnet here), so it is the riskier kind to refactor blind, and it adds no caught bug.
- Decision. Defer until it actually causes merge pain. Target split:
InputDiscovery/CompilationBuilder/OwnIrWriter,Extractors/*,Analysis/*; first cut = peelEventSubscriptionExtractor+SourceLifetimeClassifier+HandlerClassifierout, keeping theIDisposablelocal-flow lowering separate from the subscription extractor.
- Why. Positioning —
event +=isSUB001, notWPF001. - Reality check. Emitted codes are already core
OWN+ labels;WPFxxxlive only in the docs/catalog. So this is doc churn, not an emitter change. - Decision. Do it together with the OwnIR v1 / profile-config work, not alone.
Keep genuinely WPF/XAML-specific patterns (ResourceDictionary / DataContext /
Binding / WeakEventManager / visual-tree ownership) under a
WPFprofile label.
The review filed these under "next" but they are new capability, tracked where features belong:
- Transitive contract inference — resolve the one ambiguous inference case
(a param only forwarded to another call) from the callee's contract, fixpoint
over the island's call-graph SCC. This is the real next brick: it would make the
forward → sink(consume)shape catch the caller's double-dispose that v1 deliberately leaves silent. (Follows P-006/2b.) - Lambda-handler tier — finer subscription diagnostics for inline handlers
(no unsubscribe handle; captures
this/local state), tiered by source lifetime exactly like the existing static→OWN014/ injected→OWN001-warning split. - DI registration extractor — the C# frontend for
AddSingleton/Scoped/ Transient+ constructor injection that feeds the already-built DI001 core (continues the lifetime story; sells to ASP.NET).
Prove value, don't reshape form: run the oracle on more real OSS C# repos and
widen the differentiation set (where Own.NET catches what CodeQL / Infer# miss).
See docs/notes/oracle.md and docs/notes/real-world-mining.md.