Skip to content

feat(ownir): OwnIR contract hardening — spec, fail-loud guards, version single-sourcing#172

Merged
PhysShell merged 6 commits into
mainfrom
claude/ownir-contract-hardening
Jul 4, 2026
Merged

feat(ownir): OwnIR contract hardening — spec, fail-loud guards, version single-sourcing#172
PhysShell merged 6 commits into
mainfrom
claude/ownir-contract-hardening

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Закаляет контракт OwnIR — несущий шов «фронтенды продуцируют/потребляют факты, ядро выносит вердикт» — по «Now»-корзине tech-debt-реестра. Даёт формальную грамматику (spec/OwnIR.md), закрывает молчаливые дыры (проглатывание неизвестных фактов, тихий "" на опечатке кода, assert, срезаемый -O) и делает гейтинг тестов и версию продюсеров не-забываемыми. Пара к P-022 #170: spec/OwnIR.md — проза-грамматика контракта, который реализует крейт own-ir.

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

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

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

  • python tests/run_tests.py — зелёный (авто-дискавери модулей; test_ownir 211/211; test_diagnostics +code-validation)
  • ruff check . и mypy — чисто (18 файлов)
  • селфтесты затронутых скриптов (python scripts/<...>.py --selftest)

Что внутри (по тикетам реестра):

  • N1_lower_flow больше не имеет if/elif без else: неизвестный flow-op → OwnIRError, а не молчаливое проглатывание вложенных acquire/release. Пинается тестом.
  • N4 — новая нормативная spec/OwnIR.md (envelope, словари resource-kind + flow-op, DI-граф, политика эволюции версии IR1–IR6), зарегистрирована в spec/README.md; плюс version single-sourcing — тест грепит Program.cs/ownts.py и требует равенства ownir_version ядру. Schema (spec/ownir.schema.json) намеренно отложена: её триггер сработал в feat: P-022 first deliverable — CFG JSON seam, Rust workspace + own-ir, exact oracle #170 (крейт own-ir = второй языко-нативный набор типов), и правильно со-проектировать её с этим крейтом и генерить Rust-типы из неё, а не вешать jsonschema на zero-dep ядро — обоснование в реестре.
  • N6Diagnostic отвергает код без записи в TITLES при конструировании (весь сьют подтвердил, что все эмитируемые коды зарегистрированы); title индексирует вместо .get(...,"").
  • N9 — голый assert инварианта займов в join() → явный raise (не срезается python -O), по образцу _join_handle_rid.
  • N2 — раннер авто-дискаверит test_*.py и гейтит на any(rc): новый тест-файл включается сам, модуль без run() валит сьют — убит хрупкий 25-членный or.
  • N10 — лживые «kept in lockstep» комментарии на BCL-таблицах (IsOwningFactory знает ADO.NET/network, _BCL_FRESH_BY_NS — нет) заменены на точное описание ролей (эмиссия-авторитет-суперсет vs call-op-фолбэк-субсет) + инвариант безопасности; поведение не менялось.

Связанные issue

