feat(ownir): bound source coordinates to int32 and mirror it in the bridge (#259 final acceptance) - #341
Merged
Conversation
…final.0) #259's final acceptance is one contract decision, not one more port: §4.2 bounds a source coordinate at signed 64 bits, the core's AST holds a line as u32, and the four verdict_boundary_* controls sit in rust_replay_excluded because of the gap between those two numbers. #326 established the order for a change of this shape — Python-first, with the census taken against the tree rather than against the change — so this commit measures before anything moves and contains no production code. The note records the owner's decision (D1 domain [0, 2^31-1] with 0 as the absent sentinel, D2 the strict door rejecting out-of-domain as Location for every line field including the two §4.2 records as unchecked, D3 the tolerant door degrading to 0), the full coordinate inventory on both doors in both languages, the churn budget, and the cp1 controls that flip with the `why` each one's decision replaced quoted rather than deleted. tests/coordinate_census.py walks every JSON file under tests/fixtures/ and classifies every line/ctor_line/column slot at any depth. Three properties of it are load-bearing. It is WIDER than the door, because "0 stays a legal line" is a claim about the goldens and a census reading only the inputs cannot see one of them. Its value classes follow the taxonomy's axis — outside-int64 has no representable integer form (Shape), negative and above-int32 are representable coordinates violating the domain rule (Location) — because folding them would reproduce the exact defect #326's census had to discover. And the slot inventory is asserted as a SET: a declared slot no fixture reaches is reported as a phantom, the rule the schema's binding map already lives under. Two measurements the note records because they went differently than expected: * the Rust COLUMN path has no u32 anywhere. Measured through the crate rather than read: a document with "column": 2^40 is accepted by the Rust strict door, carried as Some(1099511627776) on Finding::column, and emitted to SARIF as startColumn 1099511627776. There is no second undeclared boundary; the tolerant readers gain D1's upper bound so the tolerant door cannot emit a column the strict door would refuse, not to close a divergence. * the churn budget is four verdict goldens and nothing else. tests/fixtures/repro does NOT churn, digests.json included: the digest ledger pins the canonical hash of each FACTS document and no facts document changes, and the curated artifact set — the only place a layer envelope's produced/refused status is committed — contains none of the four boundary documents. Measured by probe (the tolerant line reader replaced in-process with the degrade) across every fixture family before writing it down. The census renders to docs/generated/p022-coord-census.md through the existing fragment gate, so no count is typed in the note. Refs #250, #259, #326. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JA26kL7GDHKPxkYhmpzn9W
…ield (Python-first) (#259 final.1) The reference is the oracle, so the coordinate-domain decision lands here first and the port follows in the next commit. This is #326's order, and it was load-bearing there for the same reason it is here: a change to what the reference ACCEPTS cannot be merged beside the port that mirrors it, or the census that judges the pair is taken against a moving reference. spec/OwnIR.md §4.2 now bounds coordinates TWICE, on two axes it keeps apart. The FORM is unchanged: every coordinate integer fits a signed 64-bit integer (#326). The DOMAIN is new and sits inside it: every line lies in [0, 2147483647], every column in [1, 2147483647] or absent or null. 2147483647 is int32, and int32 is the line type of every consumer this project feeds — Roslyn's LinePosition.Line, LSP's uinteger, .NET diagnostics — so a line wider than that cannot reach the place it points at, whatever it can be stored in. 0 is legal and means "unknown / file-level". It is the reference's OWN default for an absent line, and the corpus carries it in the goldens as well as the inputs, so bounding at 0 rather than at 1 reads the reference rather than tightening it. A negative line is refused: no producer emits one — the Roslyn extractor writes StartLinePosition.Line + 1 off a 0-based position — and nothing downstream can point at it. Keeping form and domain apart is not tidiness. The #259 cp1 taxonomy classifies a rejection by its MECHANISM, and this change is the first case where one value can violate one axis and satisfy the other: i64::MAX as a line was accepted and is now `location`, because it still HAS a signed-64 form and violates only the rule about what that form may mean; i64::MAX + 1 was and stays `shape`, because it has no such form at all. Folding them would have re-created the exact defect #326's census had to discover — one violation classified two ways. §4.2's recorded exception is closed rather than carried. components[]. subscriptions[].line and a flow op's line were validated NOWHERE, not even for type, and §4.2 recorded that as an open contract question rather than a parity gap because both implementations agreed about it. The answer is yes, a coordinate no rule reads must still be well-formed — because the TOLERANT door does read it and anchors findings on it. Both are validated now, for type and for domain, the flow-op line recursing through then/else/body exactly as the column walk already did. The schema's binding map moves with them: ten untyped "line": {"type": "integer"} sites become $refs, and UNBOUND is now empty, with the machinery kept so the next unclassified path is still red. The tolerant door degrades, and the degrade is the half that had to be designed rather than transcribed: _as_int is gone. Every one of its call sites read a line, so it becomes _as_line and carries the domain: an out-of-domain line reads as 0 — the value an absent line already reads as. DEGRADE, NEVER CLAMP: 2^31 does not become 2^31 - 1 and -1 does not become 1, because a clamped anchor points at a REAL line the producer did not mean, which is worse than saying nothing. _as_col gains the same upper bound so the tolerant door cannot emit a column the strict door would refuse. The protocol family needed a door split rather than a rule. obligations. _opt_line sits behind BOTH doors through one parser, and measured, a raising _opt_line makes check_facts drop the whole method — so an impossible coordinate would have taken a real violation with it. Type and representability stay grammar and still fail loud (the entry is skipped whole, cp4b's rule, untouched); the DOMAIN is the door's, threaded as `strict` through parse_method/parse_events. load() refuses; check_facts degrades to 0. Two pins, because the two-doors contract is only true if they hold. The tolerant degrade is asserted on three paths with clamp controls at both ends. And the strict door must never REACH the degrade — a document load() accepts must carry no out-of-domain coordinate at all — asserted over every document the cp1 ledger records as accepted, not stated in prose. That assertion found the four controls this decision flips before the ledger did. Ledger: 216 -> 294 controls, insertion-stable (78 added, 0 removed, 0 reordered, 5 rewritten). The five rewritten are the four flips plus accept-zero-line, whose `why` now says zero is the bottom of the domain rather than an edge case that survived. The flips keep their `accept-` names so the change reads as one re-measured control rather than a new control beside a deleted one; each `why` states the position it replaces, and the note quotes the old wording in full. One control closes a gap the flips exposed rather than created: a column below the representable form is `shape` by mechanism even though the reference's message names the 1-based rule, because it tests `v < 1` before the form. Churn is exactly the four verdict_boundary_* goldens (the finding's `line`, nothing else), as the budget written in final.0 predicted. The DI001 evidence slice does not move: its captor step was already dropped by the >= 1 guard at -5 and is still dropped at 0. Nothing in repro/, summaries/, lowered/, verdict_renders/ or either fact-parity ledger changes. The Rust half is knowingly red at this commit — the port has no domain rule yet and the four boundary exclusions still declare a refusal that is about to stop happening. That is the same deliberate red 934213c recorded for #326: a fix landing beside its own evidence cannot be checked against it. Refs #250, #259, #326. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JA26kL7GDHKPxkYhmpzn9W
…ary controls (#259 final.2) The port follows the reference. #259's last exclusion family is promoted, and it is promoted because the CONTRACT moved in the previous commit — never because the core happens to hold a line as u32. own-ir, the strict door. defaulted_int becomes defaulted_line and carries the §4.2 domain after the representable form, answering Location where the form answers Shape; column gains the same upper bound, on the same axis as its 1-based rule; flow_columns becomes flow_coordinates and checks the op's line before its column; subscriptions gain their line between the resource-kind check and the column, which is the reference's order and observable through BR-D1. The site arrays get a second pass rather than a widened `all(...)`, because the reference puts the shape check in one folded predicate and the coordinate rule in a separate loop right after it — one pass answering both would report a domain violation as the record's shape. The typed model stays Option<i64> and now says why in a doc comment: the strict door guarantees the domain, and narrowing the field would put a SECOND door rule in serde — one answering Shape for a domain violation, in serde's field order rather than BR-D1's, firing on the tolerant path where the reference degrades. A typed field wider than the domain is not a hole; it is the shape that leaves exactly one implementation of the rule. The protocol grammar gets a Door parameter, not a second parser. parse_method / events / event_line take Door::{Strict, Tolerant}, mirroring the reference's own split: type and representability stay grammar and fail loud either way (the entry is skipped whole on the tolerant path, cp4b's rule, untouched), while the domain is the door's. Two readings of one grammar is exactly what 4b collapsed into one, so this is a parameter rather than a fork. own-bridge. ast::core_line degrades instead of refusing, and its BridgeError text is gone; it is infallible now and the signature says so, because keeping the Result would leave a refusal path the ledger can no longer reach and no test could ever exercise. verdict::anchor_line and guarded_line collapse into one core_line: guarded_line existed to fold a NEGATIVE site line to 0 while leaving the upper end fail-loud, and the domain makes both ends one rule. Removing the refusal exposed the divergences it was hiding, which is the cp5 lesson arriving on schedule — a comparison surface that gains a member can lose controls. Three of them, all in the same shape: a reader that took the fact's value where the reference takes `_as_line`'s or `_as_col`'s. * the bridge's raw line readers (as_int in lower.rs and verdict.rs) returned the coordinate unchanged. Unobservable only because the AST build refused any document carrying an out-of-domain one before a finding could be built from it. Both become as_line with the domain — the same rename the reference took. Measured: without it, a slice whose acquire line the reference drops would have been kept and anchored where nothing is. * lower::as_col had no upper bound at all, so the previous commit's Python change would have left the tolerant column readers disagreeing. Bounded to the same domain, for the reference's stated reason: the tolerant door must not emit a column the strict door would refuse. * render::code_flows silently DROPPED a step it could not convert — a rule the reference does not have, unreachable behind the refusal and a silent divergence the moment it went. It is a total conversion with a debug_assert as the tripwire, so a future builder that skips as_line says so instead of losing a step. The promotion: rust_replay_excluded 6 -> 2, the pinned set in verdicts.rs with it, and the two that remain are both #294 OD-1. Neither golden was regenerated to reach it — the four moved in final.1, with the reference. Four controls land with the code they protect, because the campaigns in final.3 are only evidence if the layers that must catch a mutation exist. Three were written for rules the promotion made reachable; the fourth exists because a campaign proved a rule was NOT reachable: * own-ir/tests/validation_replay.rs pins the PORT's own coordinate diagnostics — the axis each rule answers on and the domain each message names. Deliberately not the cross-language comparison the rest of that file makes (kind, never text): it stops this port's diagnostics rotting, which matters more now that the door has two coordinate rules on two axes. * own-bridge/tests/verdicts.rs asserts the §4.2 domain at LAYER 2, over every replayed case. Nothing else pins it: ast::core_line degrades on the way in, so a port carrying a raw coordinate through the lowering would look identical at Layer 3 and disagree with the reference at the seam cp2's evidence is taken from. * tests/fixtures/verdicts/verdict_domain_tolerant_readers is one synthetic Layer 3 case for the two tolerant readers nothing else reaches. The four promoted controls all pass through the AST build, which degrades a second time, so a port that kept the raw value still produced the right anchor — measured, as three survived mutations. A DI registration line an escape slice's source hop reads is guarded on `>= 1` and never narrowed again, and a subscription column is read by the column reader alone: at 2^31 the hop must be DROPPED and the column ABSENT, and a pass-through keeps both at coordinates nothing can point at. Its in-domain twin sits beside it so the control cannot pass by refusing everything. Insertion-stable: +1 golden, +1 digest record, zero existing records rewritten. * own-bridge/src/ast.rs gains a unit test on core_line — and its doc comment says why a direct test rather than an end-to-end one, because a unit test on the function under test is normally the weak evidence this project refuses. `lower` degrades every coordinate first, so core_line is a second line of defence over an i64 field wider than the domain: real, and unobservable end to end. The campaign proved it — clamping there, or accepting the whole u32 range again, changed no golden. Two deliberate changes beyond the code, both declared: * the four boundary fixtures' own _doc blocks said "the Rust core refuses the document rather than clamp the coordinate", which this commit makes false. A control whose documentation contradicts the behaviour it pins is worse than the four digest records it costs to fix, so they are rewritten and tests/fixtures/repro/digests.json is regenerated: exactly four records changed, one added (the new control), zero removed, zero rewritten elsewhere. No artifact, trace or reduction moves, and the own-shadow reducer still refuses Layer 3. * an amendment to final.0's churn budget, which predicted zero repro churn. The prediction was right about what the CHANGE forces; the five records are the price of an editorial fix and a control added on purpose, and saying so is the point of having written the budget down first. The campaign definitions land here rather than in final.3, and the reason is mechanical: a recorded run must name a commit that ALREADY contains the definition it ran, because the runner requires a clean tree and the gate requires the recorded commit to be an ancestor of HEAD. It is the same shape the tree already has — cp4b.2's recorded run names the commit before the one that records it. So docs/evidence/p022-coord-1.json (the strict door, 26 mutations) and p022-coord-2.json (the tolerant degrade, 15), their renderer registration, and the re-anchoring every campaign needed are here; the recorded runs are final.3's. Six anchors were re-anchored, never deleted, and two of them are findings rather than bookkeeping: * cp4's M15/M20/M21 and cp4b.2's M23/M28 pointed at core_line, as_col's bound, guarded_line, parse_method's call shape and the manifest's first exclusion — all surfaces this commit moved. * M15's CATCHER moved with it. The Layer 3 replay caught it while core_line refused; the domain makes it unreachable from outside, so the control is the direct one above. * shadow-cp4's M61 expected four catchers and two of them stopped being catchers ON MAIN, before this work: `the_reducer_is_silent_on_unchanged_data` and `two_engines_that_both_refused_a_layer_agree` only failed while some committed artifact still had ASYMMETRIC verdict-layer statuses, and 4b's promotion removed the last of them. Measured at 834f295 with the mutation applied, in a worktree, rather than inferred from this change. The two that remain are the load-bearing ones. That is precisely why every campaign is re-run rather than trusted: a recorded result stays valid for the commit it names, and says nothing about this tree. Two harnesses learn to name their failures (`FAIL[<check>]:`, the house pattern the repro and verdict families already use), so a campaign's expected catcher can be the rule it attacks rather than "this file exited non-zero". Knowingly red at this commit: no campaign result matches its definition's sha any more, and the two new campaigns have no recorded run at all. Both are final.3's, and re-running is the only honest fix — the same deliberate red 934213c recorded for #326. Everything else is green: cargo fmt --check, cargo clippy --workspace --all-targets with zero warnings, cargo test --workspace --no-fail-fast across 44 targets, ruff and mypy. Refs #250, #259, #294. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JA26kL7GDHKPxkYhmpzn9W
…259 final.3) Twelve mutation campaigns, re-run on a clean tree at this branch's head, and the surfaces that describe what they measured. Two are new. They are split by DOOR rather than by sub-checkpoint, because the two doors fail differently — the strict one refuses an out-of-domain coordinate and the tolerant one degrades it — and a campaign measuring them together could not say which half a survivor belonged to. Every rule is mutated on BOTH sides, in the reference and in its Rust mirror, since a domain only one implementation enforces is a divergence rather than a rule. Both ends of both bounds, and off-by-one at each: a domain probed only far outside cannot tell a correct limit from a narrow one. Two mutations attack the CATEGORY and the message rather than the verdict, because a domain violation filed as `shape` leaves every accept/reject green and is the exact defect #326's census found in `column`. Ten are re-runs, and the re-running is the point rather than the ceremony. A recorded result stays valid for the commit it names and says nothing about this tree; discipline 3's lesson is that a comparison surface which gains a member can lose controls. Every campaign now names this branch's head, the tree was clean before and after each, no mutation survived, and every expected catcher fired. What the first pass of the tolerant campaign found is the part worth keeping, and it is recorded in the note's §5 rather than summarized away. Seven of fifteen mutations survived, with one cause: the promotion made rules reachable in principle that the corpus still could not observe. * `ast::core_line` cannot be reached from outside any more — `lower` degrades every coordinate one layer earlier — so clamping there, or accepting the whole u32 range again, changed no golden. It is a real second line of defence over an i64 field wider than the domain, and it is not an end-to-end control; the direct test that now pins it says exactly that, so it cannot be misread as one. * The four promoted controls all pass through the AST build, which degrades a second time, so a port that kept the raw fact value still produced the right anchor. Two readers escape that: a DI registration line an escape slice's source hop reads, and the subscription column. One synthetic Layer 3 case closes both. * One equivalent mutant, proven and re-anchored rather than annotated: `render::code_flows` used to drop a step it could not convert, and §4.2 makes every flow-step line convertible, so re-introducing the drop drops nothing. The mutation moved onto the conversion itself, which the byte-exact rendered replay catches. Two mutations were rewritten because each was caught by a TRACEBACK rather than by a named check, and a catcher recorded as "non-zero exit with no reported failure" is evidence of a crash, not of the rule. Both now attack the same rule where it is legible. The surfaces. A generated cp1 census (`tests/validation_census.py` → `docs/generated/p022-cp1-census.md`) counts the strict-door ledger from the ledger itself: controls by section, by verdict and by category, plus the coordinate family on its own. The three numbers that used to be typed on the P-022 cp1 row — `216 controls`, `matrix 35/181`, `48 mutations` — leave it and stay in the issue bodies as history; the row says "four censuses" and links the fragment. cp4's row records the coordinate boundary as promoted rather than owed; cp5's replaces the `u32` sentence with the decision itself, in the terms the decision was taken in — int32 is the line type of every consumer this project feeds, never "Rust is u32, so the reference is wrong". `spec/Bridge.md` and `spec/BridgeBehaviorMatrix.md` follow: BR-D1 gains the subscription coordinate in its check order, BR-D2 and BR-L11 name `_as_line` and the domain it now carries, OD-3's entry says what "everywhere" means today, and the matrix gains two rows — one per door — rather than a sentence saying the boundary is owed. Three doc comments in the bridge still said `_as_int` after the rename; they say `_as_line` now. The queue reads "#259 final acceptance reached → #260 acceptance / #261", and the line under the checkpoint table states what that means and, at the same length, what it does not: not shadow mode (that is #260's acceptance, blocked on its own two decisions), not "P-022 done", not "Rust is the default". The churn against the branch base is in the note's §3.2, checked against the budget §2.2 wrote before anything moved. Four verdict goldens moved, in the finding's `line` and nothing else. Nothing under `summaries/`, `lowered/`, `verdict_renders/` or either fact-parity ledger moved at all — the goldens anchored at line 0, dozens in the summaries family alone, came through exactly as the decision requires. Five records under `repro/` moved and the budget said zero: four because the boundary fixtures' own documentation contradicted the behaviour they pin, one because a campaign found a blind spot and the fix was a control. Both are deliberate, and saying so is the point of having written the budget down first. Green: `python tests/run_tests.py`, `ruff check .`, `mypy`, and in `rust/` `cargo fmt --check`, `cargo clippy --workspace --all-targets` with zero warnings, `cargo test --workspace --no-fail-fast` across 44 targets. Refs #250, #259, #294, #326. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JA26kL7GDHKPxkYhmpzn9W
|
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 |
PhysShell
pushed a commit
that referenced
this pull request
Sep 8, 2026
Refs #261, #345, #262, #250. The owner ratified #261's decision packet (C-1..C-5 with the exit-code rulings) on 2026-09-08. The tree half of that move: P-022 rows 7b and 8, the preferred queue, the crate-DAG line for own-cli, the proposals index row and the own-cli row of rust/README.md now say what was decided — the oracle split by surface and behaviour class, `report` struck, the production OwnIR executable separated from the residual .own/dev CLI (#345), engine selection kept outside the executable, the production seam alone as #262's CLI prerequisite — and say explicitly that a ratified decision is not completed code. Two stale sentences on the same surfaces corrected in the same move: rust/README.md's own-bridge row still read "in progress (cp5 open)" after #259's final acceptance (PR #341), and the sentence above the P-022 table still said #260's acceptance was blocked on its decisions. No count is typed on any surface; no evidence, code or generated fragment changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016vuEiNoEXaooMDRu6tXrZ6
8 tasks
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.
Что и зачем
#259's final acceptance was one contract decision, not one more port:
spec/OwnIR.md§4.2 bounded a source coordinate at signed 64 bits while the core's AST holds a line asu32, and fourverdict_boundary_*controls sat in the Layer 3 exclusion ledger because of the gap. This lands the decision Python-first — everylineis now in[0, 2147483647]and everycolumnin[1, 2147483647], the int32 domain every consumer this project feeds actually has — mirrors it in the Rust door and bridge, and promotes the four controls out of the exclusion ledger, which leaves only the two #294 OD-1 door controls there.Тип изменения
Как проверено
python tests/run_tests.pyruff check .иmypycd rust && cargo fmt --check && cargo clippy --workspace --all-targets && cargo test --workspace --no-fail-fastpython tests/test_ownir_validation_fixtures.py/test_ownir_defensive_limits.py/test_verdict_fixtures.py/test_repro_fixtures.pypython scripts/mutate_campaign.py --campaign docs/evidence/<c>.json --run) — 276 mutations, zero survivors, every expected catcher named per mutation and hit; counts inp022-coord-mutations.mdand the other campaign fragmentsСвязанные issue
Refs #259, #250, #326, #294.
The owner applies the issue-body updates at merge (the exact text is the last section of this body) and marks #259 by hand. This PR marks nothing.
Чеклист
Checkpoint packet (#250)
Scope. The coordinate-domain contract and its port.
spec/OwnIR.md§4.1/§4.2 andspec/ownir.schema.json; the strict door's domain rule on every line-bearing field, including the two §4.2 recorded as validated nowhere (components[].subscriptions[].lineand a flow op'sline, at every nesting shape); the tolerant door's degrade-to-0; the Rust mirror inown-ir(strict.rs,protocol.rs) andown-bridge(ast.rs,lower.rs,verdict.rs,render.rs); the promotion of the fourverdict_boundary_*controls; two mutation campaigns and a re-run of the other ten; the status surfaces.Explicit non-goals. #294 OD-1 (the two remaining exclusions). The DI004/DI005 rendered-surface case. The BR-V5 protocol-path sentence (cp4b note §6.7).
own-shadow's Layer 3 boundary — the reducer still refuses the verdict layer and that is #260's. No severity, message-matrix or analysis change. No new tolerant rule for the protocol family beyond the coordinate domain.Python source of truth.
ownlang/ownir.py(_check_representable,_check_line_domain,_check_column,_check_flow_coordinates,_as_line,_as_col) andownlang/obligations.py(_opt_line, theDoorsplit). The reference changed first and the port followed; that ordering is #326's precedent and it is load-bearing, because a census taken against a moving reference measures nothing.Frozen fixture.
tests/fixtures/ownir_validation.json(the cp1 strict-door ledger),tests/fixtures/verdicts/undermanifest.json(Layer 3),tests/fixtures/verdict_renders/(BR-V9),tests/fixtures/repro/digests.json(the shadow digest ledger).Fixture regeneration command.
Steady-state test command.
Production dependency changes. None. No crate gained a dependency; no Python module gained an import.
Behaviour changes — NOT "none" this time. This is the one PR in the #259 series where Python production code changes, and it is Python-first by design:
load()rejects alineoutside[0, 2147483647]and acolumnoutside[1, 2147483647](category Location — a representable coordinate violating its domain rule). The representable-form rule (signed 64 bits, Shape) is unchanged and still checked first; keeping the two axes apart is what stops a domain violation being filed as a shape one, which is the exact defect feat(ownir): bound source coordinates and nesting depth (Python-first) #326's census had to discover incolumn.load()validatescomponents[].subscriptions[].lineand a flow op'sline— for type as well as domain, at every nesting shape. §4.2 recorded these as an open contract question ("should a coordinate no rule reads still be well-formed?"). The answer is yes, because the tolerant door does read it and anchors findings on it.check_facts()degrades an out-of-domain coordinate: a line to0("unknown / file-level", the value an absent line already reads as), a column to absent. Degrade, never clamp —2^31does not become2^31 - 1and-1does not become1, because a clamp moves a finding onto a real line the producer never named.Door: type and representability stay grammar and still skip a malformed entry whole (cp4b's rule, untouched); only the domain follows the door. Measured: a raising_opt_lineon the tolerant path drops the whole method, so one impossible coordinate would have taken a real violation with it.Consequence: four Layer 3 goldens move (the finding's
line, nothing else), and the four boundary controls leaverust_replay_excluded.Acceptance changes. The Layer 3 exclusion ledger shrinks from six entries to two, and the pinned set in
own-bridge/tests/verdicts.rswith it. Both remaining entries are #294 OD-1. The replay additionally asserts the §4.2 domain at Layer 2 over every replayed case.Local commands. As above, plus
python scripts/mutate_campaign.py --campaign docs/evidence/p022-coord-1.json --runand--campaign docs/evidence/p022-coord-2.json --runon a clean tree.GitHub Actions links. Filled in by CI on this PR — the workflows are the repository's own (
pr-issue-validation, the Python gate, the Rust gate).Known deferred cases.
ast::core_line's degrade is unreachable end to end (lowerdegrades one layer earlier), so it is a second line of defence over ani64field wider than the domain, pinned by a direct control that says so. Measured by campaign, recorded rather than removed.own-shadowreducer's refusal of Layer 3. Untouched.Migration counters (asserted by the replays, not counted by hand)
own-bridge/tests/verdicts.rs(everyFindingmember, every refusal in full, no fail-fast)own-ir/tests/validation_replay.rsover the cp1 ledgerown-bridge/tests/verdicts.rs(BR-V8 stable sort compared in order)Every count in this packet is a link into
docs/generated/:cp1 census ·
Layer 3 census ·
coordinate census ·
cp5 surface inventory ·
final-acceptance campaigns ·
cp4 ·
cp4b ·
cp5 ·
shadow.
Read the note first:
docs/notes/p022-bridge-verdict-final-acceptance.md.Issue text for the owner to apply at merge
I do not edit issues. Below is the exact replacement text.
Replacement for #259 § Final acceptance
Replacement for #250's #259 entry
Replacement for #250's queue line
🤖 Generated with Claude Code
https://claude.ai/code/session_01JA26kL7GDHKPxkYhmpzn9W
Generated by Claude Code