Skip to content

feat(bridge): wire the lowered module through the core analyses (#259 cp4) - #336

Merged
PhysShell merged 7 commits into
mainfrom
claude/p-022-analysis-wiring-mwhqlw
Sep 5, 2026
Merged

feat(bridge): wire the lowered module through the core analyses (#259 cp4)#336
PhysShell merged 7 commits into
mainfrom
claude/p-022-analysis-wiring-mwhqlw

Conversation

@PhysShell

@PhysShell PhysShell commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Что и зачем

#259 checkpoint 4 — analysis wiring. own_bridge::check_facts прогоняет реальные OwnIR-факты через портированные анализы (check_module: ownership/lifetime/buffer policy; DI и effect finders из own-analysis) и маппит вердикты обратно на C#-анкеры по spec/Bridge.md §5. Сравнение — на поверхности cp4 (identity, anchor, kind, tiering), голдены несут всё, cp5 дожимает сравнение без регенерации.

Одно изменение в ядре: own-analysis теперь проставляет subject (name#line) ровно там, где это делает analysis.py/lifetimes.py — без него BR-V3 map-or-raise невозможен.

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

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

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

  • python tests/run_tests.py (в т.ч. новый tests/test_verdict_fixtures.py: 77 кейсов), ruff check ., mypy (31 файл)
  • cargo fmt --check + cargo clippy --workspace --all-targets (0) + cargo test --workspace --no-fail-fast (36 таргетов, 0 падений)
  • кампания мутаций: 30 мутаций, 30 пойманы (M19 закрыт по ревью — см. ниже)

Python не менялся в производственном коде: он oracle. Добавлен только наблюдатель ownlang/verdicts.py (как lowered.py) и харнесс.

Связанные issue

Refs #250, #259. Ничего не закрывает — cp4 из пяти checkpoint'ов. Строки cp4/cp5 в #250/#259 — отразить при merge (как делалось для cp1); плюс новая строка 4b — protocol analysis (см. ниже).

Чеклист

  • изменение покрыто тестом/селфтестом
  • README/docs обновлены (P-022, индекс proposals, Bridge.md §6, BehaviorMatrix, rust/README — в этом же PR; заметка docs/notes/p022-bridge-verdict-checkpoint4.md)
  • коммиты в conventional-commit стиле

Пакет #250 / #259

Scope:                      #259 cp4 — analysis wiring в own-bridge; поверхность сравнения cp4
Explicit non-goals:         message matrix (BR-V4), evidence slices, render*/build_sarif на bridge-пути (BR-V9) — cp5;
                            протокольный анализ (OBL001–005) — не портирован, документ с протоколами ОТКАЗЫВАЕТСЯ (checkpoint 4b);
                            CLI; performance
Python source of truth:     ownlang/ownir.py::check_facts (+ di.py, effects.py, __main__.check_module)
Frozen fixture:             tests/fixtures/verdicts/ (manifest.json + 77 *.verdicts.json; 19 синтетических *.facts.json)
Fixture regeneration:       python tests/test_verdict_fixtures.py --write
Steady-state test command:  cd rust && cargo test -p own-bridge --test verdicts   (zero Python)
Production dependency changes: own-bridge → own-syntax, own-cfg, own-analysis, own-diagnostics (allowed set в dag.rs расширен;
                            новый тест: ни один core-крейт не зависит от моста)
Behavior changes:           none в Python; own-analysis диагностики несут subject (данные, не вердикт; проверено — ни render,
                            ни SARIF на Rust-стороне его не сериализуют, единственный носитель — DiagIdentity, ключ в памяти)
Acceptance changes:         none
Local commands:             см. «Как проверено»
GitHub Actions links:       head 43cb602 — push https://github.com/PhysShell/Own.NET/actions/runs/33976106044,
                            pull_request https://github.com/PhysShell/Own.NET/actions/runs/33976107131
                            (44/44 check runs завершены: 42 success, Owen neutral, nuget publish skipped; mergeable clean);
                            предыдущие head'ы: 0147ba8 — https://github.com/PhysShell/Own.NET/actions/runs/33970164556 (push),
                            run 1804 — https://github.com/PhysShell/Own.NET/actions/runs/33962691700 (push)
Known deferred cases:       8 объявленных исключений (executable ledger): 2 protocol docs, 4 контроля u32-границы координат, 2 OD-1 door
Python-only verdict count:      0
Rust-only verdict count:        0
Changed verdict count:          0
Ordering-only difference count: 0
Unexplained difference count:   0

над измеренным множеством: 69 кейсов replay (5 refusal'ов, 127 вердиктов) из 77 голденов — 127 вердиктов × cp4-проекция сравнения, не «verdict parity complete».

Checkpoint packet (#259)

Python-only:      0 (над 69 replay-кейсами)
Rust-only:        0
Changed:          0
Ordering-only:    0
Unexplained:      0
Fixture changed:  нет (существующие семейства не тронуты; Layer 2 27/27 и summaries 35/35 байт-в-байт как были)
Acceptance changed: нет
Production DAG changed: own-bridge получил core-рёбра (ir/lowered/syntax/cfg/analysis/diagnostics → мост); обратных рёбер нет и тест это утверждает

Что осталось за измеренным множеством — и почему это записано, а не спрятано

  1. Протоколы (2 документа). obligations.py не портирован. Мост отказывает документу с протоколами вместо неполного списка вердиктов: protocol_isloaded_clean иначе «совпал бы» вакуумно. В checkpoint-листе P-022 step 6b: implement Rust own-bridge with layered OwnIR parity #259 протокольного анализа нет вовсе, в финальном acceptance — есть. По ревью: отдельный явный checkpoint — строка 4b в таблице P-022, не в cp5 (cp5 — messages/evidence/rendering; целое analysis family сверху сделало бы последний checkpoint мешком). 4b не блокирует cp5; final acceptance P-022 step 6b: implement Rust own-bridge with layered OwnIR parity #259 — блокирует.
  2. u32-домен координат (4 контроля). Ядро держит строку в u32; строгая дверь принимает любой signed-64 (§4.2), толерантная — всё, что проходит _as_int. Координата вне 0..=u32::MAX → отказ, никогда не clamp. Референс такие документы анализирует (OWN001 в B.cs:-1, DI001 в reg.cs:-5). Это паттерн cp1: семейство расхождения вне измеренного множества, записанное для решения. Направление по ревью: Python-first tightening — нормативно определить домен координат (отрицательная строка исходника бессмысленна), научить референс отказывать так же, затем снять exclusion; отдельным сознательным изменением контракта с parity evidence, а не «Rust уже u32, значит Python неправ».
  3. OD-1, измерено (2 контроля). Rust-«толерантный вход» — типизированный конструктор OwnIr, поэтому skip-not-coerce (BR-D2) и игнор неизвестного lifetime до моста не доходят. Порт правил есть и закреплён на уровне сырого документа.

Одна граница сравнения: текст map-or-raise интерполирует сообщение диагностики ядра, а в ядре это пока title — три hoist_neg_* refusal'а сравниваются до члена message=, обеими сторонами одной функцией.

Мутации

Раунд 1 (24): три настоящих выживших — M01 (в корпусе не было ни одного OWN033/034/035/041: «закрытый» skip-list без контроля на член), M04/M06 (column и event в ключе дедупа были избыточны на том корпусе — их прикрывали handler и имена) — и одна мутация, которая не мутировала (M10). Все четыре стали контролями/исправлены до раунда 2. Плюс баг харнесса: атрибуция catcher'ов терялась из-за раздельного захвата stdout/stderr.

Раунд 2: 30 мутаций, 29 пойманы, один объявленный выживший — M19 (фильтр ERROR-only: в ядре нет warning-tier вердиктов, корпус не может его убить).

Раунд 3 (по ревью): M19 закрыт — 30/30. BR-V1 вынесен в чистый предикат is_mapped (BR-V1 + BR-V2 в одном месте), синтетический WARNING прогнан через сам map_core: тот же диагностик, тот же handle — ERROR → один finding, WARNING → ни одного, subject-less артефакт BR-V2 отбрасывается до map-or-raise. Production path не изменён; мутация ловится на unit-уровне, и тест это про себя говорит. Таблица — в docs/notes/p022-bridge-verdict-checkpoint4.md, там же матрица cp4→cp5 (proven→regression / deferred→prove) над теми же замороженными голденами — никакой регенерации рядом с реализацией cp5.

Наблюдения по дереву (не правил, только фиксирую)

  • rust/README.md утверждал «two of eight planned crates exist» при семи существующих — таблица исправлена в этом PR (статусные поверхности меняются вместе).
  • cargo doc даёт два предсуществующих warning'а в own-cfg (ссылки на приватный resolve) — не трогал.
  • По ревью (43cb602): строка cp4 в таблице P-022 и строка P-022 в индексе proposals несли census первого раунда мутаций (76 / 18 / 68 / 111) при 77 / 19 / 69 / 127 в note и здесь — выровнены с деревом (пересчитано из manifest + голденов), направление по координатам теперь одно на всех трёх поверхностях. Ручная копия census по четырём документам — класс дефекта, а не опечатка: генерация census и mutation evidence из дерева с --check в существующих Python-воротах идёт отдельной веткой после merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6

The OwnIR bridge maps a core verdict back to a fact handle through the
diagnostic's structured `subject` (`name#line`) and nothing else (BR-V3),
and `own-analysis` emitted `(code, line)` only. `Emit::push_at` now stamps
the symbol's `origin` at exactly the sites where `analysis.py` passes
`subject=sym.origin` — the state problems, OWN001 through the RID's minting
symbol, release, overspan, the return path, the buffer escape codes — and
`lifetimes.py`'s `source#line` on OWN014. The loan/permission codes stay
subject-less, as in Python. `resource_kind`, messages and evidence on the
`.own` path are deliberately not touched: nothing here asserts them.

Pinned through the production `check_module` surface in `tests/subject.rs`
(leak, param, use-after-release, double release, return path, origin
inherited across a move, subject-less OWN006, the buffer origin with its
column, the OWN014 source identity).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
…cp4)

`own_bridge::check_facts` is `ownlang/ownir.py::check_facts` as
spec/Bridge.md §5 writes it: lower, rebuild the Layer 2 document as the core
AST (`ast.rs` — composing the byte-exact cp2 surface instead of lowering a
second time), `own_analysis::check_module`, then map the ERROR-severity
verdicts through the closed BR-V2 skip list and the verdict's `subject` to a
fact handle (or refuse with the reference's map-or-raise text), anchor them
per BR-V5, grade them per BR-V4/V6, append the DI and effect findings from
the `own-analysis` finders (BR-P1/P2), the OWN050/051/052 side paths, dedup
(BR-V7) and sort (BR-V8). The bridge prepares inputs and maps outputs; every
verdict and every anchor is the analysis's own.

`lower_full` now returns beside the Layer 2 document the Python-shaped
handle records (`column` included — it is a dedup-key member), the OWN051
tuples with their owned-local gate, and the solve-failure reason for OWN052;
`lower()` is unchanged and the Layer 2 goldens stay byte-exact.

Declared, not hidden: a document declaring an obligation protocol is
refused (the OBL analysis is not ported; an incomplete verdict list is
worse than a loud no); a coordinate outside the core's `u32` line domain is
refused, never clamped (a site or binding line whose only reader guards on
`>= 1` folds a negative value to 0, which is exact on every path); the
map-or-raise text interpolates the core message, which is still a title.
The DAG widens by the four core crates the wiring needs, and a named test
asserts no core crate depends on the bridge.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
…ro Python

`ownlang/verdicts.py` is the authoritative emitter of the normalized verdict
list (every `Finding` member in declaration order, the bridge's own
ordering, a refusal as `{"error": ...}`; `VERDICTS_VERSION = 1`), and
`tests/test_verdict_fixtures.py` its verify/--write harness over a frozen
ledger: the swept `ownir`/`lowered`/`summaries` corpora plus 19 synthetic
controls (EFF001 and DI001-005 through real blocks, the DI004 call-site and
DI005 store-site anchors, duplicate-site last-wins with the line-0 and
negative-line fallbacks, multi-file ordering, tiering and suppression, the
OWN051 gates, the pooled view anchor, the closed BR-V2 skip list, one record
pair per dedup-key member, and the declared-boundary controls). 77 goldens,
all through the tolerant door.

`own-bridge/tests/verdicts.rs` replays them through `check_facts` and
compares identity, anchor, kind and tiering — every member but `message`,
`related` and `flow`, which the goldens already carry for checkpoint 5:
69 cases (5 refusals, 127 findings), 0/0/0. The other 8 are an executable
exclusion ledger — two protocol documents, four coordinate-boundary
controls, two OD-1 door controls — each run and asserted to be refused
exactly as declared, so an exclusion cannot rot into a coverage hole.

Mutation campaign (--no-fail-fast, restore from copies): 30 mutations, 29
caught, one declared survivor (the ERROR-only filter — no warning-tier core
verdict exists to filter). Round one's three survivors became controls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
Status surfaces changed together, per the status-drift rule: the P-022
checkpoint table (cp4 complete at the checkpoint-4 surface with the
unmeasured set named; cp5 unblocked against the same goldens; the preferred
queue now starts at cp5), the proposals index row, spec/Bridge.md §6 (Layer
3 built; OD-1 measured) and the behavior matrix's fixture summary, and the
rust/README crate table that still claimed two crates of eight.

The checkpoint note carries the census, the three declared boundaries and
the two decisions they owe, the dedup-key argument, and the mutation
campaign — including the round that found three coverage holes and one
mutation that did not mutate.

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

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 79813fab-3bab-4702-9810-c1299535d2d1


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 and others added 3 commits September 5, 2026 13:52
The mutation campaign left one declared survivor: removing the ERROR-only
filter changed nothing, because no facts producer reaches the one core
pass that grades below ERROR, so the corpus can never fail the rule. A
normative BR-V1 behavior that is permanently unprovable is the wrong
resting place. The filter is now one pure predicate (`is_mapped`: BR-V1's
severity rule and BR-V2's closed artifact list together), and a synthetic
WARNING is driven through `map_core` itself — same diagnostic, same handle,
ERROR maps to one finding, WARNING to none, a subject-less BR-V2 artifact is
dropped before map-or-raise. The production path is unchanged; the
mutation is now caught (30/30), at the unit level and saying so.

The replay's log label names the surface it compares — "cp4 verdict
surface (identity/anchor/kind/tiering)" — rather than "verdict parity",
so the phrase a roadmap status is made of never appears ahead of cp5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
…ection

Per the cp4 review: the obligation-protocol analysis is a checkpoint of its
own (row 4b) rather than a fourth job for cp5 — it does not block cp5, and
#259's final acceptance needs it; the coordinate-domain divergence carries
the owner's stated direction (a Python-first tightening, landed as its own
contract change with parity evidence, never "the port is u32, so the
reference is wrong"); the `subject` tail is checked rather than assumed
(no Rust output surface serializes it); M19's closure is recorded; and
cp5 gets its comparison matrix over the same frozen goldens — every
"deferred" row becomes "prove", every "proven" row a regression guard, and
no golden is regenerated beside the implementation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
The checkpoint-4 row of the P-022 status table and the P-022 line of the
proposals index still carried the round-1 census (76 goldens, 18 synthetic
controls, 68 cases replayed, 111 findings) after round 2 added the M01/M04/M06
controls; the checkpoint note and the PR packet already read 77 / 19 / 69 /
127. Derived again from the tree (manifest.json + the *.verdicts.json
goldens): 77 goldens = 58 swept + 19 synthetic, 8 excluded, 69 replayed,
5 refusals, 127 findings. The cp4 row's coordinate-boundary sentence now
names the same direction the cp5 row and the checkpoint note record
(Python-first tightening; the contract decision itself still owed) instead of
an open "Python-first bound or Rust widening".

Status surfaces change together; the census numbers are still hand-copied
across four documents, and an evidence-derived projection with a --check is
the follow-up on its own branch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
@PhysShell
PhysShell merged commit 3fc1246 into main Sep 5, 2026
46 checks passed
PhysShell pushed a commit that referenced this pull request Sep 5, 2026
The campaign's provenance gate accepts a result only while the commit it was
taken on is an ancestor of HEAD. Rebasing this branch onto main after #336
merged left the recorded run describing a history the tree no longer contains,
and `tests/test_checkpoint_status.py` said so — the signal working, not a
breakage.

Re-run on a clean tree at a529193 (this commit's parent), definition
unchanged (sha256 6f858c6bf6a2, no edit to docs/evidence/p022-cp4-mutations.json).
Outcome as the runner printed it:

    p022-cp4 @ a529193: 30/30 caught, 0 survived, 0 compile-error,
    0 invalid, 0 runner-error; expected catchers missed: none

M00, the honesty control, survived — the unmutated tree passes, so the run
measured something. Against the previous result only provenance and per-mutation
elapsed times differ: no outcome and no catcher moved.

docs/generated/p022-cp4-mutations.md is regenerated by
scripts/render_checkpoint_status.py, never edited by hand; the census fragment
is unchanged, since it does not depend on the run. ruff, mypy and
tests/run_tests.py are green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0167aQj9xTAF5r7K1nTCtDQ5
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