Skip to content

docs: interprocedural roadmap — staged path from D5 today to 'proper interprocedural' - #177

Merged
PhysShell merged 13 commits into
mainfrom
claude/project-issues-review-arws0n
Jul 8, 2026
Merged

docs: interprocedural roadmap — staged path from D5 today to 'proper interprocedural'#177
PhysShell merged 13 commits into
mainfrom
claude/project-issues-review-arws0n

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Сводит разрозненное состояние межпроцедурного слоя (d5-нота, tech-debt register §3, трекеры #122/#146, arity-остаток d5 §10 q2) в один секвенированный roadmap (стадии 0–6: спека bridge-инференции → наблюдаемость OWN051/MOS-дамп → arity-ключ → aliasOf через return → publisher provenance → exclusivity → Tier C аннотации), плюс выполняет стадии 0–1 по ходу: нормативная спека spec/Inference.md + рабочее ТЗ, CLI-команда summaries (детерминированный parity-дамп MOS), advisory-коды OWN051/OWN052, и исправления, найденные при написании ТЗ (D1 частичный release → may; D2 мёртвая ось escapes; D5 наблюдаемая деградация решателя; D7 ранний return до форварда → may; kill-site-уточнение untrack-семантики — до-вызовные вердикты больше не глотаются).

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация

Как проверено

  • python tests/run_tests.py — зелёный (132/132 analysis, 248/248 bridge, 45/45 ownership, 70/70 obligations, 3000 fuzz)
  • ruff check . и mypy — чисто
  • оба замечания Codex (P1 untrack / P2 early-return forward) воспроизведены на минимальных фактах до фикса и запиннены регрессионными тестами после

Связанные issue

Refs #122, #146 (deferred-трекеры, секвенированы в roadmap). Closes нет.

Чеклист

  • изменение покрыто тестом/селфтестом (3 новых пина в test_ownir.py + существующие D1/D5/D6-матрицы)
  • README/docs обновлены при необходимости (spec/Inference.md, spec/Diagnostics.md, spec/README.md, roadmap + ТЗ, d5-нота)
  • коммиты в conventional-commit стиле (docs:, fix:, feat:, spec:)

Co-Authored-By: Claude noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi

Summary by CodeRabbit

  • New Features
    • Added an ownlang summaries CLI command to emit deterministic solved method-ownership summaries from an OwnIR facts file.
    • Added a public dump_summaries API for generating summaries, unresolved entries, and degradation details.
  • Diagnostics
    • Introduced advisory codes OWN051 and OWN052 for unverified interprocedural ownership at call sites and interprocedural inference failures.
  • Bug Fixes
    • Improved consume/release precision across all normal-return paths.
    • Enhanced determinism (stable summary output) and surfaced solver failures via advisories instead of silent degradation.
  • Documentation
    • Expanded the interprocedural roadmap and added/updated interprocedural inference/TZ specifications, including deferred sequencing cross-references.
  • Tests
    • Updated/added conformance, regression, and byte-identical output checks for new advisories and summaries.

claude added 3 commits July 4, 2026 16:14
…interprocedural'

Consolidates the scattered interprocedural state (d5 note's shipped
slices, tech-debt register §3 prerequisite, deferred trackers #122/#146,
the arity residual of d5 §10 q2) into one sequenced roadmap:

- stage 0: spec the verdict-determining bridge inference + golden facts
  (the P-022 hard prerequisite, unlocks everything else)
- stage 1: observability first — OWN051 advisory + MOS summary dump
- stage 2: arity/signature key on call ops (additive optional field,
  no OWNIR_VERSION bump) to stop conservative overload merging
- stage 3: aliasOf:i through return + out/ref fresh (Dapper/Polly
  become oracle anchors with a recorded reason)
- stage 4: publisher provenance (#146) on top of stage 3's skeletons
- stage 5: exclusivity axis (#122) strictly on its consumer trigger
- stage 6: Tier C annotations + pessimistic mode

Includes a definition of done, a dependency graph, on-record rejections
(context sensitivity, IL decompilation, frontend inference), and a
reading path into ownership.py/ownir.py for newcomers. Cross-linked
from the d5 note's §10 open questions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
…(P-022)

Records the decision that the core rewrite is now in execution and applies
the 'one checker' guardrail to the roadmap: a feature freeze on
verdict-changing inference until parity/cutover (a new axis in Rust before
parity diverges from the reference; a new axis in Python moves the parity
target the port must chase).

Stage re-keying: stage 0 (spec the bridge inference) is language-neutral
and is the port's prerequisite — do now; stage 1's summary dump is
requalified as a parity artifact (diff Python vs Rust MOS summaries);
stage 2's extractor half runs parallel to the port while its consuming
half, stages 3-4 and 6 land in Rust after cutover; #122 is unaffected.
Also records what ports cheaply (the pure ownership.py solver, the
language-neutral synthetic OwnIR tests) and the on-record caveat that an
urgent precision fix during the port goes into Python and knowingly moves
the parity target.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
…invariants, Rust-port requirements

Deep-dive companion to interprocedural-roadmap.md. Pins the layer's
actual semantics to code at adc1496 with file:line references:

- normative rules as implemented: the Transfer lattice and path
  contributions, skeleton derivation (S), return inference (R),
  overload merging (M), the SCC fixpoint (F), call-site application (A)
  and tier precedence — the raw material for spec/Inference.md (stage 0)
- five spec decision points (D1-D5), including a REPRODUCED precision
  defect: any-path release infers consume, so a callee that disposes a
  parameter conditionally fabricates OWN002 at a careful caller and
  OWN001 in the callee itself (violates the own-only-0 floor; verified
  by executing check_facts on synthetic facts). Recommended fix:
  branch-sensitive release derivation (all-paths -> dispose, partial ->
  may -> plain), to be decided BEFORE the port so bit-parity does not
  enshrine the bug. Also: the dead escapes axis, reserved adopt/return
  path kinds, the aliasOf hop degradation, and the silent solver
  degrade (except -> empty MOS) that should become observable
- twelve numbered invariants (precision floor, determinism-from-input,
  tier precedence, closed op vocabulary, byte-exact output texts) as
  review blockers
- Rust-port requirements for own-bridge: type mapping, determinism
  rules (no hash-order reaching output), error policy matching the
  Python degrade, --dump-summaries as a third oracle_exact surface,
  and the port order (solver -> derivation -> application), each phase
  behind its parity gate
- concrete work cards for roadmap stages 2-4: signature key with
  fallback rules that preserve Tier A suppression, aliasOf:i through
  return (first slice, direct calls only), and #146 with a hard
  visibility gate — each with its test matrix and DoD

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds interprocedural ownership-transfer documentation, a new summaries CLI command, MOS-driven advisory reporting, and updated diagnostics, serialization, and tests for the bridge.

Changes

Interprocedural ownership layer

Layer / File(s) Summary
Roadmap intro and sequencing
docs/notes/d5-ownership-transfer.md, docs/notes/interprocedural-roadmap.md
Adds the roadmap framing, target-state criteria, current transfer directions, deferred-item triggers, and the D5 cross-reference to the roadmap.
Roadmap stages and constraints
docs/notes/interprocedural-roadmap.md
Adds the Rust-port mapping, stage plan, ordering/dependency notes, excluded approaches, and reading guide for the interprocedural roadmap.
Normative interprocedural specification
docs/notes/interprocedural-tz.md, spec/Inference.md, spec/Diagnostics.md, spec/README.md
Defines the interprocedural ownership inference specification, divergence resolutions, invariants, Rust-port requirements, diagnostics coverage, and the conformance checklist.
Summary shape and diagnostic catalog
ownlang/ownership.py, ownlang/diagnostics.py, tests/test_ownership.py
Updates ownership-action documentation, reserves the escapes axis, removes escapes from serialized summaries, registers the new diagnostic codes, and adds the serialization test for escapes omission.
MOS bridge and lowering
ownlang/ownir.py
Extends MOS solving observability, adds advisory capture for unverified transfers, propagates untracked lowering state, stabilizes summary generation, and introduces dump_summaries.
CLI and behavioral tests
ownlang/__main__.py, tests/test_ownir.py
Adds the summaries CLI command, broadens diagnostic handling in the CLI, and updates OwnIR tests for advisory reporting, degraded solving, deterministic dumps, and escapes omission.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • PhysShell/Own.NET#14: Changes the same OwnIR-to-core bridge path that this PR extends with MOS-solving notes and advisories.
  • PhysShell/Own.NET#111: Shares the same D5 interprocedural ownership-transfer work and summary serialization surface.
  • PhysShell/Own.NET#168: Overlaps on the interprocedural Rust-port roadmap and the own-bridge/spec alignment work.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the PR's main change: a staged interprocedural roadmap from D5 toward proper interprocedural behavior.
Description check ✅ Passed The description follows the template with all required sections filled, including change type, verification, related issues, and checklist items.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/project-issues-review-arws0n

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d25a8b425c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/notes/interprocedural-tz.md Outdated
claude added 6 commits July 4, 2026 16:52
…Z D1)

The skeleton derivation and the direct contract inference collapsed the
release signal to a boolean: a release on ANY path produced a lone
dispose path action, so a callee that disposes a parameter conditionally
(the ubiquitous null-guard dispose-helper idiom, `if (x != null)
x.Dispose()`) was inferred `consume`. That flattened `must` charged a
caller's defensive dispose a false OWN002 and the helper itself a false
OWN001 — reproduced on synthetic facts before the fix, and a direct hit
on the own-only-0 precision floor. The d5 note always defined `must` as
'released on ALL normal-return paths'; the code now matches it.

`_definite_release` walks the op tree tracking the release state of live
paths (released on none / some / all) plus whether every early `return`
was reached already-released: release in both branches or before every
exit stays a definite dispose (consume recall preserved — the careless
caller keeps its true OWN002); a one-branch, in-loop (zero-trip), or
guarded-by-early-return release now also emits a kept path, so the
lattice joins to `may` and the caller stays plain (silent). The walk
ignores only ops that cannot hide a release — anything new fails loud in
_lower_flow first (IR4), so the walk can never be lied to.

Six regression tests pin the matrix from the TZ: conditional release
silent, both-branch consume kept, while-body release silent, early
return blocks the claim, release-then-return credited, and a wrapper
forwarding to a partial releaser degrading to plain transitively.
Full suite green (ownir 236/236); no existing behaviour depended on the
flattened consume.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
…ly (TZ D5)

The bridge wraps the interprocedural summary solve in a blanket
'except Exception: mos = {}': the right call (never crash the checker),
but it put the ENTIRE interprocedural layer out silently — every
cross-method consume/borrow/fresh contract skipped with no trace, against
the honest-skip culture every other coverage gap follows (OWN050).

to_module now takes an optional notes out-list (the loc-counter house
pattern) and records the degrade reason; check_facts surfaces each as an
advisory OWN052 finding — file-level (no single C# site failed),
module-scoped, advisory like OWN050: rendered as a warning, level:note
in SARIF (region omitted for line 0), never the exit code. The
__main__ summary names the advisory codes actually present instead of
the hard-coded '(OWN050)' label — byte-identical output when only
OWN050 notes exist. 'explain OWN052' answers with the long form.

Tests: a patched-to-raise solver yields exactly one OWN052 and zero
fabricated verdicts (forwards degrade to plain, the caller stays
silent); the same facts with a healthy solver carry no OWN052 and keep
their true OWN002. No real facts input can make solve() throw today —
which is exactly why the silent except went unnoticed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
…t (TZ D2)

The two-axis d5 model reserves an escape axis orthogonal to transfer,
but no producer sets it: skeleton derivation always leaves False, so
ParamSummary.escapes is constant False in every real run. to_dict still
emitted it — and the summary dump is about to become the stage-1
Python<->Rust parity artifact, which would freeze an always-False lie
into the frozen surface (and force the Rust port to reproduce a field
that carries no information).

to_dict now omits the field until a producer lands; the model keeps the
axis (fields stay, documented RESERVED in ownership.py), and a test pins
the omission so re-adding it is a deliberate act paired with the
producer, not a drive-by.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
… D3)

The solver's contrib() understands adopt and return as must-transfer,
but skeleton derivation never emits them (adopt awaits interprocedural
T4b ctor-adopt summaries; a returned param is deliberately not a consume
signal until owned return values are modelled). Without the marker a
port would carry dead semantics as live and could expect them from real
facts. The PathAction docstring now names both as RESERVED with each
kind's activation condition.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
… parity surface)

'python -m ownlang summaries facts.json' prints the solved Method
Ownership Summaries plus the extern-boundary log as one deterministic
JSON document: summaries sorted by method key, the unresolved log
sorted by solve_with_log, fixed field order — byte-identical output
under any functions[] input permutation (pinned by test). Two consumers,
one contract: the debugging answer to 'why did this call stay
plain/consume/fresh' without reading bridge code, and the frozen surface
the Rust port of the inference layer will be diffed against — summary-
level parity is finer than diffing final diagnostics, where an inference
bug can hide behind an unrelated silence.

Skeletons now carry the declaring file from the facts (merged overloads
take the smallest (file, line) pair so the dump stays input-order-
independent); a failed solve degrades exactly like the checking path —
empty summaries with the reason in 'degraded', rc stays 0 (the surface
reports state, it does not judge), rc 2 only for unreadable facts.
Wiring into oracle_exact SURFACES rides with the Rust own-bridge
consumer, which does not exist yet.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
…/unknown call (TZ D6)

d5 §5's optimistic default says an unverified-ownership call site resolves
to 'the caller no longer owns' -> silence. The implementation lowered
may/unknown contracts to a PLAIN arg instead — which is not silence: plain
leaves the obligation with the caller, so DROPPING a local after a
may-call fabricated OWN001. The channel predates D1 (conditional
forwards), but the D1 fix widened it to every null-guard dispose helper:
'var r = new X(); Cleanup(r);' — the normal usage, Cleanup IS the
disposer — read as a leak.

Untrack makes the default real: _unverified_transfer_calls collects every
arg at a may/unknown position of a summarized callee; such locals are not
minted at all (acquire / fresh result / alias target skipped, branch-hoist
excluded), so neither a missing nor a defensive dispose after the call can
be charged. A call with may/unknown positions routes through the existing
per-argument $consume/$borrow channel (the overload path, now shared) so
an untracked name is never referenced in emitted code — the first run's
loud OWN030 (map-or-raise doing its job) proved why. Verified contracts
are untouched: borrow-drop keeps its true OWN001 (T3) and consume-reuse
its OWN002 (T2), both pinned.

The gap is now honest instead of silent-or-wrong: advisory OWN051
('ownership transfer unverified — local not checked past this call') at
the call site, gated on args that actually carry an obligation (acquired
or fresh-factory locals — no noise for plain values), advisory like
OWN050/052: warning, SARIF note, never the exit code, hidden at
--verbosity quiet. explain OWN051 answers. Seven existing may-shape tests
updated from 'empty' to 'exactly one OWN051 advisory'; three new tests pin
the closed FP, the preserved T3 recall, and the owned-local gate.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review and propose changes

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9e4f04df2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ownlang/ownir.py
…layer (roadmap stage 0)

Lifts the verdict-determining inference layer from an undocumented
bridge concern (architecture-review R2, tech-debt-register §3's hard
port prerequisite) into a normative, descriptive spec pinned by tests —
the same posture as OwnIR.md.

Covers: the MOS artifact and its two axes; the transfer lattice and join
(INF-L); parameter skeleton derivation (INF-S, incl. the definite-release
rule from D1 and the reserved adopt/return kinds from D3); return
inference (INF-R); overload merge and the arity residual (INF-M); the
SCC-condensation fixpoint (INF-F, incl. the observable-degradation rule
from D5); call-site application and the tier ladder (INF-A, incl. the
optimistic untrack from D6); the reporting policy and advisories
OWN051/OWN052 (INF-P); the deterministic summary-dump serialization
(incl. the omitted escapes axis from D2). Honestly marks every reserved
element (escapes, adopt/return, strict mode, Tier C, source tiers beyond
'inferred') as 'in the model, no producer'.

§11 maps every INF-* rule to its pinning test in test_ownership.py /
test_ownir.py; registered in spec/README.md; OWN051/052 documented in
spec/Diagnostics.md with a new interprocedural-advisory section. A
conformance check pins the codes the spec names to the catalogue. Full
suite green (ownir 245/245).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBmDyLWHqoQUGqiydSxroi
@PhysShell

Copy link
Copy Markdown
Owner Author

@codex is all good

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits. You can see your limits in the Codex usage dashboard.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex is all good

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Follow-up check found the PR was functionally passing, but the lint gate was not clean due to Ruff import/type-annotation findings.
  • Fixed the summaries CLI lint issue by moving json and OwnIR imports into the module import block and leaving cmd_summaries to call the already-imported helpers. ownlang/main.pyL41-L58 ownlang/main.pyL318-L322
  • Fixed the OwnIR type-annotation lint issue by using the postponed-annotation form directly for Finding in to_module. ownlang/ownir.pyL784-L787
  • Committed the changes on the current branch: 3d21d4b fix: satisfy lint for summaries bridge.
  • Created the required follow-up PR via make_pr.

Testing

  • python tests/test_ownir.py
  • python tests/run_tests.py
  • ruff check . && mypy — initially failed on pre-existing lint findings in ownlang/__main__.py and ownlang/ownir.py; fixed in this follow-up.
  • ruff check . --fix && mypy && python tests/test_ownir.py && python tests/run_tests.py

Note: the working tree still has an unrelated untracked file, sandboy/Cargo.lock, which I did not modify or include in the commit.

View task →

claude added 3 commits July 8, 2026 05:57
Codex's follow-up run flagged two Ruff findings its sandbox commit never
reached this branch with: the un-formatted import block in cmd_summaries
(I001) and the quoted Finding annotation in to_module (UP037). Applied
the same fixes here.
…rdicts (Codex P1+P2)

Two review findings on the interprocedural layer, both reproduced before
fixing:

P2 (S2 / INF-S3): a body like 'if (c) return; Sink(x)' kept exactly one
top-level forward, so the shape counted as an unconditional handoff and
solved to must — leaking the callee's own parameter on the guard path
and charging the caller's defensive Dispose a false OWN002. The
unconditional test now also requires no return reachable before the
forward (_early_return_before_forward), the forward twin of D1's
partial-release rule. Recorded as TZ D7.

P1 (INF-A5): the optimistic untrack applied to the whole body, so a
local passed to a may/unknown position anywhere lost its PRE-call
verdicts too (acquire s; release s; use s; maybe(s) reported nothing
but OWN051). Split into two mechanisms (_kill_sites_for_unverified):
a top-level unverified call keeps the local fully tracked up to the
call, discharges it there with a $consume on the handle (sound: a
top-level call lies on every path to the exit), and unmaps it after —
so the checked region now matches OWN051's 'not checked past this
call'. In-branch calls keep the conservative whole-body untrack (a
branch-local discharge would fabricate OWN001/OWN009 on the other
path); the residual is recorded in TZ D6 and INF-A5b.

Spec: INF-S3 + INF-A5a/A5b updated, conformance list extended.
Tests: 3 new pins (pre-call OWN002 beside OWN051, post-call silence,
early-return forward), 248/248 bridge checks green.
@PhysShell
PhysShell merged commit 4d48aeb into main Jul 8, 2026
33 checks passed
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