Skip to content

Quality gate, lifetimes module (WPF leaks), spec + proposals, and a real C# leak pipeline#8

Merged
PhysShell merged 10 commits into
mainfrom
claude/codegen-bugs-tests-670240
Jun 15, 2026
Merged

Quality gate, lifetimes module (WPF leaks), spec + proposals, and a real C# leak pipeline#8
PhysShell merged 10 commits into
mainfrom
claude/codegen-bugs-tests-670240

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 15, 2026

Copy link
Copy Markdown
Owner

What this is

A stack of work taking the PoC from "sound borrow checker on hand-written .own"
to "finds a real leak in real C#", with a quality gate and a normative spec under
it. Every step keeps the suite + gate green.

1. Quality gate (ruff + mypy --strict + assert_never)

  • pyproject.toml: ruff over the tree, mypy --strict on the ownlang package.
  • typing.assert_never in every node dispatch — an unhandled union variant is
    now a type error. This already caught a real gap (a buffer-let falling through
    the inline emitter) and a name collision in analysis.step.
  • AST node dataclasses are frozen=True (verified no code mutates them).
  • New CI lint job.

2. lifetimes module — WPF/business leaks (the killer use case)

  • Slice Remove unnecessary .txt extensions from example and source files #1: model a ViewModel as a scope; a subscription that is never
    disposed is the core's OWN001, a touch-after-dispose is OWN002. New
    domain-neutral kind "..." resource tag surfaces as [resource: ...] — the
    seam a WPF profile keys off without the core knowing about WPF.
  • Slice Add buffer storage policies with mandatory logging #2: lifetime regions with a strict order (lifetime Window < App;) and a subscribe self to source capture. A short-lived object captured
    by a longer-lived source is promoted and leaks → OWN014 (region escape).
    The ordering is what makes it a leak.
  • corpus/wpf/ (before/after C# + notes), tests/test_wpf.py,
    tests/test_lifetimes.py.

3. spec/ (normative) + docs/proposals/ (forward-looking)

  • spec/ retro-documents what the language is today, pinned by tests:
    OwnCore (rules R1–R12), BufferPolicies (B1–B8), Lifetimes (L1–L4),
    Diagnostics, CodegenContract (C1–C4), Grammar, CLI.
  • tests/test_spec.py — a conformance pilot: every normative rule fires on a
    canonical example (22/22), so spec and checker can't drift.
  • docs/proposals/ — RFCs for what is not built (P-001 extractor, P-002
    verification backend, P-003 visualization), each with explicit non-goals.

4. P-001 v0 — real C# → OwnIR → core

First time the tool bites real C#, along the recommended seam (one checker, not
two):

  • frontend/roslyn/OwnSharp.Extractor (syntax-only Roslyn) scans .cs for
    event += handler with no matching -=, emits OwnIR facts (JSON).
  • ownlang/ownir.py + python -m ownlang ownir facts.json lowers facts to a
    synthetic .own sketch, runs the existing core, and reports OWN001 at the
    C# location with the subscription-token tag.
  • CI job wpf-extractor runs the whole chain on sample .cs (dotnet is CI-only;
    the Python bridge is tested locally in tests/test_ownir.py).

Verification

Gate + suite green:
ruff clean · mypy --strict clean · analysis 123/123 · codegen 23/23 ·
property fuzz 3000 · gallery 10/10 · corpus 2/2 · wpf 3/3 · lifetimes 10/10 ·
spec 22/22 · ownir 5/5. The golden ArrayPool lowering and the new C# extractor
chain are compiled/run in CI.

Honesty / scope

corpus/wpf and the extractor samples are real-shaped but small; the extractor
is syntactic (heuristic, no semantic event resolution) and CI-only. Formal
soundness (Boogie/Dafny) and the broader C# patterns are tracked as proposals,
not claimed here.

https://claude.ai/code/session_01GbuRjHJERQsNaBWNibxFHv


Generated by Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ownlang ownir command to check event-subscription facts bridged from C#.
    • Added ownsharp-extract C# syntax extractor to emit JSON facts for CI validation.
    • Added lifetime region analysis with new/updated diagnostics: OWN014, OWN030, OWN031, OWN036.
    • Diagnostics now include optional resource-kind metadata tags.
  • Documentation

    • Expanded README and added specification/proposal docs (grammar, lifetimes, diagnostics, CLI, and codegen contract).
  • Tests / Quality

    • CI now runs repo linting (ruff) and strict type checking (mypy).
    • Added end-to-end extractor coverage plus WPF, lifetimes, spec conformance, and OwnIR test suites.

claude added 8 commits June 14, 2026 17:57
Python was chosen for prototype speed, but untyped dispatch hid a class of
'forgot a variant' bugs (the kind the old codegen kept reintroducing). Tighten
the screws:

- pyproject.toml: ruff (E,W,F,I,B,UP,C4,RUF) over the tree; mypy --strict on the
  ownlang package (tests are dynamic fuzzer code, ruff-only).
- typing.assert_never in every node dispatch (lower_stmt / step / _stmt_inline):
  an unhandled union variant is now a type error. This already surfaced a real
  gap -- a buffer let falling through the inline emitter.
- Fix all 39 strict-mypy findings (type params, untyped defs, union-attr
  narrowing, None-flow) and ruff findings; no behavior change (suite green:
  analysis 123/123, codegen content 23/23, fuzz, gallery 10/10, corpus 2/2).
