Skip to content

feat(parity): #259 foundation — Layer 2 normalized lowered representation - #299

Merged
PhysShell merged 2 commits into
mainfrom
claude/own259-layer2-lowered
Jul 18, 2026
Merged

feat(parity): #259 foundation — Layer 2 normalized lowered representation#299
PhysShell merged 2 commits into
mainfrom
claude/own259-layer2-lowered

Conversation

@PhysShell

Copy link
Copy Markdown
Owner

Что и зачем

Foundation slice #259 (строго ограниченный scope): наблюдаемая промежуточная поверхность Layer 2 из spec/Bridge.md §6 — Python-authored канонический emitter нормализованного lowered representation (ownlang/lowered.py, read-only observer поверх to_module(), ни одним production-путём не импортируется), 26 committed facts/golden пар (tests/fixtures/lowered/), детерминированный regeneration path и CI-gate (tests/test_lowered_fixtures.py, авто-дискавери в run_tests.py): stale, missing и orphaned golden — каждый красный build; per-case double-render фиксирует детерминизм. Решения нормализации заморожены в docstring emitter'а (fixed field order; semantic array order по BR-D4; закрытые statement-shapes под stmt-дискриминатором без optional-полей, null только для bare return; verbatim conds/callee; prelude/externs всегда, lifetimes ровно когда minted; handle-map allowlist фиксированного порядка вместо копии facts-документа; OwnIRError{"error": ...}; LOWERED_VERSION). Непроецируемые AST-shapes fail loud — поверхность не сериализует «тень». Это НЕ реализация own-bridge: Rust-код не добавлялся.

Покрытие матрицы (BridgeBehaviorMatrix)