Нет issue. Refs P-022 (#166, #168, #170). Реализует «Now»-корзину docs/notes/tech-debt-register.md.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет) — test_ownir (unknown-op, version-consistency), test_diagnostics (code-validation), авто-дискавери прогоняет весь сьют
  • README/docs обновлены при необходимости — spec/OwnIR.md, spec/README.md, реестр обновлены
  • коммиты в conventional-commit стиле (feat:, fix:, test:, docs:)

🤖 Generated with Claude Code

https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a formal OwnIR specification and documentation for the frontend-to-core contract, including versioning rules and supported IR vocabulary.
    • Automated test discovery now runs all matching test modules without manual registration.
  • Bug Fixes

    • Unknown diagnostics and unsupported IR operations now fail immediately instead of being silently accepted.
    • Ownership analysis now enforces state consistency even in optimized runtime modes.
  • Tests

    • Expanded coverage for diagnostic code validation, unknown IR operation rejection, and version consistency checks.
  • Documentation

    • Updated technical debt notes and spec documentation to reflect the new contract behavior.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d5d50012-1967-4ec6-8124-0a26305aeee5

📥 Commits

Reviewing files that changed from the base of the PR and between 74bf790 and fc9b7a2.

📒 Files selected for processing (10)
  • docs/notes/tech-debt-register.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • ownlang/analysis.py
  • ownlang/diagnostics.py
  • ownlang/ownir.py
  • spec/OwnIR.md
  • spec/README.md
  • tests/run_tests.py
  • tests/test_diagnostics.py
  • tests/test_ownir.py
📝 Walkthrough

Walkthrough

This PR adds a formal OwnIR specification document, enforces fail-loud behavior for unknown flow ops and unregistered diagnostic codes, replaces an assert with an explicit raise in a flow-join invariant, refactors the test runner to auto-discover tests, and updates related documentation and comments.

Changes

OwnIR contract formalization and fail-loud enforcement

Layer / File(s) Summary
OwnIR specification document
spec/OwnIR.md, spec/README.md
Adds a full spec defining the JSON envelope schema, versioning/evolution policy, subscription/resource semantics, flow-op vocabulary, DI service graph semantics, and rules IR1–IR6; links it from the spec index.
Fail-loud enforcement in flow lowering and diagnostics
ownlang/ownir.py, ownlang/diagnostics.py, ownlang/analysis.py
Adds a catch-all raising OwnIRError for unknown flow ops, validates diagnostic codes at construction via __post_init__, switches title to direct dict indexing, and replaces an assert with an explicit if/raise for the loan-set invariant in join.
Bridge/extractor factory-authority comments
ownlang/ownir.py, frontend/roslyn/OwnSharp.Extractor/Program.cs
Reworded comments clarifying IsOwningFactory as the authority for acquire facts and scoping when the BCL fresh-factory table is consulted.
Test runner auto-discovery and new contract tests
tests/run_tests.py, tests/test_diagnostics.py, tests/test_ownir.py
Replaces hard-coded test imports with test_*.py auto-discovery and aggregated return codes; adds tests for unknown-op rejection, diagnostic code validation, and OWNIR_VERSION single-sourcing across producers.
Tech-debt register updates
docs/notes/tech-debt-register.md
Updates the OwnIR protection-stack status, evolution policy, schema rationale, and the "Now" checklist entries.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Frontend as Frontend Producer
  participant OwnIR as ownlang/ownir.py
  participant Diagnostics as ownlang/diagnostics.py
  participant Tests as tests/test_ownir.py

  Frontend->>OwnIR: submit facts payload (with flow op)
  alt op is unknown
    OwnIR-->>Frontend: raise OwnIRError (version skew)
  else op is known
    OwnIR->>OwnIR: lower flow op
  end
  OwnIR->>Diagnostics: construct Diagnostic(code)
  alt code not in TITLES
    Diagnostics-->>OwnIR: raise ValueError
  else code registered
    Diagnostics-->>OwnIR: title resolved
  end
  Tests->>OwnIR: check_facts(unknown op payload)
  OwnIR-->>Tests: OwnIRError raised
  Tests->>Frontend: regex-extract ownir_version
  Tests->>OwnIR: compare to OWNIR_VERSION
Loading

Possibly related PRs

  • PhysShell/Own.NET#16: Both PRs touch OwnIR flow-op lowering in ownlang/ownir.py and related contract tests, one adding op support and this one enforcing fail-loud rejection of unknown ops.
  • PhysShell/Own.NET#36: Both modify _lower_flow's handling of ops, one extending supported ops and this one adding a hard-fail path for unrecognized ones.
  • PhysShell/Own.NET#164: Both update tests/run_tests.py's aggregation/discovery logic for test modules included in the overall exit status.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main OwnIR hardening changes, including the spec, fail-loud guards, and version consistency checks.
Description check ✅ Passed The description covers the required sections, explains the change, lists verification, related issues, and includes the checklist.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/ownir-contract-hardening

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.

claude added 5 commits July 4, 2026 08:12
Track A (contract hardening), first tickets from the tech-debt register.

- _lower_flow no longer has an else-less if/elif chain: an unknown flow op
  now raises OwnIRError instead of being silently dropped. Silently skipping a
  new compound op would drop the acquire/release facts nested inside it —
  fabricating a leak (a lost release) or hiding one (a lost acquire) while every
  existing fixture still passes. A new op is a vocabulary change and must bump
  OWNIR_VERSION. Pinned by a new test_ownir check (209/209).
- spec/OwnIR.md: the OwnIR contract was only prose in a module docstring; it is
  now a normative spec — envelope, the versioning + evolution policy (what does
  and does not bump OWNIR_VERSION), the resource-kind and flow-op vocabularies,
  the DI graph, and rules IR1–IR6 with a conformance section tying each to
  tests/test_ownir.py. Registered in spec/README.md.

Refs docs/notes/tech-debt-register.md (N1, N4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
…c code

Track A (contract hardening), continued.

- analysis.join(): the block-scoped-loan invariant was a bare `assert`, stripped
  by `python -O` (a silently-wrong merge if block-scoping is ever relaxed). Now
  an explicit `if … raise AssertionError`, mirroring _join_handle_rid. (N9)
- Diagnostic now validates its code at construction: a code absent from TITLES
  raises ValueError instead of silently rendering an empty title. The full suite
  confirms every emitted code is registered; `title` indexes instead of
  `.get(..., "")`. Closes the one stringly-typed contract's silent-"" hole
  cheaply (a full StrEnum is the heavier follow-up). Pinned by test_diagnostics. (N6)
- Register: mark N1/N4/N6/N9 done; re-scope N8 — a dedicated syntax-error code is
  NOT surgical (OWN020 is the suite-wide parse/lex-rejection code, entangled with
  the lexer's rejected-keyword mechanism and ~6 test helpers).

Refs docs/notes/tech-debt-register.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
…t or-chain)

Track A (N2). The suite aggregator imported ~16 test modules by hand and gated
on a 25-term `or` of their return codes; adding a module meant remembering to
thread its rc through that chain, and a forgotten term silently dropped a whole
module from the gate.

The runner now globs `test_*.py`, calls each module's `run()`, and returns
`any(rc)` (plus the inline analysis/codegen flags). A new test file is picked up
with no edit here; a module missing `run()` fails the suite loudly; discovering
zero modules fails loud. `test_codegen_props` keeps its lighter in-suite
iteration count (3000) via a one-entry special-case map. Per-module output is
unchanged (now ordered by filename).

Refs docs/notes/tech-debt-register.md (N2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
… rationale

Track A (N4, no-dependency slice).

- test_ownir now asserts each frontend producer (Program.cs, ownts.py) stamps
  the same ownir_version as the core's OWNIR_VERSION — so 'bumped the core,
  forgot a frontend' (or the reverse) fails loudly instead of silently
  mis-reading facts. This is the no-dep half of version single-sourcing; the
  P-022 own-ir crate will be a fourth producer to fold in (or generate from the
  schema). (211/211 bridge checks.)

- Register: recover and record the strong pro-schema argument now that its
  trigger has fired. OwnIR is THE language every frontend + the core speak; a
  language without a formal grammar lies about itself. The schema looked
  redundant only while load() was the sole validator (zero-dep, no second type
  set). #170's crates/own-ir hand-writes Rust serde structs that must match
  load() — two hand-maintained validators in two languages, the exact drift
  formalization exists to kill. So the schema flips from a shadow of load() to
  the source both are generated from (serde via typify; C#/TS source-gen),
  collapsing the hand-synced shapes AND the four ownir_version literals into one
  authority. Recommendation: co-design spec/ownir.schema.json WITH the own-ir
  crate, not a jsonschema validator bolted onto the zero-dep core.

Refs docs/notes/tech-debt-register.md (N4), P-022 #170.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
…lationship

Track A (N10). The extractor's IsOwningFactory and the bridge's _BCL_FRESH_BY_NS
both carried 'kept in lockstep' comments, but the lists had already diverged
(the extractor knows ADO.NET ExecuteReader/CreateCommand/BeginTransaction and
network Accept*; the bridge table lists neither) — the comments lied.

They are not a mirror and should not be: IsOwningFactory is the emission
authority (a match emits an 'acquire' fact), so it is the superset; the bridge
table is consulted only for 'call'-op results (_callee_returns_fresh), and since
owning factories are emitted as 'acquire' not 'call', it is a deliberate subset
— the C#-only entries never arrive as calls, so adding them would be dead weight.
Both comments now state the real roles, the safety invariant (owning factory ⇒
acquire), and the generate-both-from-schema exit. No behaviour change.

Refs docs/notes/tech-debt-register.md (N10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
@PhysShell PhysShell force-pushed the claude/ownir-contract-hardening branch from 74bf790 to 6aad152 Compare July 4, 2026 08:14

Copy link
Copy Markdown
Owner Author

Перебазировал на свежий main (конфликт был в tests/run_tests.py: main добавил test_cfg_json в ручную or-цепочку, а этот PR как раз её сносит — авто-дискавери подхватывает новый модуль сам, 20/20 проходят). Теперь чисто: run_tests.py exit 0, ruff + mypy (19 файлов) зелёные.

Связка с P-022 #170 (для ревьюеров): этот PR — контрактная пара к Rust-фундаменту.

  • spec/OwnIR.md здесь — проза-грамматика ровно того контракта, который реализует crates/own-ir в feat: P-022 first deliverable — CFG JSON seam, Rust workspace + own-ir, exact oracle #170. IsOwningFactory/_BCL_FRESH_BY_NS, flow-op словарь, resource-kinds, DI-граф — всё описано нормативно (IR1–IR6).
  • JSON Schema намеренно НЕ в этом PR. Её триггер сработал именно в feat: P-022 first deliverable — CFG JSON seam, Rust workspace + own-ir, exact oracle #170: own-ir вручную пишет serde-структуры под правилом «принимать ровно то, что принимает Python load()», синхронность держится на roundtrip фикстур. Это второй языко-нативный набор типов → схема перестаёт быть тенью load() и становится источником, из которого генерятся оба (Rust через typify/schemars). Поэтому правильно со-проектировать spec/ownir.schema.json с крейтом own-ir, а не вешать jsonschema-валидатор на zero-dep ядро здесь. Обоснование целиком — в docs/notes/tech-debt-register.md §2.
  • Version single-sourcing здесь (тест равенства ownir_version в Program.cs/ownts.py ядру) — тоже no-dep предтеча схемы: когда own-ir станет четвёртым продюсером, его литерал добавляется в ту же проверку (или всё генерится из схемы).

Merge-порядок с #170 не важен — файлы не пересекаются (кроме run_tests.py, уже согласованного через rebase).


Generated by Claude Code

@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: 1

🤖 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 `@spec/OwnIR.md`:
- Around line 49-54: The OwnIR versioning guidance for resource kinds is too
permissive: new discriminator values in the resource routing logic can change
analysis behavior and should not be treated as harmless additive metadata.
Update the policy in spec/OwnIR.md around the resource-kind rules and §4 routing
so that changes to resource-kind values either require bumping OWNIR_VERSION or
explicitly fail on unknown values instead of silently defaulting to
subscription; reference the resource-kind discriminator and OWNIR_VERSION
guidance when revising the table and surrounding text.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 81a426f6-cc32-456b-822e-24168f6ef64d

📥 Commits

Reviewing files that changed from the base of the PR and between 2ecab43 and 74bf790.

📒 Files selected for processing (10)
  • docs/notes/tech-debt-register.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • ownlang/analysis.py
  • ownlang/diagnostics.py
  • ownlang/ownir.py
  • spec/OwnIR.md
  • spec/README.md
  • tests/run_tests.py
  • tests/test_diagnostics.py
  • tests/test_ownir.py

Comment thread spec/OwnIR.md

@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: 74bf79068d

ℹ️ 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 spec/OwnIR.md Outdated
Comment on lines +24 to +26
"components": [ /* §4 owned-resource records, grouped by type */ ],
"functions": [ /* §5 flow bodies (intra-procedural CFG facts) */ ],
"services": [ /* §6 DI registration graph */ ]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include effects in the OwnIR contract

When the OwnTS frontend emits effects (frontend/ownts/ownts.py:598-602) and the core validates/consumes it (ownlang/ownir.py:548-577), this new normative envelope leaves that top-level block out. If the promised schema/types are generated from this spec, valid EFF001 fact sets would either lose the effect graph or be rejected, disabling effect-storm diagnostics. Please document effects as an optional top-level block and specify its shape alongside the other consumed OwnIR sections.

Useful? React with 👍 / 👎.

PhysShell pushed a commit that referenced this pull request Jul 4, 2026
Review follow-ups on PR #172.

- CodeRabbit (Major): a present-but-unknown resource kind fell through §4 routing
  to the owned/subscription path and mis-classified — the same silent-misroute
  N1 fixes for flow ops. load() now rejects a present-but-unknown
  against the known-kinds set (_KNOWN_RESOURCE_KINDS); an ABSENT field still
  defaults to  (additive). So a new kind is a vocabulary change
  that must bump OWNIR_VERSION — spec/OwnIR.md §2 table + IR3/IR4 updated
  accordingly, docstring corrected (field-vs-value). Pinned by two test_ownir
  checks (unknown kind raises; absent defaults). 213/213.
- Codex (P2): the top-level  block (OwnTS emits it, effects.py consumes
  it → EFF001) was missing from the spec envelope — generating types from the
  spec would drop/reject effect facts. Added to §1 and a new §7 documenting its
  shape (io/line/deps/bindings); §8/§9 renumbered; conformance updated.
- Register §2: correct the now-inaccurate 'unknown kinds coerce to subscription'
  note to the closed-enum-for-present-values reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
Review follow-ups on PR #172.

- CodeRabbit (Major): a present-but-unknown resource kind fell through the §4
  routing to the owned/subscription path and was mis-classified -- the same
  silent-misroute that N1 fixes for flow ops. load() now rejects a
  present-but-unknown "resource" against the known-kinds set
  (_KNOWN_RESOURCE_KINDS); an ABSENT field still defaults to "subscription"
  (additive). So a new kind is a vocabulary change that must bump OWNIR_VERSION
  -- spec/OwnIR.md section 2 table + IR3/IR4 updated, docstring corrected
  (field-vs-value). Pinned by two test_ownir checks (unknown kind raises; absent
  defaults). 213/213.
- Codex (P2): the top-level "effects" block (OwnTS emits it, effects.py consumes
  it -> EFF001) was missing from the spec envelope -- generating types from the
  spec would drop/reject effect facts. Added to section 1 and a new section 7
  documenting its shape (io/line/deps/bindings); Rules/Conformance renumbered;
  conformance updated.
- Register section 2: correct the now-inaccurate "unknown kinds coerce to
  subscription" note to the closed-enum-for-present-values reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJSfUpRbRXegrwJNV1rx1X
@PhysShell PhysShell force-pushed the claude/ownir-contract-hardening branch from 723e9c7 to fc9b7a2 Compare July 4, 2026 08:25
@PhysShell PhysShell merged commit 7bae944 into main Jul 4, 2026
29 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