- CI: new 'lint' job runs ruff + mypy on every push/PR.
- README: document the gate next to the regression net.
Make every ast_nodes dataclass frozen=True. Verified no code mutates AST nodes
after construction (container fields are only appended to, which frozen still
allows), so this is behavior-preserving and now blocks accidental post-parse
mutation at runtime. Gate + suite stay green.
Captures the direction before touching the parser: reframe reachability as
linear ownership (Subscribe returns an Owned token that must be Disposed),
proof that the core WPF leak already trips OWN001 today, the lifetime-region
syntax for the genuinely-new part, an OWN-WPF code catalogue mapped to slices,
and the honesty/scope caveats. Two open forks left for sign-off.
The core ownership checker already catches the main class of WPF lifetime
leaks once a ViewModel is modelled as a scope (constructor = scope start,
Dispose = scope end): a subscription/timer that is never disposed is OWN001,
touched after dispose is OWN002. Slice #1 makes that usable without inventing
WPF-specific codes:

- resource declarations gain an optional, contextual 'kind "..."' attribute
  (no new reserved word). It is domain-neutral metadata threaded onto the owning
  Symbol and surfaced on diagnostics as a '[resource: <kind>]' suffix -- the seam
  a later WPF profile / Roslyn front-end keys off, with the core staying generic.
- corpus/wpf/: two self-checking real-pattern cases (zombie ViewModel -> OWN001,
  handler-use-after-dispose -> OWN002) with before/after C#, case.own, expected
  codes and honesty notes; tests/test_wpf.py pins both the codes AND that the
  kind tag reaches rendered output.
- docs/lifetimes.md captures the full module plan (lifetime regions, OWN-WPF
  catalogue, slice boundaries); README gains a business-application section.
- bonus: assert_never now also guards lower_let's rhs dispatch; the strict gate
  caught a real 'kind' name collision in analysis.step during this work.

Gate + suite green: analysis 123/123, codegen 23/23, fuzz, gallery 10/10,
corpus 2/2, wpf 2/2.
The genuinely-new analysis on top of the ownership core: it reasons about region
escape (the WPF 'zombie ViewModel promoted to App lifetime' theorem), not just
release within a scope.

Language:
- lifetime declarations with a strict order: 'lifetime Window < App;'
  (transitive; cycles -> OWN036, undefined refs -> OWN030, redef -> OWN031).
- lifetime annotations on the object a function sets up and on its service
  params: 'fn VM(bus: EventBus lifetime App) lifetime ViewModel'.
- a strong-capture statement: 'subscribe self to SOURCE;'
  ('self'/'to' contextual; only 'lifetime'/'subscribe' are reserved; '<' lexed).

Analysis (ownlang/lifetimes.py, check_lifetimes):
- region escape OWN014: if SOURCE strictly outlives self, the strong capture
  promotes self to SOURCE's lifetime and it leaks. The *ordering* is what makes
  it a leak -- a same/shorter-lived source is clean. Per fork B the code is
  domain-neutral (OWN014 'escapes to a longer-lived region'), not WPF-branded.
- the mitigation (a disposable token released on close) is the slice-#1
  acquire/release pattern, so both halves of the theorem now exist.

Wiring/tests:
- CLI 'check' runs check_lifetimes; the new Subscribe stmt is a no-op for the
  loans/permissions flow and a schematic emit in codegen (both assert_never
  dispatchers updated; lower_let rhs got assert_never too).
