From 4b5d6fa29174c4803279742c2c7e510cf3c6facb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 08:40:14 +0000 Subject: [PATCH 1/2] docs: fix doc-drift, sharpen positioning, record consolidation backlog Cheap, doc-only hygiene from an external architecture review; the larger structural items are recorded as a deferred backlog rather than done now. - ROADMAP: the "frontend does NOT touch closures / interprocedural" note drifted from reality. Carve out the two honest exceptions that already exist: syntactic lambda-event-handler classification, and bounded *modular* interprocedural (ownership contracts + inference) in the CORE, not the frontend (P-006/2b). - ROADMAP framing: state plainly that Own.NET is one resource/lifetime analyzer WITH profiles, WPF is the first profile (not the identity); the engine emits domain-neutral OWN codes + a profile label. - lifetimes.md: note the naming convention -- WPFxxx are pattern-catalog IDs, not emitted codes (the engine emits OWN001/002/003/014 + [resource]/[profile]). - New docs/notes/consolidation-and-positioning.md records the review's verdict and the DEFERRED items (OwnIR v1 schema rename, extractor split, WPFxxx->SUB/TMR/DISP catalog rename) with cost/benefit and the trigger that should pull each off the shelf -- plus the honest meta-point that the highest-leverage next move is proving value (oracle on real repos), not reshaping form. No code change; full suite, ruff, mypy --strict unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED --- docs/ROADMAP.md | 35 ++++++-- docs/lifetimes.md | 8 ++ docs/notes/consolidation-and-positioning.md | 94 +++++++++++++++++++++ 3 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 docs/notes/consolidation-and-positioning.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d9637529..6b64016a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -16,8 +16,18 @@ The first public pitch is **not** "we're building a borrow checker for C#". It i > leaks, missing `Dispose`, DI lifetime mismatch, and pooled-buffer misuse. That is concrete, painful, and shippable without a five-year R&D detour. The -borrow checker is the *first combat module*, not the whole universe. The -long-term identity the backlog is aiming at: +borrow checker is the *first combat module*, not the whole universe. + +Read structurally, Own.NET is **one resource/lifetime analyzer with profiles** — +subscriptions, timers, `IDisposable`, DI lifetimes, pooled buffers — and **WPF is +the first configured profile, not the identity.** The engine emits domain-neutral +core verdicts (`OWN001/002/003/014`); a profile only contributes the lifetime facts +(WPF's `ViewModel < Window < App`, `Loaded`/`Closed` = release regions) and a label. +Code names that still read `WPFxxx` are pattern-catalog IDs, not emitted codes — the +catalog rename (`SUB`/`TMR`/`DISP`) and the other consolidation items are recorded in +[docs/notes/consolidation-and-positioning.md](notes/consolidation-and-positioning.md). + +The long-term identity the backlog is aiming at: > **An external static-contract layer for C#/.NET** that adds ownership, > typestate, effects, capabilities, and domain-specific types **without @@ -46,13 +56,26 @@ long-term identity the backlog is aiming at: ### What the C# frontend deliberately does NOT touch yet -`async`/`await`, full generics, LINQ, closures/lambdas, interprocedural analysis, -virtual dispatch, whole-program analysis, source generators, `unsafe` pointer -arithmetic, the XAML/binding engine. Not "never" — just not *before* the tool has -found its first real bug. An `async` method in v0 is honestly skipped (or flagged +`async`/`await`, full generics, LINQ, general closure/dataflow analysis, virtual +dispatch, whole-program analysis, source generators, `unsafe` pointer arithmetic, +the XAML/binding engine. Not "never" — just not *before* the tool has found its +first real bug. An `async` method in v0 is honestly skipped (or flagged "unsupported"), because honestly skipping beats confidently lying; the market for confident-but-wrong tooling is already saturated. +Two honest exceptions have since been carved out, because a real bug needed each: + +- **Syntactic lambda-event-handler classification.** General closure analysis stays + out, but the frontend *does* recognise an inline `evt += (s,a) => …` handler — an + inline handler caches no delegate, so it can never be `-=`'d, which is part of the + subscription-leak profile. It is the syntactic *shape*, not dataflow over captures. +- **Bounded, *modular* interprocedural — in the core, not the frontend.** The + frontend still extracts facts one method at a time. But the core now checks + ownership *across* methods **compositionally, against a callee's contract** + (`consume`/`borrow`, inferred from its body when unannotated) — the signature is + the cut point, never whole-program points-to (P-006/2b). "Interprocedural" in the + exclusion above means the intractable *whole-program* kind, which stays out. + ## Priorities Targets are ranked by four criteria: (1) the pain is frequent or expensive, diff --git a/docs/lifetimes.md b/docs/lifetimes.md index 4691a2b8..19fe476e 100644 --- a/docs/lifetimes.md +++ b/docs/lifetimes.md @@ -124,6 +124,14 @@ weak-reference policy как явный escape-hatch. ## 5. Каталог кодов (OWN-WPF) и куда какой слайс +> **Соглашение об именах.** `WPFxxx` ниже — это **ID паттернов каталога** +> (таксономия болей), а **не** коды, которые эмитит движок. Движок всегда выдаёт +> доменно-нейтральные **core-коды** (`OWN001/002/003/014`) плюс ярлык ресурса/ +> профиля (`[resource: subscription token]`, `[profile: wpf]`). То есть `event +=` +> — это `OWN001`/`OWN014` под профилем WPF, а не отдельный «WPF001»-эмиттер. +> Массовое переименование каталога (`WPFxxx → SUB/TMR/DISP`) отложено как +> low-value churn — см. `docs/notes/consolidation-and-positioning.md`. + | Код | Смысл | Сводится к | Слайс | |-----|-------|-----------|-------| | WPF004 | `Subscribe` вернул owned-токен, результат проигнорирован → утечёт | `OWN001` | **#1** | diff --git a/docs/notes/consolidation-and-positioning.md b/docs/notes/consolidation-and-positioning.md new file mode 100644 index 00000000..b2c8f345 --- /dev/null +++ b/docs/notes/consolidation-and-positioning.md @@ -0,0 +1,94 @@ +# Consolidation & positioning backlog + +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. + +## Verdict (agreed) + +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. + +## Done now (this PR — cheap, doc-only, ~zero risk) + +- **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 `OWNxxx` codes + a profile label. +- **Naming convention noted** in `docs/lifetimes.md`: `WPFxxx` are pattern-catalog + IDs, not emitted codes. + +## Deferred (recorded, NOT scheduled) + +Ordered by the review; annotated with the real cost/benefit and the trigger that +should pull each off the shelf. + +### 1. OwnIR v1 schema — rename `subscriptions` → `resources` (+ `captures`) +- **Why.** The component-level field is historically named `subscriptions` but + holds any owned-resource record (subscription / timer / disposable / capture). +- **Reality check.** Milder than the review implies: `services` and `functions` + are **already** separate top-level fields; only the component resource list is + misnamed, and a `resource` kind 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. + +### 2. Split the Roslyn extractor (`Program.cs`) +- **Why.** `OwnSharp.Extractor/Program.cs` carries 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 = peel `EventSubscriptionExtractor` + + `SourceLifetimeClassifier` + `HandlerClassifier` out, keeping the + `IDisposable` local-flow lowering separate from the subscription extractor. + +### 3. Catalog rename `WPFxxx → SUB/TMR/DISP` +- **Why.** Positioning — `event +=` is `SUB001`, not `WPF001`. +- **Reality check.** Emitted codes are **already** core `OWN` + labels; `WPFxxx` + live 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 `WPF` profile label. + +## Genuinely next *capabilities* (features, not refactors) + +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). + +## The actual priority + +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`. From 569077005d3ef47de425af9552791e03918ff531 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 11:29:39 +0000 Subject: [PATCH 2/2] docs: drop the nonexistent `[profile: wpf]` output claim (codex P2) The positioning edits promised the engine emits a `[profile: wpf]` label, but the renderers only carry/render a resource kind: `ownlang/diagnostics.py` emits ` [resource: ...]` from `resource_kind`, and `ownlang/ownir.py` renders human/GitHub/MSBuild findings with `[resource: ...]` only. Anyone asserting CLI output would look for metadata the tool never emits. Fix (docs-only, in the spirit of this doc-accuracy PR): say the engine emits the core code + a `[resource: ...]` kind tag, and note a dedicated `[profile: ...]` label is NOT emitted today -- it is a consolidation-backlog item. ROADMAP, lifetimes.md catalog note, and the consolidation note all corrected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED --- docs/ROADMAP.md | 6 ++++-- docs/lifetimes.md | 7 ++++--- docs/notes/consolidation-and-positioning.md | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6b64016a..8f965336 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -21,8 +21,10 @@ borrow checker is the *first combat module*, not the whole universe. Read structurally, Own.NET is **one resource/lifetime analyzer with profiles** — subscriptions, timers, `IDisposable`, DI lifetimes, pooled buffers — and **WPF is the first configured profile, not the identity.** The engine emits domain-neutral -core verdicts (`OWN001/002/003/014`); a profile only contributes the lifetime facts -(WPF's `ViewModel < Window < App`, `Loaded`/`Closed` = release regions) and a label. +core verdicts (`OWN001/002/003/014`) plus a `[resource: …]` kind tag; a profile only +contributes the lifetime facts (WPF's `ViewModel < Window < App`, `Loaded`/`Closed` += release regions). (A dedicated `[profile: …]` label is **not** emitted today — it +is a consolidation-backlog item.) Code names that still read `WPFxxx` are pattern-catalog IDs, not emitted codes — the catalog rename (`SUB`/`TMR`/`DISP`) and the other consolidation items are recorded in [docs/notes/consolidation-and-positioning.md](notes/consolidation-and-positioning.md). diff --git a/docs/lifetimes.md b/docs/lifetimes.md index 19fe476e..ae76a1df 100644 --- a/docs/lifetimes.md +++ b/docs/lifetimes.md @@ -126,9 +126,10 @@ weak-reference policy как явный escape-hatch. > **Соглашение об именах.** `WPFxxx` ниже — это **ID паттернов каталога** > (таксономия болей), а **не** коды, которые эмитит движок. Движок всегда выдаёт -> доменно-нейтральные **core-коды** (`OWN001/002/003/014`) плюс ярлык ресурса/ -> профиля (`[resource: subscription token]`, `[profile: wpf]`). То есть `event +=` -> — это `OWN001`/`OWN014` под профилем WPF, а не отдельный «WPF001»-эмиттер. +> доменно-нейтральные **core-коды** (`OWN001/002/003/014`) плюс **ярлык ресурса** +> (`[resource: subscription token]`); отдельного `[profile: …]`-ярлыка движок пока +> **НЕ** эмитит (это пункт consolidation-бэклога). То есть `event +=` — это +> `OWN001`/`OWN014` класса subscription, а не отдельный «WPF001»-эмиттер. > Массовое переименование каталога (`WPFxxx → SUB/TMR/DISP`) отложено как > low-value churn — см. `docs/notes/consolidation-and-positioning.md`. diff --git a/docs/notes/consolidation-and-positioning.md b/docs/notes/consolidation-and-positioning.md index b2c8f345..6c4b0827 100644 --- a/docs/notes/consolidation-and-positioning.md +++ b/docs/notes/consolidation-and-positioning.md @@ -28,7 +28,8 @@ this list: it is running the differentiation oracle on more real repos. (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 `OWNxxx` codes + a profile label. + engine emits core `OWNxxx` codes + a `[resource: …]` kind tag (a dedicated + `[profile: …]` label is itself one of the deferred items below). - **Naming convention noted** in `docs/lifetimes.md`: `WPFxxx` are pattern-catalog IDs, not emitted codes.