feat(bridge): #259 cp5 — the full Finding, the refusal text and the rendered surfaces, proven - #339
Merged
Merged
Conversation
Checkpoint 4 left three Finding members carried but not compared (message, related, flow) and the rendered surfaces with no golden at all. Before porting any of it, establish what the frozen goldens actually reach. tests/verdict_surface_inventory.py is the ledger: every BR-V4 wording branch read off ownlang/ownir.py with WHO OWNS THE STRING (the bridge's matrix, the DI/effect finders' own `message`, or the core diagnostic's), every BR-V5 evidence family and degradation rule, and every BR-V9 rendered-surface rule — each matched against the committed goldens so the coverage column is computed from the tree. It is self-policing: a finding matching zero or two branches, or a slice matching zero or two families, is a ledger problem that fails the gate rather than a count rounded down. scripts/render_checkpoint_status.py renders it into docs/generated/p022-cp5-inventory.md, and the existing suite gate holds it in sync, so no cp5 number is ever typed into a status surface. Two readings the inventory forced, both recorded in the note: * the core-diagnostic message layer does not exist on the Rust side — every core diagnostic is built with its TITLE as the message, and own-cfg's Diag carries no message field at all, which is why the three map-or-raise refusals are still compared up to their `message=` member; * the pooled "never returned" sentence is emitted by two branches of the matrix and is byte-identical on a byte-identical `kind`, so the two are one row here — a property of the surface, stated rather than papered over. No production code changed; no golden was regenerated or edited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…cp5.1) own_bridge::Finding grows `message`, `related` and `flow`; the BR-V4 wording matrix and the BR-V5 slice builders are ported; own-bridge/tests/verdicts.rs compares EVERY member of the reference's Finding instead of cp4's identity / anchor / kind / tiering projection. No golden was regenerated or edited to get there. The Layer 3 family has carried those three members since cp4 precisely so this checkpoint could tighten the comparison against the files as committed, and it went green against them. That the comparison bites was checked, not inferred: breaking one wording turns fourteen cases red. Three additive pieces land in own-analysis, because the reference keeps them there and BR-B1 says the analysis owns its verdict, message included: * di::Service regains the ctor metadata #214 dropped as "presentation-only", and DiFinding carries its message, the registration (file, line) its DI004/DI005 `related` needs beside the call/store-site primary, and the raw site line that says whether the primary came from a site at all — which `line` alone cannot, once the registration fallback has been applied; * effect::EffectStorm carries its message plus the origin kind, mint line and reference chain it is built from; the lattice already computed the chain and the port was discarding it; * nothing else moves — check_di and effect_diagnostics keep their (line, code) projections, so no existing core surface changes shape. A green replay over a corpus that never reaches a branch proves nothing about it, so the cp5.0 inventory's named gaps are closed rather than noted. Six synthetic cases cover every reachable one (the OWN009 and pooled flow-local wordings with their slices, the DI-scoped and both lambda-note OWN014 wordings, the injected-source lambda note, the dropped escape slice, the bare consuming-constructor tail, the one-step DI path, the dropped effect slice); adding them rewrote zero existing records in the verdict ledger and zero in the shadow digest ledger, which is discipline rule 4 measured rather than asserted. Five branches no facts document can reach are pinned by controls driven through map_core, in cp4's M19 shape — and their expected text is the reference's own output, taken by substituting the lowering under check_facts, not a reading of ownir.py. The cp4 campaign definition is re-anchored where cp5.1 moved the code it mutates; its recorded run is superseded and re-run in the following commit, which is what the anchor gate exists to demand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…laration The cp5.1 campaign's first run caught 31 of 40. Eight survivors and one compile error are each a statement about the corpus, so each gets an answer rather than a footnote. Three synthetic cases, added under the frozen ledger (insertion-stable: zero existing records rewritten in the verdict ledger or the shadow digest ledger): * `verdict_unknown_line_guards` — one record per slice builder that IS a verdict but cannot anchor its slice: an acquire with no line, a proven captive whose subscribe line is unknown, an effect whose minting binding has none. All three must emit their verdict and no slice; a step at line 0 is a well-formed coordinate pointing nowhere; * `verdict_degenerate_metadata` — metadata present and degenerate, which is not absent: an empty `type` (the parenthetical is a truthiness test) and a `"?"` ctor type (the tail and the related label drop to the bare wording rather than quoting a placeholder as a type name); * `verdict_dedup_needs_the_message` — two findings equal on every other member of the BR-V7 key and differing only in the `type` that rides into the message. cp4 argued the message member was redundant on the reference's own outputs; this is the document where it is not, and without it a leak is lost. One survivor is unreachable end to end and says so itself: the effect message's `via` clause is guarded on a chain longer than one hop, and a storm only words "derives from" when it walked at least one reference — so the guard is pinned by a unit control on a hand-built storm, on the same reasoning the reference's own `len(path) > 1` rests on. M18 did not compile (an unused format argument) and was rewritten to attack the same rule with valid Rust; a compile error is reported as such and never as "caught". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
Forty mutations over the production surfaces cp5.1 added: every branch of the BR-V4 wording matrix (both OWN001 splits, the pooled twins, the OWN025 view sentence, both OWN014 families with their lifetime phrases and the inline-lambda note in both directions, every token kind, the three advisories), the analysis-owned DI and effect messages with their two conditional tails, every BR-V5 slice family with its line guard and its short-slice degradation, and the BR-V7 dedup key's new message member. Every one is caught, and by the layer that should catch it: the goldens catch the reachable branches through the replay, and the four rules a facts document cannot reach are caught by the unit controls that exist because it cannot. The cp4 campaign's recorded run is still the superseded one — it re-runs next, against the re-anchored definition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…rd why Re-running the cp4 campaign against the cp5.1 tree turned five "dedup key drops <member>" mutations from caught into survived. Putting `message` in the key made three of its members unobservable at the output surface: every wording that varies with `event`, `kind` or `severity` interpolates it, so no facts document can produce two findings equal on the message and differing on one of the three. Two of the five were recoverable and are now a golden. The disposable-field wording names no handler, so one field disposed by two methods is kept apart by `handler` alone; the flow-local wording names no component, so the same local leaking in two methods of one file is kept apart by `component` alone. The other three now drive `dedup`, extracted from `check_facts` so the control runs the production function instead of a copy of it, and the cp4 definition names that control instead of the replay — the replay genuinely cannot catch them any more, and leaving it listed would have been a coverage claim the tree does not support. Worth stating because it will recur: a comparison surface that gains a member can lose controls for the members it subsumes. The campaign surfaced it; a green suite would not have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…ition 30/30 caught on the cp5.1 tree, with the three unobservable dedup members now caught by the control that can see them. The previously recorded run measured a tree cp5.1 reshaped, which is what the anchor gate exists to say. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
Both campaigns now name the same commit, so neither describes a tree the other has since reshaped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 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 |
… cp5.2) The `message=` normalization is gone from both sides: the three `hoist_neg_*` map-or-raise refusals now compare byte for byte, and no comparison boundary is left on a refusal. `own_cfg::Diag` carries the reference's message where this core has it, and the resolver's `undefined name '<name>'` is what it has — measured, not assumed: driving every facts document in the four shared corpora through to_module + check_module and keeping the diagnostics that fail to map yields exactly one message, and that is it. `own-analysis` reads it and falls back to the code's title where own-cfg has none. The remainder being an Option is the point. A code whose text is not ported still renders as its title, and because the refusal comparison is now byte-exact, the first golden that refuses on such a code goes RED demanding the message rather than agreeing with a title. Porting only what the surface consumes is safe because the boundary became a tripwire. Removing the cut turned the three refusals red at once, on a member cp4 could not see. The reference formats the interpolated message with CPython's repr, which switches from ' to " when the string holds a single quote and no double quote — and every core message naming an identifier does. cp4's placeholder quoted unconditionally. Classified as a port bug, fixed in Rust, and pinned against CPython's own repr() output across the quote switch, both escape directions, the backslash, the ASCII control range and None. That is the case for removing a comparison boundary instead of living with it: it was hiding a formatting defect in code that was already ported, not text that was not. One consequence in own-shadow. Its capture declared the verdict layer a partial projection whose stated reason was that BR-V4 and the evidence slices are not ported — false since cp5.1, and a committed artifact carrying a false declaration is worse than none. The layer now emits every member and declares full; artifacts and traces are regenerated. The reducer still REFUSES the verdict layer and records the refusal in every reduction: that is #260's boundary and it has not moved. Promoting it opened a gap, closed here rather than noted: the projection check only validated partial claims, so a full declared over a short document — the over-claim that became reachable once nothing was partial — was unchecked. It now validates both, and the shadow cp2 campaign's M35 is re-anchored from the lie that is no longer possible to the one that is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
The cp5.2 campaign runs in workspace mode, where a catcher is labelled by package and test target, and was written with the shadow slice's layer labels; the shadow cp2 definition still named the projection test under the name it had before cp5.2 widened it. Both are re-anchored so a required catcher that did fire is recorded as having fired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
Ten mutations over what cp5.2 built: the resolver message own-cfg now carries, the seam own-analysis reads it through, every rule of the CPython repr the BR-V3 refusal quotes it with (the quote switch in both directions, the backslash, the ASCII control range, None), and the shadow engine's verdict capture, which stopped being a partial projection here. Three of the ten are caught only by the py_repr control, and that is the honest split: the corpus's messages contain no backslash and no control character, so those rules have no reachable golden and the unit test exists for exactly that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…d M35/M36 cp5.2 made the over-claim M35 attacked impossible (nothing declares a partial projection any more) and widened the test M36 names. Both are re-anchored and the campaign re-run: 11/11 caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…yte (#259 cp5.3) BR-V9 had no golden of any kind: Layer 3 froze what the bridge concludes, never what a consumer sees. `render_finding` and `build_sarif` were pinned only by a handful of hand-written strings in test_ownir.py, which is not the same as freezing the bytes a run emits — and the cp5.0 inventory reported every BR-V9 row as having no control. A new fixture family, built to the Layer 3 pattern: * ownlang/renders.py — an observer beside verdicts.py, imported by nothing in the production path. Every format at both host severities, plus one format render_finding does not know, so the fallback is RENDERED rather than asserted equal to the human line; * tests/fixtures/verdict_renders/ — seven targeted cases under a frozen manifest ledger, each naming the BR-V9 rows it is the control for. Listed, never swept: rendering 79 documents at two severities would freeze megabytes to prove less than seven documents chosen for the rules; * tests/test_verdict_render_fixtures.py — verify/--write, stale/missing/ orphaned each red; * own-bridge/tests/renders.rs — zero Python, compared BYTE for byte. Not a value comparison: SARIF key order is part of this surface, so the port reconstructs the document through typed structs whose field order is the emitter's key order, rather than a map type that sorts and would have quietly produced a different document. Reuse where the format is shared, and not where it is not. codeFlows is own_diagnostics::code_flow verbatim. relatedLocations is deliberately the bridge's own: the core's builder drops a step whose file is empty — the "unprocessable log" invariant evidence.py names — while ownir.py's inline comprehension filters on the line alone and emits the empty uri. Python is the oracle, so the port reproduces that, and render_evidence_slices is the golden that goes red if someone simplifies the two into one. own-diagnostics is unchanged on the core path. The checkpoint-4 subject tail is closed rather than restated: both harnesses scan the rendered bytes for a `subject` key. ownir.Finding has no such member, so a bridge surface emitting one would be inventing a field. Every BR-V9 ledger row is now pinned, and the join is computed: the row ledger is in the inventory, the pins are in the fixture manifest, a row nobody pins reads GAP, and a case pinning a row the ledger does not know is a hard problem. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
The first cp5.3 run caught 25 of 29. Each of the four is answered. `render_rule_catalogue` is a document where the SARIF log's two orderings disagree: the advisory sits on the first line and the leaks after it, so the results run OWN050, OWN001, OWN001 in the bridge's own order while the catalogue must run OWN001, OWN050 — sorted and deduplicated. Until it existed, a log that sorted its results, left its catalogue in encounter order, or listed OWN001 twice all matched the goldens; three mutations survived on that alone. The fourth is defensive and says so: `relatedLocations` drops a step whose line is unknown, and no facts document can build one — every `related` builder requires its line to be `>= 1` before it emits a step at all. It is the reference's rule either way, and dropping it would emit `"startLine": 0`, which is not a coordinate, so it is pinned by a unit control on the production function instead of left unproven. M28 did not compile and was rewritten to attack the same rule with valid Rust. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
Twenty-nine mutations over BR-V9: the three line-per-finding formats and their host-severity pass-through, every workflow-command escaping rule in both directions and in the order they compose, and the bridge's SARIF log — the level mapping's three branches, the sorted and deduplicated catalogue with its titles, the ownirSchemaVersion stamp, region omission and startColumn, backslash normalisation, conditional properties, both evidence projections (including the one place reusing the core's builder would be a bug), suppressions, and the result order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…cp5.4) The census fragment now describes the checkpoint-5 comparison surface — every `Finding` member and every refusal in full — and counts the rendered-surface family beside the verdict ledger through tests/verdict_render_census.py, the same interpretation the render harness and the inventory use. Its filename stays checkpoint 4's, because that is where it was introduced and two notes link it; what it describes it now says in its own first paragraph, which keeps one census for one ledger instead of two that can disagree. The cp5.1/5.2/5.3 campaigns are registered like cp4's and the shadow slice's and rendered by the same single interpreter, so the existing gate holds them: a result that no longer matches its definition, was taken on a dirty tree, missed a required catcher, or names a commit this tree does not descend from is a red build. spec/Bridge.md §6 and spec/BridgeBehaviorMatrix.md move the BR-V4 wording rows and the BR-V9 rendering rows from "carried by the goldens, compared at cp5" to compared, marked L3 ✅ with a legend saying what the mark means. The P-022 status row, the proposals index and the cp4 note's forward pointer move with them. The cp4 note keeps its own numbers as history and says so. No count is typed into any status surface: every number lives in a generated fragment and the prose links. The wording these surfaces now carry is "Layer 3 parity over the measured set at the full Finding and the rendered surfaces", with the unmeasured set named — protocol documents (row 4b), the coordinate-domain controls (decision owed), the OD-1 door controls. Not "verdict parity complete", not "#259 complete", not "shadow mode". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
Three review findings, each taken as stated. **The probe is in the tree now.** The controls for branches no facts document can reach claimed their expected text was "the reference's own output, taken by substituting the lowering" — but the substitution existed only in a shell and a paragraph of prose. Nobody could re-run it, which made those strings typed numbers wearing a sentence's clothes. tests/test_unreachable_branch_probe.py performs the substitution and records what the reference answered into tests/fixtures/unreachable_branches.json, with verify/--write like every other family. The Rust controls (own-bridge's verdict::tests, own-analysis's effect::tests) now `include_str!` that record and read the wording out of it instead of carrying their own copy — so there is one source of truth, the two sides cannot drift into agreeing with each other instead of with Python, and a changed reference wording turns both red. The probe's own limits are written down beside it: the substitution removes reachability, ordering and the pipeline around the branch, so it can never stand in for a golden anywhere a golden is possible, and it is used only where one is not. Two mutations attack the wiring rather than the port: corrupt a recorded wording, and the corresponding control must fail. A control that survives a drifting oracle record is carrying a literal and only appears to be reading the reference. **The queue no longer contradicts the row above it.** P-022's preferred queue still read "#259 cp5 → 4b … 4b does not block cp5" while row 5 in the same table read complete. It now reads 4b → the coordinate-domain decision → #259 final acceptance → #260 acceptance / #261, and the sentence about not blocking cp5 goes with cp5. **The proposals index stops being a database.** The P-022 row carried typed counts from the step-7a slice; they are replaced by what the slice established plus a pointer to the two generated fragments that count it. The cp1 numbers beside them stay: they predate the generator and have no fragment to point at, so removing them would delete information rather than relocate it. One thing found on the way: the capture-branch disposition had a test name wrapped mid-identifier, shipping a reference nobody could follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
…mutations 42/42 caught. The two new ones corrupt a recorded oracle wording and require the Rust control that reads it to fail — so "these controls read the reference's answer, not a literal" is now a recorded fact rather than something demonstrated once in a shell and described afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb
PhysShell
pushed a commit
that referenced
this pull request
Sep 6, 2026
…259 cp4b.2) `refuse_protocols` is gone. `own_bridge::check_facts` now runs the obligation family in its BR-V1 place — after effects, before OWN050 — through the shared grammar and `own_analysis`'s walk, and does the half the bridge owns: the (kind, definite) -> OBL001-004 table, the four deliberately line-free wordings with both exit_desc branches, the component/handler derivations, the anchorless advisory OBL005 with its sorted CPython-repr scope list, and the opened -> barrier (or exit) -> late-close slice with its line guard. The two reference documents leave `rust_replay_excluded` and the replay's pinned exclusion set drops from eight names to six. That is the contract change this checkpoint exists to make. Neither golden was regenerated: the port converges on `protocol_isloaded_violation` and its silent twin exactly as committed, on every `Finding` member. Seven synthetic Layer 3 cases close every row the corpus could not reach — OBL002/003/004/005, all four wordings, the two-, one- and zero-step slices, the identity derivations including the awkward splits, the tolerant door's skip-whole and first-wins rules, and the family composing with DI, effects and OWN050. Insertion-stable: zero existing records rewritten. An eighth case freezes the rendered surfaces — OD-6's literal `?` URI with the region omitted at level note, and a `codeFlows` built from a protocol slice. Two things measured rather than assumed, and recorded instead of faked into controls: the BR-V5 "a slice shorter than two steps is dropped" rule is NOT applied by the protocol path (a leak off the end carries a one-step slice, and one whose open has no line carries none), and the family's append POSITION is unobservable end to end because the sort key's code component decides before insertion order can. The surface inventory follows: the `.*` placeholder row becomes five wordings and two exit_desc tails, the two protocol slice families become five precise shapes plus an empty-slice degradation, and every one of them is now counted over the REPLAYED set instead of carrying a "row 4b" disposition. Shadow: the Rust engine's `verdicts` layer for the protocol document moves from `refused` to `produced`, so its artifact and trace regenerate — the only goldens this checkpoint rewrites, for the same reason #339 recorded when the capture moved partial -> full. The reducer's scope is untouched and still refuses Layer 3: that line is #260's. One gate is knowingly red until the next commit: cp4's mutation M14 attacked the refusal this checkpoint removes, so its definition is re-anchored on the rule as it is now stated (the family must not go silently missing) and the recorded result no longer matches its definition. The re-run follows immediately, exactly as d80359e did for the previous re-anchor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WbgYFv2UW3iRJ3L33naVC3
This was referenced Sep 6, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что и зачем
Checkpoint 4 left three members of
ownir.Findingcarried by the Layer 3 goldens but not compared —message,related,flow— one normalization on every refusal, and the rendered surfaces with no golden at all. This PR closes all of it across checkpoints 5.0–5.4: the inventory, the message matrix and evidence slices, the refusal text, a new byte-exact fixture family for BR-V9, and the status surfaces brought level with what the tree proves. No existing golden was regenerated or edited.Тип изменения
Как проверено
python tests/run_tests.pyruff check .иmypycd rust && cargo fmt --check,cargo clippy --workspace --all-targets(zero warnings),cargo test --workspace --no-fail-fastСвязанные issue
Refs #259 (checkpoints 5.0–5.4). Refs #250. Does not close #259: row 4b (the obligation-protocol analysis) and the coordinate-domain decision are both outstanding.
Чеклист
Checkpoint report
Scope
tests/verdict_surface_inventory.py— a completeness ledger: every BR-V4 wording branch with who owns the string, every BR-V5 evidence family and degradation rule, every BR-V9 rule, each matched against the committed goldens so the coverage column is computed from the tree. Self-policing: a finding matching zero or two branches, or a slice matching zero or two families, fails the gate.own_bridge::Findinggrowsmessage,related,flow; the BR-V4 matrix and BR-V5 slice builders are ported; the replay compares every member.own_cfg::Diagcarries the reference's message for the resolver text BR-V3's map-or-raise line interpolates; themessage=cut is removed and refusals compare in full.ownlang/renders.py+tests/fixtures/verdict_renders/+ its harness + a Rust replay comparing the bytes — the surface that had no golden of any kind.spec/Bridge.md§6,spec/BridgeBehaviorMatrix.md, the P-022 row and the proposals index.Explicit non-goals
rust_replay_excluded.u32coordinate domain — its boundary controls stay excluded; the contract decision is still owed, and the stated direction is a Python-first tightening.own-shadowis not extended to Layer 3. The reducer still refuses the verdict layer and records the refusal in every reduction. One thing there did change and is explained below: the engine's declaration of what it puts in the envelope, which had become false.rust_replay_excludedneither grew nor shrank.Python source of truth
ownlang/ownir.py::check_facts(the BR-V4 matrix, BR-V5 slice construction,_di_findings,_effect_findings,_unresolved_findings, the OWN051 mint),ownlang/di.py,ownlang/effects.py,ownlang/evidence.py::di_path_steps, andownir.render_finding/ownir.build_sariffor BR-V9.ownlang/cfg.py::lookupfor the one core message the refusal text quotes.Frozen fixtures and their regeneration commands
tests/fixtures/verdicts/*.verdicts.json—python tests/test_verdict_fixtures.py --writetests/fixtures/verdict_renders/*.renders.json—python tests/test_verdict_render_fixtures.py --write(new family)tests/fixtures/unreachable_branches.json—python tests/test_unreachable_branch_probe.py --write(the oracle probe, below)No pre-existing golden was regenerated or edited, verified against the branch base: the only pre-existing file touched under the four frozen corpora is
manifest.json, and its diff is additions only — zero deleted lines. Cases were added to both families, and the shadow slice's digest ledger gained one record per added document with existing-record churn measured at zero: P-022 discipline rule 4, on both of its lines.Steady-state test commands without Python
Run from
rust/:cargo test -p own-bridge --test verdictsandcargo test -p own-bridge --test renders.Production dependency changes
own-bridgegainsserdeas a normal dependency (it was a dev-dependency): the BR-V9 SARIF log is typed structs whose field order is the surface, so the shape is checked at compile time rather than assembled as a free-form value. No change to the crate DAG;own-diagnostics/tests/dag.rsstill asserts no core crate depends on the bridge.Behavior changes
In Python: none. The only file added under
ownlang/isrenders.py, an observer in the same shape asverdicts.pyandlowered.py— it calls the production renderers and records what they returned, and nothing in the production path imports it. No existingownlang/module was touched. The OwnIR schema is unchanged.On the Rust side, additive core data where the reference keeps it (BR-B1 — the analysis owns its verdict, message included):
own_analysis::di::Serviceregains the ctor metadata P-022 step 4: port own-analysis (worklist solver + ownership/lifetime/effect/DI) with layered diagnostic parity #214 dropped as "presentation-only";DiFindingcarries itsmessage, the registration(file, line)its DI004/DI005relatedneeds beside the call/store-site primary, and the rawsite_linethat says whether the primary came from a site at all — whichlinealone cannot, once the fallback has been applied;own_analysis::effect::EffectStormcarries itsmessage,origin_kind,decl_lineand referencechain;own_cfg::Diaggainsmessage: Option<String>andown-analysisreads it. TheOptionis the design: an unported message renders as the code's title, and because refusals now compare byte-exact, the first golden that refuses on such a code goes red demanding the message rather than agreeing with a title — a tripwire, not a blind spot.check_di,effect_diagnosticsandown-diagnosticsare untouched, so no existing core surface changed shape.Counters over the measured set
Asserted, not tallied: each replay collects every divergence without fail-fast and fails if one exists, so a green run is 0/0/0/0/0 by construction. That the comparisons bite was checked rather than inferred — breaking a single wording turns a large block of verdict cases red, and breaking a single escaping rule turns a render case red.
The measured set, and what it is not — generated, never typed:
p022-cp4-census.md(the Layer 3 census, both families, describing the cp5 surface) andp022-cp5-inventory.md(which BR-V4 wording, BR-V5 slice family and BR-V9 rule the corpus reaches, and the recorded disposition of every one it does not). Unmeasured, named: protocol documents (row 4b), coordinate-domain controls (decision owed), OD-1 door controls.The oracle probe: where the unreachable branches' expected text comes from
Some BR-V4 wordings are unreachable end to end — the routing table never mints a handle that would take them, or the analysis never reports a verdict that would select them. Their controls have to get their expected text from somewhere, and "the reference's own output" is only worth saying if someone else can re-run it.
tests/test_unreachable_branch_probe.pyrunscheck_factswith its lowering and core substituted, which is the only way to ask the oracle about a state its own inputs cannot construct, and records what it answered intotests/fixtures/unreachable_branches.json. The Rust controls (own-bridge'sverdict::tests,own-analysis'seffect::tests)include_str!that record and read the wording out of it rather than carrying their own copy — one source of truth, and the two sides cannot drift into agreeing with each other instead of with Python.What the probe does not prove is stated beside it: the substitution removes reachability, ordering and the pipeline around the branch, so it can never stand in for a golden anywhere a golden is possible. It is used only where one is not.
Two mutations in the cp5.1 campaign attack the wiring rather than the port: corrupt a recorded wording, and the corresponding control must fail. A control that survives a drifting oracle record is carrying a literal and only appears to read the reference.
Mutation campaigns
All re-run on a clean tree at this branch's head, rendered into
p022-cp5-mutations.mdandp022-cp4-mutations.mdby the one interpreter every campaign in the tree shares. Counts live only there; the suite gate fails while any is stale, was taken on a dirty tree, missed a required catcher, or names a commit this tree does not descend from.Divergences found, and how each was classified
Between Python and Rust, on the verdict list: none. The first full-equality run over the cases cp4 replayed was green.
Which is exactly why the rest of the work exists — a green replay over a corpus that never reaches a branch proves nothing about it.
map_core, in cp4'sM19shape, reading their expected text out of the recorded probe above.message=cut and thehoist_neg_*refusals went red immediately — onpy_repr. CPython'sreprswitches from'to"when a string contains a single quote and no double quote, and every core message naming an identifier does; cp4's placeholder quoted unconditionally. Fixed in Rust, pinned against CPython's ownrepr()output across the quote switch, both escape directions, the backslash, the ASCII control range andNone. This is the argument for removing a boundary instead of living with it: it was hiding a formatting defect in code that was already ported, not text that was not.BR-V7 dedup key drops <member>mutations from caught to survived. Not a weakening: puttingmessagein the key made several of its members unobservable at the output surface — every wording that varies withevent,kindorseverityinterpolates it. The recoverable ones are now a golden (verdict_dedup_key_members: the disposable-field wording names no handler, the flow-local wording names no component); the rest drivededup, extracted fromcheck_factsso the control runs the production function rather than a copy, and the cp4 definition names that control instead of the replay. Worth stating because it will recur: a comparison surface that gains a member can lose controls for the members it subsumes.own-shadow's capture declared its verdict layer a partial projection whose stated reason was that BR-V4 and the evidence slices "are checkpoint 5 and are not ported". After 5.1 that was false. The layer now emits every member and declaresfull; artifacts and traces were regenerated. The reducer still refuses the verdict layer — P-022 step 7a: add dual-engine shadow mode and zero-diff reproduction artifacts #260's boundary, unmoved. Promoting it opened a gap and the gap is closed rather than noted: the projection check only validated partial claims, so afulldeclared over a short document — the over-claim that became reachable the moment nothing was partial — was unchecked. It validates both now, and the shadow cp2 campaign'sM35was re-anchored from the lie that is no longer possible to the one that is.Two properties of the surface, recorded rather than papered over
kind, with the same emptyhandler. Nothing in a serializedFindingseparates them, so the inventory carries them as one row and says why. A port reaching the sentence by the other branch would produce a byte-identical golden — that is what parity on this surface means.codeFlowsreusesown_diagnostics::code_flowverbatim;relatedLocationsdeliberately does not. The core's builder drops a step whose file is empty — the "unprocessable log" invariantevidence.pynames — whileownir.py's inline comprehension filters on the line alone and emits the empty uri. Python is the oracle, so the port reproduces that, andrender_evidence_slicesis the golden that goes red if someone simplifies the two into one.own-diagnosticsbehaviour on the core path is unchanged.The checkpoint-4
subjecttail, closedcp4 established that no Rust output surface serializes a diagnostic's
subjectand left the re-check to cp5, once the bridge grew render and SARIF paths. Both harnesses now scan the rendered bytes for asubjectkey.ownir.Findinghas no such member, so a bridge surface emitting one would be inventing a field.Wording
Not "verdict parity complete", not "#259 complete", not "shadow mode", not "P-022 done".
CI
GitHub Actions runs on this head are linked from the checks section of this PR.
Not for merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DQ471fbXuoL2Poo4Ap3Kfb