feat(rust): #259 slice 3 — own-bridge, OwnIR facts → Layer 2 lowering - #301
Conversation
…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
📝 WalkthroughWalkthroughThe PR adds the Rust ChangesOwnIR bridge and Layer 2 parity
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
Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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
|
Ревизия по review (REQUEST CHANGES, docs-only) — применена, head
Локально: python suite + ruff зелёные, полный Rust workspace 115/0. Жду зелёный CI на Generated by Claude Code |
|
Независимый review — финальный verdict на head
Merge авторизован reviewer'ом. Последовательность: ready → дождаться CodeRabbit post-draft review → перепроверка (exact head / mergeable clean / CI green / нет новых blocking threads) → merge commit без изменения head. Generated by Claude Code |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
rust/crates/own-bridge/tests/replay.rs (1)
68-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMissing
rulesnon-emptiness check (present in the sibling own-lowered validator).
own-lowered/tests/replay.rsandtests/test_lowered_fixtures.pyboth now assert each case'srulesis 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/blankrulesentry 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
⛔ Files ignored due to path filters (1)
rust/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
rust/Cargo.tomlrust/crates/own-bridge/Cargo.tomlrust/crates/own-bridge/src/lib.rsrust/crates/own-bridge/src/lower.rsrust/crates/own-bridge/src/mos.rsrust/crates/own-bridge/tests/mechanisms.rsrust/crates/own-bridge/tests/replay.rsrust/crates/own-diagnostics/tests/dag.rsrust/crates/own-lowered/tests/replay.rsspec/Bridge.mdspec/BridgeBehaviorMatrix.mdtests/test_lowered_fixtures.py
There was a problem hiding this comment.
💡 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".
Что и зачем
Третий 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, ограниченный семантикой, которую тренируют 26rust_replay: truefixtures:released-митигирование),returned_freshprovenance, DI-типизированный capture с регионами изservices, token-путь всех kind'ов creleased→Release;sub_/cap_и общий счётчикparg_/loc_; capture-параметры с line 0 под регионом источника; lifetimes ровно при any-capture;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 тесты) → green6531d70(порт до 26/26) → choreb6465d0(ledger).Тип изменения
Как проверено
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-текстомff58433)cargo fmt --check,cargo clippy --workspace --all-targets— 0 warnings; полный workspace — 115 passed / 0 failedpython tests/run_tests.py,ruff— зелёные;ownir.py/to_moduleне изменялись; fixtures/goldens не изменялись (кроме нуля — ledger diff только в harness-валидации)rules→ FAIL harness)Связанные issue
Refs #259 (slice 3 — НЕ закрывает issue), #258, #294 (не решается — unknown-kind теперь громкий отказ в Rust, поведение не зафиксировано), #296 (rules-валидация закрыта здесь как ledger follow-up), #250.
Чеклист
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
Tests
Documentation