- corpus/wpf/viewmodel-escapes-to-app (before/after C# + notes); test_wpf made
  tolerant of non-kinded cases. tests/test_lifetimes.py: 10 region cases.
- docs/lifetimes.md + README updated to mark slice #2 built.

Gate + suite green: mypy --strict, ruff, analysis 123/123, codegen 23/23, fuzz,
gallery 10/10, corpus 2/2, wpf 3/3, lifetimes 10/10.
…ilot

Split the project's design material into two clearly-separated tracks so
aspirational docs can never lie about the code:

spec/ -- NORMATIVE, descriptive: what OwnLang IS today, derived from the working
checker and pinned by tests.
- OwnCore.md: affine ownership + borrow permissions; identity, states, loans,
  numbered rules R1-R12, call boundary.
- BufferPolicies.md (B1-B7), Lifetimes.md (L1-L4), Diagnostics.md (every OWN code
  linked to the rule that raises it), CodegenContract.md (C1-C4: codegen may
  reject a sound program but must never emit unsafe C#).

docs/proposals/ -- FORWARD-LOOKING RFCs for what is NOT built yet, with explicit
non-goals (the discipline is refusing the soul-eating version):
- P-001 C# -> OwnIR extractor (the WPF leak spike; recommended seam + v0 scope).
- P-002 verification backend (Boogie/Dafny, horizon).
- P-003 lifetime visualization (RustOwl-style, horizon).

Conformance pilot: tests/test_spec.py runs one canonical program per normative
rule and asserts its code fires (15/15), so spec and checker cannot drift. Wired
into run_tests; README structure updated.

Gate + suite green: ruff, mypy --strict, analysis 123/123, codegen 23/23, fuzz,
gallery 10/10, corpus 2/2, wpf 3/3, lifetimes 10/10, spec 15/15.
…n conformance

Round out the normative spec so every existing surface has a reference point for
future changes:

- spec/Grammar.md: the full surface syntax -- tokens (reserved vs contextual vs
  rejected), EBNF, and a construct->spec map. Was only in a parser docstring.
- spec/CLI.md: check / emit / cfg / report and their exit semantics.
- spec/BufferPolicies.md: add the 'policy {}' block + options spec (rule B8:
  unknown/malformed keys -> OWN030).
- spec/README.md index + reading order updated.

Conformance pilot broadened to 22 rules pinning ~21 distinct codes (added
Buffer B1/B4/B8 and structural OWN031/033/035/041); the remaining codes
(maybe-variants, buffer specifics) stay covered by the buffer/analysis suites.

Gate + suite green; markdown cross-links validated.
First time the tool bites real C#, along the recommended seam: a Roslyn
extractor produces facts, the existing Python core renders the verdict -- one
checker, not two.

- ownlang/ownir.py + 'python -m ownlang ownir facts.json': ingests OwnIR facts,
  lowers them to a synthetic .own sketch (subscription = owned resource, += =
  acquire, -= = release), runs the core, and maps OWN001 back to the C# location
  with the [resource: subscription token] tag.
- frontend/roslyn/OwnSharp.Extractor: syntax-only Roslyn that scans .cs for
  'target += handler' with no matching '-=' and emits OwnIR JSON. CI-only (no
  local dotnet).
- CI job 'wpf-extractor': real .cs -> extractor -> facts -> core -> leak at the
  C# line; the disposed sample stays silent.
- tests/test_ownir.py (5 checks) pins the bridge locally against hand-written
  facts; wired into run_tests. docs/proposals/P-001 -> in progress (v0 built).

Scope v0 is exactly the event-subscription leak; timers / IDisposable fields /
region facts are next. Gate + suite green (ownir 5/5).
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 89c4cdb1-9cb0-4f40-976a-6cc635185718

📥 Commits

Reviewing files that changed from the base of the PR and between 26b868a and ef5b9ff.

📒 Files selected for processing (1)
  • ownlang/ast_nodes.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • ownlang/ast_nodes.py

📝 Walkthrough

Walkthrough

Adds lifetime-region analysis (new lifetime declarations, subscribe statement, LifetimeDecl/Subscribe AST nodes, lifetimes.py, OWN014/OWN036/OWN030/OWN031 diagnostics) and an OwnIR fact bridge (ownir.py + Roslyn C# extractor) that maps event += leaks to OWN001 findings. Threads resource_kind metadata through Symbol, Diagnostic, and the analysis pipeline. Adds three WPF corpus cases, a full spec/ directory, proposals, four new test suites, mypy-strict type cleanup, and CI lint/extractor jobs.

Changes

OwnLang Lifetime Regions, OwnIR Bridge, WPF Corpus, and Spec

Layer / File(s) Summary
AST contract: frozen nodes, LifetimeDecl, Subscribe, resource kind
ownlang/ast_nodes.py
All AST dataclasses converted to frozen=True; LifetimeDecl(name, longer, line) and Subscribe(source, line) nodes added; ResourceDecl.kind, Param.lifetime, and FnDecl.lifetime fields added; Module.lifetimes field added to store lifetime declarations; Stmt union extended to include Subscribe.
Lexer and parser: LIFETIME, SUBSCRIBE, LT tokens and lifetime/subscribe syntax
ownlang/lexer.py, ownlang/parser.py
Lexer gains LIFETIME, SUBSCRIBE, LT tokens; parser extended to recognise module-level lifetime IDENT [< IDENT]; declarations via parse_lifetime; contextual kind "..." parsed in resource members; optional lifetime IDENT annotations allowed on fn declarations and function parameters; new subscribe self to IDENT; statement parsed via parse_subscribe.
Resource kind metadata: Symbol, Diagnostic, and analysis wiring
ownlang/cfg.py, ownlang/diagnostics.py, ownlang/analysis.py
Symbol.resource_kind field added and propagated through owned-param binding, buffer acquisition lowering, and move aliasing; new collect_kinds(mod) helper computes resource-name→kind mapping; build_cfg extended to accept and forward resource_kinds dict; Diagnostic.resource_kind field added with [resource: <kind>] render suffix; analysis pipeline updated to supply resource_kind in _Analyzer.err, _state_problem, leak_check, Release, and Return diagnostics; exhaustiveness enforced with assert_never in dispatch.
Lifetime-region analysis: check_lifetimes validating ordering and emitting OWN014
ownlang/lifetimes.py, ownlang/__main__.py
New check_lifetimes(mod) validates lifetime declarations (duplicates→OWN031, undefined references→OWN030, ordering cycles→OWN036) via transitive-closure _strictly_longer computation; per-function region-escape check emits OWN014 when subscribe captures self into a longer-lived source; wired into _collect pipeline via collect_kinds and check_lifetimes; unrelated lifetimes suppress OWN014 to avoid false positives.
OwnIR fact bridge: Roslyn extractor, JSON loading, and C# location mapping
frontend/roslyn/OwnSharp.Extractor/..., frontend/roslyn/samples/..., frontend/roslyn/README.md, ownlang/ownir.py, ownlang/__main__.py, .github/workflows/ci.yml
New .NET ownsharp-extract CLI (Roslyn syntax-only) parses C# for target += handler patterns and checks for matching target -= handler within the class, emitting JSON facts with released flag; ownir.py implements load(path) (JSON validation), to_own(facts) (synthetic .own module with Subscription resource prelude), and check_facts(facts) (runs _collect, maps OWN001 findings back to C# locations); cmd_ownir added to CLI dispatcher; sample CustomerViewModel.cs (leak) and OrdersViewModel.cs (clean) included; CI wpf-extractor job validates end-to-end.
WPF corpus: three bug patterns with before/after C#, case.own reduction, and expected diagnostics
corpus/wpf/zombie-viewmodel/..., corpus/wpf/viewmodel-escapes-to-app/..., corpus/wpf/handler-use-after-dispose/...
Three WPF pattern corpus cases added under corpus/wpf/: zombie-viewmodel (OWN001 leaked subscription token from no-dispose-path event subscription), viewmodel-escapes-to-app (OWN014 region-escape promotion from window-scoped VM subscribing to app-lifetime bus), handler-use-after-dispose (OWN002 use-after-release from queued callbacks touching disposed subscription). Each includes before.cs (buggy), after.cs (fixed), case.own (OwnLang reduction), expected-diagnostics.txt, and notes.md with honesty/scope notes.
Code generation and type-safety cleanup
ownlang/codegen.py, ownlang/buffers.py, ownlang/report.py, pyproject.toml, .github/workflows/ci.yml
codegen.py adds A.Subscribe lowering (emits .Subscribe(this) with lifetime-capture comment); adds assert isinstance(st, A.Let) in _emit_hoist; raises explicit CodegenError if buffer let reaches inline emitter; tightens type annotations across buffers.py (BufferInfo.branches()→list[dict[str, str]], Policy.dups→tuple[str, ...], resolve(intent: A.BufferIntent, ...)), report.py (build_report/render_report→dict[str, Any]), codegen helpers (Iterator, assert_never, parameter/return types); adds pyproject.toml with ruff (100-char line, py311 target, high-signal rules) and mypy strict for ownlang package; CI lint job runs ruff check . and mypy --strict ownlang.
Test infrastructure: four new test runners and run_tests.py wiring
tests/test_wpf.py, tests/test_lifetimes.py, tests/test_ownir.py, tests/test_spec.py, tests/fixtures/ownir/sample.facts.json, tests/run_tests.py, tests/test_codegen.py, tests/test_codegen_props.py, tests/test_corpus.py, tests/test_gallery.py
Four new standalone test runners: test_wpf.py (corpus-driven WPF case validation with code and resource-kind tag checks), test_lifetimes.py (CASES table for lifetime ordering/escape analysis with pretty-render headline verification), test_ownir.py (bridge fixture tests using sample.facts.json), test_spec.py (spec conformance pilot validating normative rule diagnostics). Adds tests/fixtures/ownir/sample.facts.json (WpfApp module with two component entries). Updates run_tests.py to import and execute all four new suites, combining return codes. Cleans up inline # noqa: E402 and # noqa: BLE001 comments from imports in existing test files to comply with ruff.
Specification, proposals, design doc, and README updates
spec/..., docs/lifetimes.md, docs/proposals/..., README.md, corpus/real-world/arraypool-use-after-return/notes.md
Adds full spec/ directory: OwnCore.md (resource identity, state semantics, loan model, R1–R12 rules), Grammar.md (syntax, construct→spec map, OWN020 scope), Diagnostics.md (code catalog, rendering, resource-kind suffix), Lifetimes.md (region ordering, validation rules, escape semantics), BufferPolicies.md (storage modes, constraints, report format), CodegenContract.md (safety, release guarantees, lowering modes), CLI.md (commands, exit codes), spec/README.md (reading guide). Adds docs/lifetimes.md design doc (Russian-language motivation, slice planning, WPF→OWN catalog). Adds docs/proposals/ with README.md (proposal index), P-001-csharp-extractor.md (v0 Roslyn pipeline), P-002-verification-backend.md (Boogie soundness), P-003-lifetime-visualization.md (visualization). Updates README.md with "Business-application" WPF section, new diagnostic codes (OWN014, OWN036, OWN030), expanded module layout (lifetimes.py, ownir.py, codegen.py, report.py, CLI), and CI quality-gate notes. Fixes arraypool-use-after-return/notes.md honesty/scope text.

Sequence Diagram(s)

sequenceDiagram
  participant src as .own source (with lifetimes)
  participant parser as parser.parse_module
  participant lf as lifetimes.check_lifetimes
  participant cfg as cfg.collect_kinds + build_cfg
  participant an as analysis.analyze
  
  parser->>parser: parse lifetime decls + subscribe stmts
  parser-->>src: Module(lifetimes=[...], fns=[...])
  lf->>lf: validate ordering, transitive closure
  lf->>lf: per-fn subscribe escape check
  lf-->>src: OWN014/OWN030/OWN031/OWN036 diagnostics
  cfg->>cfg: collect_kinds + lower subscribe as no-op
  cfg->>cfg: set Symbol.resource_kind on acquire/move
  an->>an: leak_check / Release / Return with resource_kind
  an-->>src: OWN001/OWN002 diagnostics + [resource: kind] suffix
Loading
sequenceDiagram
  participant cs as C# source files
  participant ext as OwnSharp.Extractor (Roslyn)
  participant json as facts.json
  participant ownir as ownir.check_facts
  participant core as OwnLang core (_collect)
  participant cli as python -m ownlang ownir
  
  cli->>ext: run ownsharp-extract *.cs -o facts.json
  ext->>cs: parse syntax trees
  ext->>json: emit JSON (components + subscriptions)
  cli->>ownir: load(facts.json)
  ownir->>ownir: to_own(facts) → synthetic .own module
  ownir->>core: parse + collect_kinds + build_cfg + analyze
  core-->>ownir: OWN001 diagnostics with sub_<id> subjects
  ownir->>ownir: extract handle, map back to C# file:line
  ownir-->>cli: Finding list (CustomerViewModel.cs:12 OWN001)
  cli->>cli: print findings + exit code
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • PhysShell/Own.NET#6: Both PRs directly touch the diagnostic-rendering pipeline in ownlang/diagnostics.py and ownlang/__main__.py, implementing rustc-style pretty output with caret and identifier highlighting, making the changes code-level related.

Poem

🐇 A rabbit hops through lifetimes with care,
Subscriptions released—no leaks in the air!
OwnIR facts flow from C# to .own,
The zombie ViewModels? Each one is now known.
With specs in the warren and corpus aligned,
The checker's exhaustive—no path left behind! 🌿

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/codegen-bugs-tests-670240

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ownlang/__main__.py (1)

79-92: ⚠️ Potential issue | 🟠 Major

cmd_cfg is missing the resource_kinds argument passed elsewhere.

Line 45 passes all five arguments to build_cfg including kinds, but Line 90 passes only four (omitting resource_kinds). While this won't crash at runtime because resource_kinds has a default value, the inconsistency suggests this call was not updated during the refactor. Verify whether cmd_cfg should pass resource_kinds explicitly or if relying on the default value is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ownlang/__main__.py` around lines 79 - 92, The build_cfg function call in
cmd_cfg is missing the resource_kinds argument that is passed explicitly at line
45 elsewhere in the code. Update the build_cfg call on line 90 (within the for
loop iterating over mod.functions) to include the resource_kinds argument
explicitly, making it consistent with other invocations of build_cfg in the
codebase. Determine the appropriate value for resource_kinds based on how it is
computed at the other call site to ensure consistency across all build_cfg
invocations.
docs/lifetimes.md (1)

1-171: ⚠️ Potential issue | 🟡 Minor

Fix missing code block language specifier and align documentation language with project norms.

The fenced code block at line 23–29 lacks a language identifier; it should be marked ```ownlang instead of bare ``` (MD040 violation).

Additionally, docs/lifetimes.md is written entirely in Russian while all specification documents (spec/*.md) and project documentation are in English. Although spec/Lifetimes.md provides an English normative source, this draft design document should be translated to English for consistency and accessibility, or clearly documented as a secondary non-normative artifact if the dual-language approach is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/lifetimes.md` around lines 1 - 171, Add the `ownlang` language
identifier to the fenced code block at lines 23–29 that currently lacks one
(change ` ``` ` to ` ```ownlang `). Additionally, translate the entire
`docs/lifetimes.md` document from Russian to English for consistency with the
project's specification documents and normative documentation (all `spec/*.md`
files), or if maintaining a dual-language approach is intentional, add a clear
notice at the beginning stating that this is a secondary non-normative artifact
with `spec/Lifetimes.md` as the authoritative English source.
🧹 Nitpick comments (1)
corpus/wpf/viewmodel-escapes-to-app/notes.md (1)

29-32: ⚡ Quick win

Clarify C# ingestion scope to avoid stale wording.

Line 29 currently says OwnLang has no C# front-end, but this PR introduces C# ingestion via the Roslyn extractor + OwnIR bridge. Please reword this to distinguish “no direct C# parsing in ownlang check” from “C# facts are supported via ownir,” and apply the same wording to corpus/wpf/handler-use-after-dispose/notes.md Line 20.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@corpus/wpf/viewmodel-escapes-to-app/notes.md` around lines 29 - 32, The note
at corpus/wpf/viewmodel-escapes-to-app/notes.md Line 29 incorrectly states there
is no C# front-end, but the PR now enables C# ingestion through the Roslyn
extractor and OwnIR bridge. Reword Line 29 to clarify that `ownlang check` has
no direct C# parser, but C# facts are supported via the `ownir` intermediate
representation. Apply the same clarified wording to
corpus/wpf/handler-use-after-dispose/notes.md Line 20 to maintain consistent
documentation across both files and accurately reflect that C# ingestion is now
available through the OwnIR bridge rather than being completely unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 10-17: The CI workflow is relying on default GitHub token
permissions which may be overly broad. Add an explicit permissions block at the
workflow level to set a read-only baseline (permissions: read-only) and then
only elevate specific permissions for the lint job or other jobs that require
additional access. This follows the principle of least privilege for the
GITHUB_TOKEN.

In `@corpus/wpf/zombie-viewmodel/notes.md`:
- Around line 29-31: The statement in notes.md at the identified location
contains outdated information claiming OwnLang has no C# front-end, which now
conflicts with the addition of the Roslyn extractor/OwnIR bridge to the stack.
Update the wording to acknowledge that while a C# front-end (Roslyn extractor)
now exists, the specific case.own example discussed in this document is a
hand-reduced version created manually rather than directly produced from the
extractor output. This clarification should help readers understand the
relationship between the extracted pattern and the manual example being
analyzed.

In `@docs/lifetimes.md`:
- Around line 23-29: The fenced code block containing lifetime and function
declarations is missing a language identifier. Add ownlang as the language
specifier to the opening fence (the triple backticks before the lifetime App;
line) to enable proper syntax highlighting and follow markdown best practices
for code blocks.

In `@docs/proposals/P-001-csharp-extractor.md`:
- Line 21: The document contains inconsistent claims about OWN014 region
facts—line 21 lists them as "next" (future work), but the v0 scope section
(lines 57-58) indicates they are already produced. Review both locations to
determine whether OWN014 region facts are actually part of the current v0
implementation or are planned for future work, then update line 21 accordingly
to match the v0 scope claim. Additionally, audit the resource-kind label
terminology throughout the document and ensure it aligns with the actual
implemented naming convention of "subscription token" to prevent confusion about
what features are delivered versus planned.

In `@ownlang/ast_nodes.py`:
- Around line 52-53: Frozen dataclasses with mutable container fields can be
modified after construction, breaking the immutability contract. Convert all
list fields to tuple types (e.g., args: list[Expr] becomes args: tuple[Expr,
...]) and all dict fields to frozendict types (e.g., options: dict[str, Expr]
becomes options: frozendict[str, Expr]) across all affected nodes. Specifically,
in the Acquire class convert the args field, in the Call class convert the args
field, in BufferIntent convert the options field, in BorrowBlock convert the
body field, in the If class convert both then_body and else_body fields, in
ResourceDecl convert the members field, in ExternDecl convert the params field,
in FnDecl convert both params and body fields, in PolicyDecl convert the
settings field, and in the Module class update all fields that use
default_factory to return frozen containers instead. You may need to import
frozendict from an appropriate library or create a frozen dict type if not
already available.

In `@ownlang/lifetimes.py`:
- Around line 86-87: The early return at lines 86-87 exits before validating
function and parameter lifetime annotations, which causes undeclared lifetime
annotations to not emit the OWN030 diagnostic and skips subscribe checks for
those functions. Move the early return to after the validation logic (such as
the _check_fn calls) so that all functions are properly validated for undeclared
lifetime annotations regardless of whether mod.lifetimes is empty, then return
the diagnostics after validation is complete.

In `@ownlang/ownir.py`:
- Around line 66-93: The to_own function assumes every item in the components
list and subscriptions list is a dictionary without validating the schema first.
Add schema validation at the start of the to_own function to check that
facts.get("components", []) contains only dictionaries, and that each
component's subscriptions (from comp.get("subscriptions", [])) also contains
only dictionaries. If validation fails, raise a ValueError with a descriptive
message instead of allowing the code to crash when calling .get() on
non-dictionary items. This validation should occur before the iteration loops at
lines starting with "for comp in facts.get" and "for sub in comp.get".

In `@spec/OwnCore.md`:
- Around line 132-134: The spec documentation in spec/OwnCore.md at lines
132-134 inaccurately describes the test behavior, claiming that
tests/test_spec.py asserts the exact code when it actually only checks
membership (verifying that the rule code is among the produced diagnostics).
Update the wording in this section to accurately reflect that the suite checks
for rule code membership in the diagnostics rather than asserting exact code
equivalence.

In `@spec/README.md`:
- Line 25: The lifetime rules range mentioned in the README is incomplete and
does not reflect all normative rules defined in the Lifetimes specification.
Update the text that currently states "Lifetimes L1–L3" to "Lifetimes L1–L4" to
include the L4 rule (resource kind metadata) that is defined as normative in the
provided Lifetimes.md specification document.

In `@tests/test_lifetimes.py`:
- Around line 163-166: Add a guard check before accessing `diags[0]` to verify
that the diagnostics list is not empty. If `check_lifetimes(parse(escape_src))`
returns no diagnostics, the code will raise an IndexError and fail the test
suite unexpectedly. Insert a check (such as an assertion or conditional) that
ensures `diags` contains at least one element before calling
`diags[0].render_pretty()`, so the test fails with a clear diagnostic message
instead of crashing.

In `@tests/test_wpf.py`:
- Around line 101-104: The `set()` conversion on line 101 removes duplicate
diagnostic codes, which masks regressions with duplicate diagnostics. Replace
`sorted(set(codes))` with `sorted(codes)` to preserve the multiplicity of each
diagnostic code in the comparison between `got` and `want`.

---

Outside diff comments:
In `@docs/lifetimes.md`:
- Around line 1-171: Add the `ownlang` language identifier to the fenced code
block at lines 23–29 that currently lacks one (change ` ``` ` to ` ```ownlang
`). Additionally, translate the entire `docs/lifetimes.md` document from Russian
to English for consistency with the project's specification documents and
normative documentation (all `spec/*.md` files), or if maintaining a
dual-language approach is intentional, add a clear notice at the beginning
stating that this is a secondary non-normative artifact with `spec/Lifetimes.md`
as the authoritative English source.

In `@ownlang/__main__.py`:
- Around line 79-92: The build_cfg function call in cmd_cfg is missing the
resource_kinds argument that is passed explicitly at line 45 elsewhere in the
code. Update the build_cfg call on line 90 (within the for loop iterating over
mod.functions) to include the resource_kinds argument explicitly, making it
consistent with other invocations of build_cfg in the codebase. Determine the
appropriate value for resource_kinds based on how it is computed at the other
call site to ensure consistency across all build_cfg invocations.

---

Nitpick comments:
In `@corpus/wpf/viewmodel-escapes-to-app/notes.md`:
- Around line 29-32: The note at corpus/wpf/viewmodel-escapes-to-app/notes.md
Line 29 incorrectly states there is no C# front-end, but the PR now enables C#
ingestion through the Roslyn extractor and OwnIR bridge. Reword Line 29 to
clarify that `ownlang check` has no direct C# parser, but C# facts are supported
via the `ownir` intermediate representation. Apply the same clarified wording to
corpus/wpf/handler-use-after-dispose/notes.md Line 20 to maintain consistent
documentation across both files and accurately reflect that C# ingestion is now
available through the OwnIR bridge rather than being completely unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c5199251-b9c6-42a6-b920-b3cae065a168

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd3842 and 375fa70.

📒 Files selected for processing (59)
  • .github/workflows/ci.yml
  • README.md
  • corpus/wpf/handler-use-after-dispose/after.cs
  • corpus/wpf/handler-use-after-dispose/before.cs
  • corpus/wpf/handler-use-after-dispose/case.own
  • corpus/wpf/handler-use-after-dispose/expected-diagnostics.txt
  • corpus/wpf/handler-use-after-dispose/notes.md
  • corpus/wpf/viewmodel-escapes-to-app/after.cs
  • corpus/wpf/viewmodel-escapes-to-app/before.cs
  • corpus/wpf/viewmodel-escapes-to-app/case.own
  • corpus/wpf/viewmodel-escapes-to-app/expected-diagnostics.txt
  • corpus/wpf/viewmodel-escapes-to-app/notes.md
  • corpus/wpf/zombie-viewmodel/after.cs
  • corpus/wpf/zombie-viewmodel/before.cs
  • corpus/wpf/zombie-viewmodel/case.own
  • corpus/wpf/zombie-viewmodel/expected-diagnostics.txt
  • corpus/wpf/zombie-viewmodel/notes.md
  • docs/lifetimes.md
  • docs/proposals/P-001-csharp-extractor.md
  • docs/proposals/P-002-verification-backend.md
  • docs/proposals/P-003-lifetime-visualization.md
  • docs/proposals/README.md
  • examples/golden_arraypool/verify_emit.py
  • frontend/roslyn/OwnSharp.Extractor/OwnSharp.Extractor.csproj
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/README.md
  • frontend/roslyn/samples/CustomerViewModel.cs
  • frontend/roslyn/samples/OrdersViewModel.cs
  • ownlang/__main__.py
  • ownlang/analysis.py
  • ownlang/ast_nodes.py
  • ownlang/buffers.py
  • ownlang/cfg.py
  • ownlang/codegen.py
  • ownlang/diagnostics.py
  • ownlang/lexer.py
  • ownlang/lifetimes.py
  • ownlang/ownir.py
  • ownlang/parser.py
  • ownlang/report.py
  • pyproject.toml
  • spec/BufferPolicies.md
  • spec/CLI.md
  • spec/CodegenContract.md
  • spec/Diagnostics.md
  • spec/Grammar.md
  • spec/Lifetimes.md
  • spec/OwnCore.md
  • spec/README.md
  • tests/fixtures/ownir/sample.facts.json
  • tests/run_tests.py
  • tests/test_codegen.py
  • tests/test_codegen_props.py
  • tests/test_corpus.py
  • tests/test_gallery.py
  • tests/test_lifetimes.py
  • tests/test_ownir.py
  • tests/test_spec.py
  • tests/test_wpf.py

Comment thread .github/workflows/ci.yml
Comment thread corpus/wpf/zombie-viewmodel/notes.md Outdated
Comment thread docs/lifetimes.md Outdated
Comment on lines +23 to +29
```
ownlang/
core states/lattice/dataflow/diagnostics (= нынешние analysis/cfg/diagnostics)
buffers профиль OwnSharp.Performance (есть)
lifetimes профиль OwnSharp.Lifetimes (этот док)
frontend/csharp Roslyn-ингест (далёкая фаза)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language specifier to fenced code block.

The code block at line 23–29 is missing a language identifier. Based on context (lifetime and function declarations), it should specify ownlang.

📝 Proposed fix
-```
+```ownlang
 lifetime App;
 lifetime Window < App;            // Window строго короче App
 lifetime ViewModel < Window;

 fn CustomerViewModel(bus: EventBus lifetime App) lifetime ViewModel {
     subscribe self to bus;        // bus сильно держит self
 }
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/lifetimes.md` around lines 23 - 29, The fenced code block containing
lifetime and function declarations is missing a language identifier. Add ownlang
as the language specifier to the opening fence (the triple backticks before the
lifetime App; line) to enable proper syntax highlighting and follow markdown
best practices for code blocks.

Comment thread docs/proposals/P-001-csharp-extractor.md
Comment thread ownlang/ast_nodes.py
Comment thread ownlang/ownir.py
Comment thread spec/OwnCore.md Outdated
Comment thread spec/README.md
Comment thread tests/test_lifetimes.py Outdated
Comment thread tests/test_wpf.py Outdated
claude added 2 commits June 15, 2026 09:22
Code / correctness:
- lifetimes.py: drop the early-return on empty mod.lifetimes so a 'fn/param
  lifetime X' annotation referencing an undeclared region still emits OWN030
  (was silently accepted with no declarations). Real soundness gap.
- ownir.py: shape-check OwnIR facts (root/components/subscriptions) in load()
  AND to_own(), raising a clear ValueError on malformed input instead of a
  deep traceback.

Tests:
- test_lifetimes.py: guard the headline render check against empty diags
  (pick the OWN014 diagnostic explicitly).
- test_wpf.py: compare sorted(codes), not sorted(set(...)), so a duplicate
  diagnostic is a regression, not masked.

CI hardening:
- ci.yml: add least-privilege 'permissions: contents: read' (action
  SHA-pinning stays deferred per README #7).

Docs (stale-claim cleanup now that the P-001 extractor exists):
- corpus notes + P-001: reword 'no C# front-end' to 'not direct extractor
  output; the P-001 extractor is narrow (event subscriptions)'.
- P-001: v0 produces OWN001 with [resource: subscription token]; OWN014 is the
  next increment, not v0.
- spec/OwnCore.md: conformance asserts the code is *among* produced codes
  (membership), matching test_spec.
- docs/lifetimes.md: add 'text' language to the directory-tree fence (MD040).

Gate + suite green (analysis 123/123, spec 22/22, wpf 3/3, lifetimes 10/10,
ownir 5/5).
@PhysShell
PhysShell merged commit 4ac2ae7 into main Jun 15, 2026
15 checks passed
PhysShell pushed a commit that referenced this pull request Jun 27, 2026
…wtonsoft run

First entries where Own.NET (not the oracle) over-reports, triaged from the
4 product own-only findings on JamesNK/Newtonsoft.Json:

- #7 event '+=' on a freshly-created, RETURNED publisher (JsonSerializer:717):
  the dual of ownership transfer; bounded by the returned object's lifetime, so
  no '-=' needed. Already tiered as an advisory warning ('injected'), not a hard
  error — surfaces only under --severity warning. Proper fix is interprocedural
  publisher provenance.
- #8 owning field whose IDisposable holds no unmanaged resource (TraceJsonReader/
  Writer StringWriter/JsonTextWriter): a StringWriter is StringBuilder-backed, not
  a handle. Fixable by adding System.IO.StringWriter/StringReader to the existing
  IsDisposeOptional allowlist (mirrors Task/DataTable); the JsonTextWriter wrapper
  stays a low-value residual.

Both verdicts: not real leaks, criticality ~0. CodeQL/Infer# correctly silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMngnax1jsy378fSAZb9r
PhysShell pushed a commit that referenced this pull request Jun 27, 2026
A StringWriter is StringBuilder-backed and a StringReader reads a string — neither
holds an OS handle or unmanaged resource, so Dispose() frees nothing real and an
undisposed owning field of these is not a leak. Add System.IO StringWriter/StringReader
to IsDisposeOptional (the same lever already used for Task/ValueTask/DataTable/DataSet/
DataView), so the owned-field detector stays silent on them. MemoryStream is deliberately
NOT exempted — it can own a real buffer and still warns.

Surfaced by the oracle on Newtonsoft.Json (field-notes #8): TraceJsonReader/TraceJsonWriter
own undisposed StringWriter/JsonTextWriter fields; CodeQL/Infer# correctly stay silent on
the StringWriter (not a real resource), Own.NET over-reported. Adds a HolderWithStringWriter
control to ResolvedDisposableSample + a CI assertion it stays silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YMngnax1jsy378fSAZb9r
PhysShell pushed a commit that referenced this pull request Jul 10, 2026
…pply shape bounded

The dual of ownership transfer, interprocedural: a += on a PARAMETER publisher
is locally indistinguishable from a DI-singleton bus, so it honestly warns
(OWN001, injected tier). The extractor now runs a lazy, memoized,
compilation-wide provenance pass: when the subscribing method is
private/internal and EVERY visible caller passes a freshly-constructed local
that escapes only into the call / its own return (and the callee never lets
the param escape), the subscription is stamped
source_provenance: "returned_fresh" and the bridge drops it — bounded by the
returned publisher's lifetime, same boundedness as a locally-constructed
source. Instance-level provenance deliberately beats the type-level DI hop.

Precision-first denials (all keep the warning, pinned by
ReturnedPublisherSample.cs in CI + test_ownir.py): public candidate,
method-group reference, named/ref/omitted argument, non-fresh/non-local
argument, any other use of the caller's local (field store, other callee,
lambda capture, reassignment), param->param forwarding, zero visible callers.
Constructors are excluded by construction (MethodKind gate), so the
ctor-injected DI shape can never be silenced.

Clears the mined Newtonsoft JsonSerializer.Create over-report (field-notes #8);
its oracle-fp-baseline entry is REMOVED so a regression resurfaces in triage
instead of being swallowed by the allowlist. Spec'd in OwnIR.md §4 + the JSON
schema (additive optional field, no version bump per IR rules).

Closes #146

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
PhysShell pushed a commit that referenced this pull request Jul 10, 2026
…nction denial cases

CodeRabbit nitpicks on #208: the returned-fresh denial-case enumerations in
field-notes #8 and oracle-known-fps predate c3cb656, so they omitted the two
local-function closure captures; and the disposition summary still counted
serializer.Error as baselined (Fixed 6->7, Baselined 5->4, 8 findings/7 rules
-> 7 findings/6 rules).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
PhysShell added a commit that referenced this pull request Jul 16, 2026
…ent-leaks, fallback, Action, docs)

Addresses the REQUEST CHANGES review on PR #284. Scope stays B0+B1.

1. Handler-shape guard. MatchesDeclaredWeakSubscribe now runs the second argument
   through NormalizeHandler + IsHandler, so a declared overload whose 2nd parameter is
   not a delegate -- AddPropertyChanged(source, 42) -- is NOT minted as a subscription.
   New negative control NonHandlerSecondArgument (zero facts).

2. --no-event-leaks respected. The new detector is gated on `emitEvents &&
   weakSubscribe.Count > 0` (was just Count > 0), and the Rx-Subscribe suppression only
   fires when emitEvents is on. New facts case: sample + --no-event-leaks + declared ->
   neither the weak wrapper nor the ordinary += is emitted.

3. Real unresolved-external fixture. WeakSubscribeUnresolvedSample.cs calls
   External.WeakEvents.AddPropertyChanged (type not in the compilation) so the syntactic
   receiver-name fallback is exercised (acceptance #8), plus an unresolved negative
   control with a different final receiver type name.

4. Composite Action exposes the feature. action.yml gains an optional `config:` input,
   forwarded as OWN_CONFIG and passed to own-check (--config) on both the SARIF and the
   normal path. A real Action-level test (action-marketplace-readiness.yml, uses: ./):
   a weak wrapper is a finding WITHOUT config and silent WITH it, and a malformed config
   hard-errors through the Action.

5. Docs synced to reality. P-035 status + "What exists today" (method-call wrapper is
   detected, config is consumed; method detector folded into P-035; A and C deferred);
   P-015 (format resolved: explicit --config own.toml TOML carrier, rest deferred);
   spec/CLI.md (the `config` command + own-check --config).

Verified locally via WSL: extractor facts for all four configs (on/off/no-events/
unresolved) pass tests/check_weak_subscribe_facts.py; the WeakBus fixture is OWN001
without --config and silent with it; ruff + mypy --strict + run_tests.py all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants