Skip to content

feat(rust): #259 slice 3 — own-bridge, OwnIR facts → Layer 2 lowering - #301

Merged
PhysShell merged 4 commits into
mainfrom
claude/own259-bridge-facts-lowering
Jul 19, 2026
Merged

feat(rust): #259 slice 3 — own-bridge, OwnIR facts → Layer 2 lowering#301
PhysShell merged 4 commits into
mainfrom
claude/own259-bridge-facts-lowering

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Третий slice #259 в авторизованной границе: Rust lowering facts → Layer 2 — новый pure transformation crate own-bridge (OwnIr -> Result<LoweredDocument, BridgeError>; без ФС, CLI, диагностики и analysis side effects — fixture I/O живёт только в integration tests). Порт ownlang/ownir.py::to_module, ограниченный семантикой, которую тренируют 26 rust_replay: true fixtures:

  • routing R1–R6: unresolved-skip, self-subscribe-skip, static capture (+ released-митигирование), returned_fresh provenance, DI-типизированный capture с регионами из services, token-путь всех kind'ов c releasedRelease;
  • глобальный minting sub_/cap_ и общий счётчик parg_/loc_; capture-параметры с line 0 под регионом источника; lifetimes ровно при any-capture;
  • MOS как замороженная часть lowering-семантики: скелеты (explicit effects, definite vs partial release, правило единственного безусловного forward, early-return guard, fresh/forward return-инференция с границей Tier A/Tier B и BCL-таблицей), merge overload'ов на гранулярности (key, index) + per-sig ключи, SCC-fixpoint solve, cycle-safe return chase — без расширения inference и без изменения контракта;
  • flow lowering: localmap, kill-on-rebind, alias_join (старый binding умирает первым), тишина unmapped-ссылок, hoisting со всеми четырьмя negative gates, канал $consume/$borrow (overload'ы и may/unknown-контракты), top-level kill-site discharge+unmap против in-branch whole-body untrack, fresh-result minting, fail-loud vocabulary с точным текстом Python-ошибки (kill-sites по identity узла в value-дереве — аналог id(n)).

Вход — типизированный own_ir::OwnIr (acceptance-цепочка facts → own-ir parse → own-bridge → own-lowered emitter); внутри walk словарный, поверх однократной re-сериализации (round-trip — закреплённое свойство own-ir), чтобы каждое membership/default/truthiness-решение читалось против референса строка-в-строку.

Одна громкая сознательная дивергенция: present-but-unknown resource kind — BridgeError, а не тихий Python-fallback в Subscription: tolerant door — открытое решение #294, и crate отказывается его предвосхищать в любую сторону (tolerant_unknown_kind остаётся единственным rust_replay: false, что утверждается тестом).

DAG расширен сознательно: own-bridge → {own-ir, own-lowered} — обе стрелки в data-leaves, обратные запрещены; own-lowered остаётся leaf.

Отдельным маленьким коммитом — два ledger follow-up с review #300: floor >= 26 и валидация rules как непустого массива непустых строк (Python harness, проверено красным + Rust replay). BR-L7 wording nit не взят — spec/Bridge.md в этом slice не трогается.

История: red ff58433 (skeleton + падающие replay/mechanism тесты) → green 6531d70 (порт до 26/26) → chore b6465d0 (ledger).

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

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

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

  • own-bridge/tests/replay.rs — acceptance contract: для каждого manifest-кейса rust_replay: true цепочка facts.json → OwnIr::from_json → lower → to_canonical_json == golden byte-for-byte, 26/26, дважды (детерминизм); golden — только expected output, никогда не вход; LOWERED_VERSION == 1, exact ledger/tree equality, skip-set == ровно {tolerant_unknown_kind} — всё утверждается самим Rust-тестом
  • own-bridge/tests/mechanisms.rs — метаморфические/негативные пины: порядок records предсказуемо меняет глобальный mint; kill-on-rebind убирает старый mapping; precise sig-канал $consume НЕ unmap'ит (use после канала жив), merged-may — unmap'ит (release молчит); каждый hoisting negative gate блокирует hoist при зелёном positive control; unknown flow op — fail-loud с точным Python-текстом
  • red-состояние зафиксировано: все 10 тестов падали на скелете (ff58433)
  • cargo fmt --check, cargo clippy --workspace --all-targets — 0 warnings; полный workspace — 115 passed / 0 failed
  • python tests/run_tests.py, ruff — зелёные; ownir.py/to_module не изменялись; fixtures/goldens не изменялись (кроме нуля — ledger diff только в harness-валидации)
  • rules-валидация проверена красным (пустой rules → FAIL harness)

Связанные issue

Refs #259 (slice 3 — НЕ закрывает issue), #258, #294 (не решается — unknown-kind теперь громкий отказ в Rust, поведение не зафиксировано), #296 (rules-валидация закрыта здесь как ledger follow-up), #250.

Чеклист

  • изменение покрыто тестом/селфтестом
  • README/docs обновлены при необходимости (crate-доки + DAG rationale)
  • коммиты в conventional-commit стиле

Draft — не мержить, не self-accept. Гейт: независимый review — подтверждение, что (1) порт семантически точен против to_module (routing/minting/MOS/flow), (2) golden нигде не используется как вход построения, (3) MOS не расширен и контракт не изменён, (4) дивергенция по unknown-kind не решает #294, (5) DAG-запись обоснована и own-lowered остался leaf, (6) ledger-коммит не маскирует lowering diff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MdYFKUaygHz1T9H1qJ7BqK


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added Rust-based lowering from OwnIR facts to normalized Layer 2 output.
    • Added ownership-transfer analysis, control-flow handling, overload resolution, and deterministic output generation.
    • Unknown resource types and flow operations now produce clear errors.
  • Tests

    • Added mechanism and byte-exact replay coverage.
    • Expanded fixture validation and replay requirements.
  • Documentation

    • Updated bridge behavior and parity requirements for Layer 2 lowering.

claude added 3 commits July 19, 2026 01:15
…lay, mechanism pins

New crate own-bridge (deps: own-ir, own-lowered — both data leaves; the DAG
map widened deliberately). Production API is a pure transformation:
OwnIr -> Result<LoweredDocument, BridgeError>; no filesystem/CLI/analysis.

Red now, green next:
* tests/replay.rs — the acceptance contract: every rust_replay manifest
  case lowered FROM FACTS (golden is expected output only, never an input)
  must be byte-identical to the Python golden, twice (determinism); plus
  independent LOWERED_VERSION, ledger/tree equality, and the exact
  {tolerant_unknown_kind} Python-only set.
* tests/mechanisms.rs — metamorphic/negative pins the plain replay cannot
  prove: record order drives the global mint counters; kill-on-rebind
  removes the old mapping; a sig-carrying overload call channels $consume
  WITHOUT unmapping; a sig-less merged-may call discharges at the kill
  site and unmaps; each hoisting negative gate (nested depth, while body,
  early return) blocks the hoist while the positive control hoists; an
  unknown flow op fails loud with Python's exact rejection text.

The skeleton lowers everything to an empty document, so all 10 tests fail.

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

The lowering port, restricted to the frozen semantics the shared fixtures
exercise. own_bridge::lower(&OwnIr) re-serializes the typed document to a
JSON value once and walks it with the same key-by-key logic as Python's
to_module — every membership/default/truthiness decision stays textually
comparable to the reference:

* routing R1–R6 (unresolved skip, self-subscribe skip, static capture with
  released mitigation, returned_fresh provenance, DI-typed capture with
  region derivation, token kinds with released Release);
* global sub_/cap_ minting and the shared parg_/loc_ counter; capture
  params at line 0 under their source region; lifetimes emitted iff any
  capture; DI registrations via the services graph;
* MOS: skeleton building (explicit effects, definite/partial release,
  single-unconditional-forward rule, early-return guard, fresh/forward
  return inference with the Tier A/Tier B boundary and the BCL factory
  table), overload merge at (key, index) granularity + per-sig overload
  keys, SCC-condensation fixpoint solve, cycle-safe return chase;
* flow lowering: localmap, kill-on-rebind, alias_join (old binding dies
  first), unmapped-reference silence, hoisting with all four gates, the
  $consume/$borrow channel (overloads and may/unknown contracts), the
  top-level kill-site discharge + unmap vs the in-branch whole-body
  untrack, fresh-result minting, and the fail-loud vocabulary error with
  Python's exact message text (kill sites keyed by node identity in the
  value tree — the id(n) analog).

