- Status: in progress — well past v0. The seam (Roslyn extractor → OwnIR → Python
core) is built and now extracts far more than the original
event += without -=spike: WPF001–005 (events, timers,IDisposablefields, ignoredSubscribe, static-event region escape → OWN014), the DI registration + constructor graph (DI001–DI005, P-006), the ArrayPool/Span facts (POOL001–003 built; 004/005 first slices, P-007), path-sensitive per-method flow facts (--flow-locals: acquire/use/release/loops/try, OWN001/002/003, P-016), and project-local semantic resolution (SemanticModel-bound+=, OWN050 for unresolved types, P-014 Tier A). All CI-validated end-to-end on the extractor samples. - Depends on:
spec/OwnCore.md,spec/Lifetimes.md(the fact vocabulary)
The event += without -= pattern, end-to-end, exactly along the recommended
seam:
- Roslyn extractor (
frontend/roslyn/OwnSharp.Extractor, C#, type-aware: project-localSemanticModel— see P-014): scans.cs, emits OwnIR facts (JSON) — built & run in CI (wpf-extractor). - Python fact bridge (
ownlang/ownir.py,python -m ownlang ownir): lowers facts to a synthetic.ownsketch, runs the existing core, and maps the OWN001 verdict back to the C# location with the[resource: subscription token]tag. Tested locally against hand-written facts (tests/test_ownir.py). - CI (
wpf-extractorjob): real.cs→ extractor → facts → core → leak at its C# line; the disposed sample stays silent.
Next: IDisposable fields, and feeding region facts to OWN014 (timers built —
the WPF002 increment, see P-004).
Today OwnLang catches real bug patterns, but only on hand-written .own: there
is no C# front-end, so the corpus is hand-reduced. The highest-value next step —
and the one that turns "our DSL correctly rejected release-after-move" into
"Own.NET found a leak in our real code" — is ingesting actual C# for the
narrow class of leaks the core already models: event subscriptions, timers,
IDisposable fields, ignored Subscribe results.
This is not a full C# ownership front-end (generics, async, dataflow — that is
human-years and explicitly rejected). It is a narrow, intraprocedural fact
extractor — type-aware for the one fact that needs it (the type of a += LHS),
via a project-local SemanticModel (P-014).
Recognize, in classes that look like ViewModels/Views (heuristic: name ends
ViewModel/View, derives Window/UserControl/Page, implements
INotifyPropertyChanged):
source.Event += handlerwith no matching-=in aDispose/OnClosed/Unloadedbody;Subscribe(...)whoseIDisposableresult is ignored;- (next)
DispatcherTimerstarted with noStop/Tick -=; - (next) an
IDisposablefield with no cascadeDispose.
Emit these as OwnIR facts in the spec's vocabulary (so DSL, C# and any future front-end speak one language):
acquire(Subscription, loc) // event += / Subscribe(...)
release(Subscription, loc) // event -= / token.Dispose()
owner(this, Subscription)
escapes(this, App) // strong capture by a longer-lived source
In v0 the existing core produces OWN001 (no release path) with the
[resource: subscription token] kind tag. OWN014 (region escape) is enabled
once the extractor emits the escapes(...)/lifetime facts above — that is the
next increment, not v0.
XAML / binding engine / visual tree / routed events / dependency properties /
WeakEventManager inference / Rx beyond IDisposable / every event-aggregator
library. A [OwnIgnore("reason")] suppression attribute is the escape hatch.
Recommended seam: Roslyn (C#) extractor → OwnIR facts (JSON) → the existing
Python core checks them and renders diagnostics. Do not reimplement the
checker in C# (a second checker drifts from the core — the project's own
meta-irony). The two meet through OwnIR, exactly as spec/ enables.
*.cs --[Roslyn extractor (C#)]--> facts.ownir.json --[Python core]--> OWN001/OWN014
Environment note: this sandbox has dotnet only in CI (the dotnet-golden
job). So: build and fully test the Python fact-ingest locally against
hand-written facts.ownir.json fixtures now; the Roslyn extractor is a
CI-validated C# artifact (like the golden). Land one pattern first
(event += without -=) end-to-end before adding timers/fields.
Seam: confirmResolved: extractor → versioned OwnIR JSON → Python core. The core also exposes an AST-level entry (C# extractor → OwnIR → Python core(vs all-in-C#).__main__.check_module) so the next pattern can build a module directly instead of round-tripping through.owntext.- v0 scope: one pattern first, or the four-rule set in one go.
OwnIR serialization: JSON schema vs emittingResolved: JSON, stamped with.owndirectly.ownir_version(OWNIR_VERSION, currently 0). A mismatched extractor/core pair fails loudly at load instead of being silently mis-read; the bridge maps verdicts back to C# by the diagnostic's structuredsubject, not by scraping the human message.- Heuristic vs annotation for "this class is a lifetime-bound component".