Строки секции (b), получившие прямой Layer 2 golden:

  • routing R1–R6 → routing_r1_unresolved, routing_r2_subscribe_self, routing_r3_capture_static (unreleased/released/conservative), routing_r4_returned_fresh (+ unknown-provenance twin), routing_r5_di_capture (+ регион самого subscriber'а из services), routing_r6_token_kinds;
  • глобальные sub_/cap_ counters → handles_global_counters; parg_/loc_ ordering → fn_params_ordering; capture lifetime declarations → routing_r3/r5 (и их отсутствие в token-only кейсах);
  • kill-on-rebind → flow_kill_on_rebind, alias_join_cases; unmapped use/release/overspan/bare-return → flow_unmapped_refs;
  • hoisting: positive release + positive use-only + три negative gate (nested depth, while-body, early-return) + pool-kind → hoist_* (5 кейсов);
  • alias_join (wrap, untracked-src no-claim + rebind) → alias_join_cases; overspan/OWN025 L2-часть (Overspan stmt) → lines_preserved; unknown-callee drop → mos_call_unknown_drop; source lines → lines_preserved; vocabulary fail-loud → vocab_unknown_op (текст отказа — часть поверхности); OD-2 tolerant-door fallback запинен как documented behavior → tolerant_unknown_kind;
  • «every declared flow op lowers»: все 9 flow-ops (+ subscribe) присутствуют в goldens — проверено обходом.

MOS-sensitive строки секции (c) с L2 golden: direct consume call (mos_call_direct_consume), in-branch optimistic untrack (mos_untrack_inbranch), top-level kill-site $consume (mos_killsite_toplevel), sig-precise vs merged overload (mos_call_channel_overload_sig; отмечено: у sig-less merged-may вызова $consume — это kill-site discharge, не channel — оба механизма честно видимы), first-party + Tier B fresh mints (mos_fresh_mint).

Отложено с обоснованием:

  • «lowered facts parse as valid .own» — это textual twin (to_own), не Module-проекция; twin-rule BR-L1 остаётся запинен существующими to_own+parse проверками test_ownir.py;
  • deferred-projection escape (L637 UnitOfWork) — сложная составная форма, уже покрытая end-to-end (L3); отдельный L2-кейс добавится при первом расхождении;
  • _FLOW_OPS-declared-but-unhandled internal inconsistency — недостижимо из данных по построению (это dev-инвариант, не входная форма);
  • TZ D1 definite-release ladder и решётка solver'а — принадлежат S-слою (summaries dump, INF-R1), не L2.

Acceptance packet

  • Python-only: emitter API (project_lowered/render_lowered) + 26 goldens + regeneration (--write, с prune orphans) + stale/missing/orphan failure gates (проверены фактически, все три срабатывают). Rust-only: none. Changed: parity infrastructure only (+ реконсиляция двух строк в spec/Bridge.md §6 / BridgeBehaviorMatrix.md footer с «not built yet» на built).
  • git diff vs main: только добавления + эти две spec-правки — production findings побитово идентичны by construction (ownir.py/schema/diagnostics/CLI не тронуты; OD-1–6 не «исправлялись» — OD-2 запинен как есть).
  • python tests/run_tests.py, ruff, mypy (strict, 30 файлов), cargo test (весь workspace) — зелёные; повторная регенерация → чистый git tree.

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

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

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

  • python tests/run_tests.py (новый тест авто-дискаверится: «lowered (Layer 2) fixtures OK: 26 cases»)
  • ruff check . и mypy
  • cargo test — Rust workspace зелёный, не менялся
  • негативные ворота harness'а провзведены вручную (stale/missing/orphan → red)
  • семантическая валидация ключевых goldens против ожиданий Bridge.md (hoist positive/negative, kill-site vs untrack, счётчики, DI-регионы, fail-loud текст)

Связанные issue

Refs #259 (foundation slice — НЕ закрывает issue), #258, #250, #294 (OD-2 запинен), #296.

Чеклист

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

Draft — не мержить, не self-accept. Гейт: независимый review поверхности — подтверждение, что она действительно видит lowering (routing-решения, handle identity, hoist/untrack/kill-site, MOS-применение), а не сериализует его тень. Полный Rust own-bridge этим slice НЕ разрешается.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MdYFKUaygHz1T9H1qJ7BqK


Generated by Claude Code

…tion

The observability surface the Rust own-bridge port is gated on
(spec/Bridge.md §6, layer 2): a Python-authored canonical JSON projection of
what to_module() lowered — Module AST + normalized handle map — taken BEFORE
any analysis runs, so a wrong lowering is visible on its own instead of
hiding behind an unrelated silence at the diagnostics layer.

* ownlang/lowered.py — the emitter (read-only observer; imported by nothing
  on the production verdict path). The docstring FREEZES the normalization
  decisions: fixed field order; semantic (unsorted) array order per BR-D4;
  closed statement shapes under a `stmt` discriminator with no optional
  fields (null only for a bare return); verbatim conds and callee strings;
  prelude/externs always, lifetime declarations exactly when minted; a
  normalized handle-map allowlist in fixed order (never a copy of the facts
  document); OwnIRError projecting as {"error": ...}; LOWERED_VERSION keys
  the surface. Unprojectable AST shapes fail loud — the projection must not
  silently shadow an unknown lowering.
* tests/fixtures/lowered/ — 26 minimal facts/golden pairs covering the
  mandated lowering families: routing R1-R6 (incl. released/conservative
  capture skips and the returned_fresh instance-beats-DI rule), global
  sub_/cap_ counters, parg_/loc_ ordering, capture lifetime declarations,
  DI-routed capture (+ subscriber's own region), the OD-2 tolerant-door
  unknown-kind fallback pinned as documented behavior, kill-on-rebind,
  unmapped use/release/overspan/bare return, cross-branch hoisting positive
  (release and use-only) and the negative gates (nested depth, while-body,
  early-return; pool-kind preservation), optimistic in-branch untrack,
  top-level kill-site $consume, direct consume call, sig-precise vs merged
  overload resolution, unknown-callee drop, first-party + Tier B fresh
  mints, alias_join (incl. untracked-src no-claim + rebind), source-line
  preservation, and the fail-loud unknown-op rejection text.
* tests/test_lowered_fixtures.py — verify/--write harness in the
  cfg_parity mould: stale, missing, and orphaned goldens are each a red
  build; a per-case double-render pins determinism; --write regenerates
  and prunes orphans. Auto-discovered by tests/run_tests.py.
* spec/Bridge.md §6 + BridgeBehaviorMatrix.md footer reconciled from
  "no Python emitter yet" to the built surface.

Guardrails held: no rust/crates/own-bridge, no validation/MOS port, no
ownir.py or schema or diagnostics change (git shows additions + the two
spec files only — production findings are byte-identical by construction),
no CLI change (the emitter is API + test harness). Full suite, ruff, mypy
(strict, now 30 files), and the Rust workspace stay green; regeneration
leaves a clean tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01MdYFKUaygHz1T9H1qJ7BqK
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 54 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: 7ece7e09-9ae6-4322-a380-015db5f56281

📥 Commits

Reviewing files that changed from the base of the PR and between 8855f7e and eb61e43.

📒 Files selected for processing (57)
  • ownlang/lowered.py
  • spec/Bridge.md
  • spec/BridgeBehaviorMatrix.md
  • tests/fixtures/lowered/alias_join_cases.facts.json
  • tests/fixtures/lowered/alias_join_cases.golden.json
  • tests/fixtures/lowered/flow_kill_on_rebind.facts.json
  • tests/fixtures/lowered/flow_kill_on_rebind.golden.json
  • tests/fixtures/lowered/flow_unmapped_refs.facts.json
  • tests/fixtures/lowered/flow_unmapped_refs.golden.json
  • tests/fixtures/lowered/fn_params_ordering.facts.json
  • tests/fixtures/lowered/fn_params_ordering.golden.json
  • tests/fixtures/lowered/handles_global_counters.facts.json
  • tests/fixtures/lowered/handles_global_counters.golden.json
  • tests/fixtures/lowered/hoist_neg_early_return.facts.json
  • tests/fixtures/lowered/hoist_neg_early_return.golden.json
  • tests/fixtures/lowered/hoist_neg_nested_depth.facts.json
  • tests/fixtures/lowered/hoist_neg_nested_depth.golden.json
  • tests/fixtures/lowered/hoist_neg_while_body.facts.json
  • tests/fixtures/lowered/hoist_neg_while_body.golden.json
  • tests/fixtures/lowered/hoist_pool_kind.facts.json
  • tests/fixtures/lowered/hoist_pool_kind.golden.json
  • tests/fixtures/lowered/hoist_positive_release.facts.json
  • tests/fixtures/lowered/hoist_positive_release.golden.json
  • tests/fixtures/lowered/hoist_positive_use_only.facts.json
  • tests/fixtures/lowered/hoist_positive_use_only.golden.json
  • tests/fixtures/lowered/lines_preserved.facts.json
  • tests/fixtures/lowered/lines_preserved.golden.json
  • tests/fixtures/lowered/manifest.json
  • tests/fixtures/lowered/mos_call_channel_overload_sig.facts.json
  • tests/fixtures/lowered/mos_call_channel_overload_sig.golden.json
  • tests/fixtures/lowered/mos_call_direct_consume.facts.json
  • tests/fixtures/lowered/mos_call_direct_consume.golden.json
  • tests/fixtures/lowered/mos_call_unknown_drop.facts.json
  • tests/fixtures/lowered/mos_call_unknown_drop.golden.json
  • tests/fixtures/lowered/mos_fresh_mint.facts.json
  • tests/fixtures/lowered/mos_fresh_mint.golden.json
  • tests/fixtures/lowered/mos_killsite_toplevel.facts.json
  • tests/fixtures/lowered/mos_killsite_toplevel.golden.json
  • tests/fixtures/lowered/mos_untrack_inbranch.facts.json
  • tests/fixtures/lowered/mos_untrack_inbranch.golden.json
  • tests/fixtures/lowered/routing_r1_unresolved.facts.json
  • tests/fixtures/lowered/routing_r1_unresolved.golden.json
  • tests/fixtures/lowered/routing_r2_subscribe_self.facts.json
  • tests/fixtures/lowered/routing_r2_subscribe_self.golden.json
  • tests/fixtures/lowered/routing_r3_capture_static.facts.json
  • tests/fixtures/lowered/routing_r3_capture_static.golden.json
  • tests/fixtures/lowered/routing_r4_returned_fresh.facts.json
  • tests/fixtures/lowered/routing_r4_returned_fresh.golden.json
  • tests/fixtures/lowered/routing_r5_di_capture.facts.json
  • tests/fixtures/lowered/routing_r5_di_capture.golden.json
  • tests/fixtures/lowered/routing_r6_token_kinds.facts.json
  • tests/fixtures/lowered/routing_r6_token_kinds.golden.json
  • tests/fixtures/lowered/tolerant_unknown_kind.facts.json
  • tests/fixtures/lowered/tolerant_unknown_kind.golden.json
  • tests/fixtures/lowered/vocab_unknown_op.facts.json
  • tests/fixtures/lowered/vocab_unknown_op.golden.json
  • tests/test_lowered_fixtures.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/own259-layer2-lowered

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.

…hapes, discriminating fixtures

Five review findings on the Layer 2 surface, each closing a known loophole
before the contract freezes:

1. tolerant_unknown_kind is now a PYTHON-ONLY behavior snapshot: the new
   manifest marks it rust_replay: false with decision: "#294", and the
   emitter/harness/spec wording now says Rust replays every rust_replay:
   true case — a Python-only ambiguity case pins an open decision and takes
   no side on how #294 resolves. The fixture no longer decides OD-1/OD-2
   for the port by accident.

2. tests/fixtures/lowered/manifest.json — the frozen case ledger (name,
   BR/OD rules, rust_replay, decision). The harness requires
   manifest == facts == goldens EXACTLY, so deleting a facts+golden PAIR
   (invisible to the per-file checks) is now a red build, and --write
   refuses to regenerate a shrunken contract (missing facts for a manifest
   case, or an unlisted facts file, are errors). "Cannot silently rot in
   any direction" is now technically true.

3. handles are an ordered ARRAY in mint order with `handle` as the first
   field of each entry — JSON object key order is not a sound carrier of
   semantic order for a byte-exact cross-language contract (BR-L2/BR-D4
   now expressed explicitly, not via map insertion behavior).

4. The closed projection fails loud on every intentionally-unprojected
   shape instead of silently dropping it: non-empty Acquire.args, module
   policies, an extern return type, prelude resources carrying emission
   templates. The docstring also states the Layer 2 boundary explicitly:
   lowered Module + identity/routing metadata; lambda/ignore_reason are
   Layer 3 verdict-presentation metadata and excluded deliberately.

5. The overload fixture now DISCRIMINATES channel from kill-site through
   observable semantics (no invented origin field): after the sig-precise
   channel $consume the post-call `use c1` survives and projects; after
   the merged-may kill-site the post-call release vanishes via unmap. A
   lowerer that wrongly kill-sites the precise call now changes the golden.

Negative gates exercised by hand: pair-deletion red, unlisted-facts red,
--write refusal, plus the prior stale/missing/orphan trio. Full suite,
ruff, mypy, cargo green; double regeneration leaves a clean tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01MdYFKUaygHz1T9H1qJ7BqK

Copy link
Copy Markdown
Owner Author

Independent review verdict (Layer 2 foundation, #259): APPROVED, no blocking findings.

Verified on eb61e431:

  • OD-2 fixture is Python-only pending Bridge contract OD-1/2/3: pin the tolerant door — direct check_facts() diverges from load() (unknown kind fallback, line coercion) #294 (rust_replay: false, decision: "#294"); the other 25 cases are the shared Rust parity set;
  • manifest == facts == goldens prevents silent contract shrink in every direction (golden-only deletion, facts-only deletion, pair deletion, unlisted facts, orphaned golden, stale golden, manifest version/duplicate problems), and --write refuses a shrunken ledger;
  • handles are an ordered array in mint order, handle first — semantic order carried structurally, not via map key order;
  • omitted non-default AST shapes fail loud (Acquire.args, Let RHS, unknown statements, Module.policies, extern return types, resource emission templates, non-VarRef call args); the handle-metadata boundary (lambda/ignore_reason = Layer 3) is stated explicitly;
  • sig-precise channel and merged-may kill-site are observably distinct through post-call reference survival vs unmap — no invented origin field;
  • full CI is green.

Non-blocking (recorded, no commit now): the manifest harness does not yet validate that rules is a non-empty array of non-empty strings — fold into the next ledger change or #296's machine coverage-mapping guard.

Post-merge status: Layer 2 foundation ACCEPTED / FROZEN at LOWERED_VERSION = 1. The next slice is strictly the typed Rust replay/emitter for rust_replay: true fixtures only — no validation/MOS/analysis wiring, and no resolving #294 en route.


Generated by Claude Code

@PhysShell
PhysShell marked this pull request as ready for review July 18, 2026 18:17
@PhysShell
PhysShell merged commit 1a383ed into main Jul 18, 2026
42 of 43 checks passed
PhysShell pushed a commit that referenced this pull request Jul 19, 2026
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
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