One loud deliberate divergence: a present-but-unknown resource kind is a
BridgeError, not Python's silent Subscription fallback — the tolerant-door
contract is #294 and this crate refuses to pre-empt it in either direction
(tolerant_unknown_kind stays Python-only, asserted in the replay).

All 26 rust_replay cases lower byte-identically from facts (golden used
only as expected output), twice (determinism); all 9 mechanism pins green;
clippy 0 warnings; full workspace + python suites green.

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

The two follow-ups recorded on PR #300's review threads (deferred to the
next ledger change, which this slice is):

* own-lowered's minimum-coverage floor rises 25 -> 26 to cover the
  handles_null_metadata fixture (exact ledger integrity stays with the
  manifest == facts == goldens equality; the floor is the shrink guard);
* manifest 'rules' must be a non-empty array of non-empty strings —
  enforced in the Python harness (verified red on violation) and in the
  own-lowered typed replay.

Kept separate from the lowering commits so the own-bridge diff stays
readable.

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

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds the Rust own-bridge crate, ports OwnIR-to-Layer 2 lowering with MOS analysis, exposes a public lowering API, and adds mechanism, replay, manifest, dependency-graph, and specification coverage for byte-exact Rust parity.

Changes

OwnIR bridge and Layer 2 parity

Layer / File(s) Summary
Workspace and bridge API
rust/Cargo.toml, rust/crates/own-bridge/Cargo.toml, rust/crates/own-bridge/src/lib.rs
Registers the crate and exposes BridgeError plus lower(&OwnIr) -> Result<LoweredDocument, BridgeError>.
Lowering analysis and MOS solving
rust/crates/own-bridge/src/lower.rs, rust/crates/own-bridge/src/mos.rs
Ports JSON coercion, skeleton inference, overload merging, ownership-transfer analysis, branch hoisting, SCC-based fixpoint solving, and return resolution.
Typed document and flow lowering
rust/crates/own-bridge/src/lower.rs
Validates handles and regions, lowers parameters and flow operations, applies MOS contracts, emits typed documents, and rejects unknown resource kinds or flow operations.
Replay and fixture contracts
rust/crates/own-bridge/tests/*, rust/crates/own-diagnostics/tests/dag.rs, rust/crates/own-lowered/tests/replay.rs, spec/*, tests/test_lowered_fixtures.py
Adds mechanism and byte-exact replay tests, strengthens manifest validation, updates dependency allowances, and documents the Rust replay contract.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OwnIR
  participant own_bridge
  participant MOS
  participant own_lowered
  participant GoldenFixtures
  OwnIR->>own_bridge: parse and lower facts
  own_bridge->>MOS: solve ownership summaries
  MOS-->>own_bridge: transfer and return contracts
  own_bridge->>own_lowered: emit LoweredDocument
  own_lowered->>GoldenFixtures: canonicalize output
  GoldenFixtures-->>own_lowered: compare byte-exact golden
Loading

Possibly related issues

Possibly related PRs

  • PhysShell/Own.NET#300 — Directly connects through the typed own-lowered surface and replay manifest validation.
  • PhysShell/Own.NET#112 — Covers the interprocedural ownership-transfer model implemented by the Rust MOS solver.
  • PhysShell/Own.NET#152 — Relates to overload skeleton merging and merged consume/borrow contracts.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: the new Rust own-bridge lowering from OwnIR facts to Layer 2.
Description check ✅ Passed The description follows the required template and fills all major sections with relevant implementation, testing, issue, and checklist details.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/own259-bridge-facts-lowering

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.

The normative documents described the pre-slice-3 world. Recorded now:

* spec/Bridge.md §6 Layer 2: #299 (Python emitter + fixture foundation),
  #300 (typed own-lowered surface/emitter, presence-aware metadata,
  per-document version gate), #301 (own-bridge constructs Layer 2 from
  facts and reproduces all 26 shared goldens byte-exact; golden is
  expected output only); tolerant_unknown_kind stays Python-only under
  #294 (the Rust bridge fails loud on an unknown kind instead of adopting
  the tolerant fallback); Layer 1, Layer 3, analysis wiring and #259 as a
  whole remain open.
* spec/BridgeBehaviorMatrix.md: the Layer 2 summary paragraph updated to
  the same completed state.
* tests/test_lowered_fixtures.py docstring: future tense ("will replay",
  "until the Rust emitter exists") moved to the present.
* BR-L7 wording nit folded in (recorded on #297): condition (4) guards the
  post-merge reference/discharge, not only a release — the use-only hoist
  fixture is in the gate too.

Docs-only; no Rust code, facts, manifest or golden changes.

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

Copy link
Copy Markdown
Owner Author

Ревизия по review (REQUEST CHANGES, docs-only) — применена, head 878cfdf (docs-only коммит поверх b6465d0; Rust-код, facts, manifest и goldens не тронуты — diff строго spec/Bridge.md, spec/BridgeBehaviorMatrix.md, docstring tests/test_lowered_fixtures.py).

  1. Bridge.md §6, Layer 2 — статус переписан в настоящее время: feat(parity): #259 foundation — Layer 2 normalized lowered representation #299 (Python emitter + fixture foundation), feat(rust): #259 slice 2 — own-lowered, typed Layer 2 replay/emitter #300 (typed own-lowered surface/emitter: presence-aware missing/null/value, per-document version gate), feat(rust): #259 slice 3 — own-bridge, OwnIR facts → Layer 2 lowering #301 (own-bridge строит Layer 2 из facts, 26/26 shared goldens byte-exact, golden — только expected output); tolerant_unknown_kind остаётся Python-only под Bridge contract OD-1/2/3: pin the tolerant door — direct check_facts() diverges from load() (unknown kind fallback, line coercion) #294 (Rust — громкий отказ на unknown kind, без tolerant-fallback); Layer 1, Layer 3, analysis wiring и P-022 step 6b: implement Rust own-bridge with layered OwnIR parity #259 целиком — открыты.
  2. BridgeBehaviorMatrix.md — итоговый Layer 2 абзац приведён к тому же состоянию (built and Rust-implemented, feat(parity): #259 foundation — Layer 2 normalized lowered representation #299/feat(rust): #259 slice 2 — own-lowered, typed Layer 2 replay/emitter #300/feat(rust): #259 slice 3 — own-bridge, OwnIR facts → Layer 2 lowering #301, sole Python-only case, L1/L3 open).
  3. Docstring test_lowered_fixtures.py — «will replay» / «until the Rust emitter exists» переведены в настоящее: Rust-половина контракта существует и верифицирует goldens без Python; Python авторитетен на генерации (--write).
  4. BR-L7 wording nit (записан с spec(bridge): #258 — executable own-bridge contract (Bridge.md + behavior matrix) #297) — сложен сюда же: условие (4) сторожит post-merge reference/discharge, не только release.

Локально: python suite + ruff зелёные, полный Rust workspace 115/0. Жду зелёный CI на 878cfdf и повторный review. PR остаётся draft — не мержить, не self-accept.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Независимый review — финальный verdict на head 878cfdf0ea84c632729a3d383f771fb0ce52f6d4:

APPROVED, no blocking findings.
Verified on exact head 878cfdf: pure own-bridge transformation boundary, facts-only construction, 26/26 byte-exact Layer 2 replay, deterministic output, frozen MOS semantics, explicit #294 non-decision, correct DAG, isolated ledger follow-up, reconciled normative docs, and fully green CI.

Merge авторизован reviewer'ом. Последовательность: ready → дождаться CodeRabbit post-draft review → перепроверка (exact head / mergeable clean / CI green / нет новых blocking threads) → merge commit без изменения head.


Generated by Claude Code

@PhysShell
PhysShell marked this pull request as ready for review July 19, 2026 02:15

@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.

🧹 Nitpick comments (1)
rust/crates/own-bridge/tests/replay.rs (1)

68-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing rules non-emptiness check (present in the sibling own-lowered validator).

own-lowered/tests/replay.rs and tests/test_lowered_fixtures.py both now assert each case's rules is a non-empty array of non-empty strings, but this file — which explicitly claims to independently enforce manifest-contract properties — doesn't. Vec<String> doesn't guarantee non-emptiness at the type level, so an empty/blank rules entry would silently pass here.

♻️ Proposed fix
     for case in &manifest.cases {
         assert!(
             listed.insert(case.name.clone()),
             "duplicate manifest case name: {}",
             case.name
         );
+        assert!(
+            !case.rules.is_empty() && case.rules.iter().all(|r| !r.is_empty()),
+            "{}: 'rules' must be a non-empty array of non-empty strings",
+            case.name
+        );
     }
🤖 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 `@rust/crates/own-bridge/tests/replay.rs` around lines 68 - 74, Extend the
manifest validation loop in the replay test to assert that every case’s rules
collection is non-empty and that each rule is a non-empty string after trimming.
Use the case’s existing rules field alongside the duplicate-name check,
preserving the validator’s independent enforcement of manifest-contract
properties.
🤖 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.

Nitpick comments:
In `@rust/crates/own-bridge/tests/replay.rs`:
- Around line 68-74: Extend the manifest validation loop in the replay test to
assert that every case’s rules collection is non-empty and that each rule is a
non-empty string after trimming. Use the case’s existing rules field alongside
the duplicate-name check, preserving the validator’s independent enforcement of
manifest-contract properties.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 08637902-cf51-411d-9471-fed54548ce9e

📥 Commits

Reviewing files that changed from the base of the PR and between b9879b3 and 878cfdf.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • rust/Cargo.toml
  • rust/crates/own-bridge/Cargo.toml
  • rust/crates/own-bridge/src/lib.rs
  • rust/crates/own-bridge/src/lower.rs
  • rust/crates/own-bridge/src/mos.rs
  • rust/crates/own-bridge/tests/mechanisms.rs
  • rust/crates/own-bridge/tests/replay.rs
  • rust/crates/own-diagnostics/tests/dag.rs
  • rust/crates/own-lowered/tests/replay.rs
  • spec/Bridge.md
  • spec/BridgeBehaviorMatrix.md
  • tests/test_lowered_fixtures.py

@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: 878cfdf0ea

ℹ️ 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 rust/crates/own-bridge/src/lower.rs
@PhysShell
PhysShell merged commit 70b33a9 into main Jul 19, 2026
40 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