diff --git a/crates/ogar-class-view/src/lib.rs b/crates/ogar-class-view/src/lib.rs index a0aa0d0a..260aa85b 100644 --- a/crates/ogar-class-view/src/lib.rs +++ b/crates/ogar-class-view/src/lib.rs @@ -76,23 +76,29 @@ use ogar_vocab::{ auth_zanzibar, auth_zitadel, automation_trigger, + bfo, billable_work_entry, billing_party, blob, bone, canonical_concept_id, charset, + cob, commercial_document, commercial_line_item, currency_policy, diagnosis, document, + eco, examination, external_practice, + fbbi, + hpo, hr_department, hr_employee, hr_employment_contract, hr_job, + iao, investigation, joint, knowledge_item, @@ -106,7 +112,10 @@ use ogar_vocab::{ medication, mmio_chip, mmio_register, + mondo, network_layer, + obcs, + obi, ocr_renderer, osm_changeset, osm_element_tag, @@ -122,6 +131,7 @@ use ogar_vocab::{ page_image, page_layout, patient, + pato, payment_record, practitioner, pricelist, @@ -153,12 +163,16 @@ use ogar_vocab::{ project_wiki_page, project_work_item, recoder, + ro, + ro_relation_body, rom_image, + sepio, skeleton, tax_policy, textline, treatment, typed_field, + uberon, unicharset, unit_of_measure, visit, @@ -214,6 +228,24 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { ("pricelist", pricelist()), ("pricelist_rule", pricelist_rule()), ("unit_of_measure", unit_of_measure()), + // ── 0x03XX — Ontology (minted 2026-08-22; registered here so the + // codebook↔registry gates hold — a CODEBOOK promotion without a + // registry entry is exactly what `every_codebook_id_appears_in_ + // class_ids_all` exists to catch, and it caught this one) ── + ("mondo", mondo()), + ("hpo", hpo()), + ("uberon", uberon()), + ("pato", pato()), + ("ro", ro()), + ("ro_relation_body", ro_relation_body()), + ("bfo", bfo()), + ("cob", cob()), + ("iao", iao()), + ("obi", obi()), + ("obcs", obcs()), + ("sepio", sepio()), + ("eco", eco()), + ("fbbi", fbbi()), // ── 0x04XX — Weather / Atmosphere ── // These canonical views intentionally carry no W1 payload fields: // field/level/unit slots are selected by WeatherNext's ClassView diff --git a/crates/ogar-obo/src/lib.rs b/crates/ogar-obo/src/lib.rs index 2293dc3e..9e26ce3f 100644 --- a/crates/ogar-obo/src/lib.rs +++ b/crates/ogar-obo/src/lib.rs @@ -89,6 +89,62 @@ pub enum Namespace { } impl Namespace { + /// **Every OBO-core namespace, in concept-id order.** + /// + /// **Scope, stated precisely: this is `OBO_CORE`, not the `0x03` domain.** + /// The Ontology domain has three claimants and this array is one of them — + /// [`registry::OBO_CORE`] (`0x0301..=0x0305`, mirrored by this enum), + /// [`registry::META_STUDY_SPINE`] (`0x0340..=0x0347` — BFO / COB / IAO / + /// OBI / OBCS / SEPIO / ECO / FBbi), and `ogar_ro::RELATION_BODY_CONCEPT_ID` + /// (`0x0306`, in a crate this one cannot see: the dependency runs + /// `ogar-ro` → `ogar-obo`). A caller that needs the whole domain asks the + /// shared codebook (`concepts_in_domain(ConceptDomain::Ontology)`, 14 + /// rows since the mint); iterating `ALL` and calling it "the ontology" + /// would miss nine of fourteen concepts. + /// + /// That distinction is not pedantry — `META_STUDY_SPINE` was once minted + /// **over** `0x0306` and nothing failed, because no enumeration spanned the + /// domain. It was found by hand-enumerating during an unrelated audit; the + /// guard that now catches it lives in `ogar-ro`, the only side that can see + /// both. + /// + /// # Why this still exists next to the shared codebook + /// + /// Since the 2026-08-22 mint, `ogar_vocab::CODEBOOK` carries all 14 + /// Ontology rows (`0x0301..=0x0306`, `0x0340..=0x0347`), so + /// `concepts_in_domain(ConceptDomain::Ontology)` now returns the FULL + /// domain — that is the discovery surface for "everything ontological". + /// (An earlier revision of this doc said the domain carried zero shared + /// rows by design; the mint reversed that ruling, and the sentence was + /// corrected rather than left to steer consumers into assembling the + /// three producer lists by hand.) This array remains the TYPED + /// five-namespace subset — the `OBO_CORE` band as `Namespace` variants, + /// for callers that want the enum (CURIE prefixes, per-namespace + /// dispatch), not a `(name, id)` row scan. + /// + /// Without an enumeration a consumer has three bad options: repeat the list + /// locally (the re-implementation [`from_concept_id`](Self::from_concept_id) + /// already warns about), scan the whole `0x03` block calling + /// `from_concept_id` on all 256 slots, or compute the ids by arithmetic + /// from the variant order. The third is the one that was found downstream: + /// `0x0300 | (ns as u32 + 1)` happens to be right only because the five + /// variants are declared in the same order as five contiguous ids, and it + /// invents a `0x0306` for any ordinal past the end. + /// + /// Ordered by [`concept_id`](Self::concept_id) so a caller iterating this + /// walks the core band in address order. It is also the enum order + /// [`registry::OBO_CORE`] is pinned against, and + /// `registry::tests::obo_core_matches_the_shipped_enum` consumes THIS array + /// rather than repeating it — the duplicate that test used to carry is what + /// promoting it to a public const removes. + pub const ALL: [Namespace; 5] = [ + Namespace::Mondo, + Namespace::Hpo, + Namespace::Uberon, + Namespace::Pato, + Namespace::Ro, + ]; + /// The OBO CURIE prefix (`"MONDO"`, `"HP"`, `"UBERON"`, `"PATO"`, `"RO"`). #[must_use] pub const fn prefix(self) -> &'static str { @@ -913,6 +969,60 @@ pub fn merge_logical_defs( #[cfg(test)] mod tests { + /// **`ALL` must be exactly what `from_concept_id` accepts.** + /// + /// The failure this exists for is the classic one: someone adds a sixth + /// namespace, extends `concept_id` / `from_concept_id` / `prefix` (the + /// compiler forces all three — they are exhaustive matches) and leaves the + /// array at five. Nothing in the type system catches that, because an array + /// literal is not a match. + /// + /// So the test does not compare against a second hand-written list. It + /// sweeps the ENTIRE u16 space, collects every concept `from_concept_id` + /// resolves, and asserts that set is exactly `ALL`'s — in both directions. + #[test] + fn all_is_exactly_the_set_from_concept_id_resolves() { + let swept: Vec = (0..=u16::MAX) + .filter(|c| super::Namespace::from_concept_id(*c).is_some()) + .collect(); + let listed: Vec = super::Namespace::ALL + .iter() + .map(|n| n.concept_id()) + .collect(); + assert_eq!( + swept, listed, + "ALL and from_concept_id disagree — a namespace was added to one and not the other" + ); + + // Anti-vacuity: the sweep really is selective (5 of 65_536), so the + // equality above is not two empty sets agreeing. + assert_eq!(swept.len(), super::Namespace::ALL.len()); + assert!(!swept.is_empty(), "the sweep found nothing at all"); + assert!( + swept.len() < 16, + "from_concept_id is accepting far too much: {}", + swept.len() + ); + + // Ordered by concept id, as the doc promises — a caller iterating ALL + // walks the block in address order. + assert!( + listed.windows(2).all(|w| w[0] < w[1]), + "ALL is not in ascending concept-id order: {listed:?}" + ); + + // Every entry round-trips, and every entry is in the 0x03 block. + for n in super::Namespace::ALL { + assert_eq!(super::Namespace::from_concept_id(n.concept_id()), Some(n)); + assert_eq!( + n.concept_id() >> 8, + 0x03, + "{n:?} is outside the Ontology domain" + ); + assert!(!n.prefix().is_empty()); + } + } + use super::*; use std::collections::HashMap; diff --git a/crates/ogar-obo/src/registry.rs b/crates/ogar-obo/src/registry.rs index 7bbd12ba..f90b8935 100644 --- a/crates/ogar-obo/src/registry.rs +++ b/crates/ogar-obo/src/registry.rs @@ -266,13 +266,11 @@ mod tests { /// moves the shipped digests — and this test is what refuses it. #[test] fn obo_core_matches_the_shipped_enum() { - let enum_order = [ - Namespace::Mondo, - Namespace::Hpo, - Namespace::Uberon, - Namespace::Pato, - Namespace::Ro, - ]; + // `Namespace::ALL` — not a local literal. This test used to carry its + // own copy of the five, which made three copies of one list (enum, + // registry, test). Consuming the public const leaves two, and the + // agreement between those two is exactly what this test asserts. + let enum_order = Namespace::ALL; assert_eq!( OBO_CORE.len(), enum_order.len(), diff --git a/crates/ogar-r2il/examples/probe_counterfactual_witness_kernel.rs b/crates/ogar-r2il/examples/probe_counterfactual_witness_kernel.rs new file mode 100644 index 00000000..b9896998 --- /dev/null +++ b/crates/ogar-r2il/examples/probe_counterfactual_witness_kernel.rs @@ -0,0 +1,530 @@ +//! Counterfactual visibility probe — WIRED to the loco/r2il thinking surface. +//! +//! # What changed against the earlier lance-graph stream probe +//! +//! The earlier D-SK-STREAM fixture generated witness events from a seeded +//! RNG and called a seed-preserving reshuffle an "intervention" — measuring +//! that a mechanical dummy is mechanically invisible. This probe replaces +//! the dummy with the real thinking substrate: +//! +//! - the stream source is an `ogar_loco::FunctionBody` — a real stored +//! program under the real call ABI, mixing shared-core operand producers +//! (`NUMBER`, `VAR_GET`) with R2IL consumers (`IntAdd`/`IntSub`/`IntXor`/ +//! `Store`) resolved through `R2ILVocabulary`; +//! - the Markov window IS the loco statement: `statement_bounds` (the +//! operand-producing post-order run plus its consuming call, the R5 +//! maskable unit) segments the body, and each statement becomes one +//! witness window — intra-window order is the STACK DISCIPLINE, not noise; +//! - the counterfactual is a SEMANTIC PROGRAM EDIT, localized to one +//! statement found via `statement_bounds` and confined under the masked +//! lane projection (`CallMask` + `project`): CF-1 swaps `IntSub`'s +//! operand-producing calls (minuend/subtrahend exchange — a dataflow +//! counterfactual), CF-2 substitutes the consuming operator +//! (`IntAdd` → `IntSub` — a rule counterfactual), CF-3 is the identical +//! program (silence). +//! +//! # The four-tier visibility ladder under measurement +//! +//! For each counterfactual, four readings of "how far is the edited +//! timeline from the factual one": +//! +//! 1. **byte tier** — differing call slots between the two value slabs, +//! counted through the r2il masked projection (loco's own diff); +//! 2. **exact-area tier** — normalized signature-kernel distance with the +//! full per-window antisymmetric area matrices (computed from the call +//! stream — information the register does NOT store); +//! 3. **register tier** — the same kernel with the 24-locus register cast +//! as level-2 coefficients (sign = orientation within the statement, +//! magnitude = i4 net); +//! 4. **increment tier** — the same kernel, increments only. +//! +//! Pre-registered expectations (each an assert): +//! +//! - G0 (STOP on fail): the inline Goursat solver matches the I₀(2√⟨u,v⟩) +//! closed form on linear paths (rel err < 2e-2). +//! - G1: segmentation is GREEN over the mixed core+R2IL body, and the edit +//! is CONFINED: outside the edited statement's `CallMask`, the projected +//! calls of factual and counterfactual slabs are identical; inside, they +//! differ (the lens proves locality — can-fire + can-stay-silent). +//! - G2a (CF-1a intra-run swap): byte > 0, exact-area > 0, increment == 0 +//! (net per-locus counts unchanged — increments are structurally blind +//! to dataflow order), register == 0 — the orientation bit is +//! MIDPOINT-granular and honestly blind to sub-midpoint order (the +//! probe's first gate expected otherwise and was falsified; blind spot +//! #1, named). +//! - G2b (CF-1b midpoint-crossing swap): byte > 0, exact-area > 0, +//! increment == 0, register > 0 — the orientation tier sees exactly the +//! class of dataflow edit its bit encodes. +//! - G3 (CF-2 operator substitution): byte > 0, increment == 0, AND +//! register == 0 — the honest resolution limit: `IntAdd` and `IntSub` +//! land on the same arithmetic locus, so a rule counterfactual below the +//! locus granularity is visible ONLY at the byte tier. Named, not hidden. +//! - G4 (CF-3 identical): all three tiers read exactly 0. +//! +//! Run: `cargo run -p ogar-r2il --example probe_counterfactual_witness_kernel` + +use ogar_loco::vocabulary::conformance::validate; +use ogar_loco::{ + Call, FnIndex, FunctionBody, LaneShape, StatementBounds, VALUE_SLAB_LEN, statement_bounds, +}; +use ogar_r2il::{CallMask, R2ILFn, R2ILVocabulary, project}; + +const D: usize = 24; +const N_PAIRS: usize = D * (D - 1) / 2; +const SHAPE: LaneShape = LaneShape::Pairs; + +fn r2il(name: &str) -> FnIndex { + let o = R2ILFn::MNEMONICS + .iter() + .position(|m| *m == name) + .unwrap_or_else(|| panic!("unknown mnemonic {name}")); + R2ILFn::from_ordinal(o).unwrap().0 +} + +/// Function → witness locus. Deliberately COARSER than the codebook: the +/// register has 24 loci, the codebook 256 slots, so classes share loci — +/// G3 measures exactly what that costs. +fn locus(f: FnIndex, arith: &[FnIndex]) -> usize { + if f == FnIndex::NUMBER { + 0 + } else if f == FnIndex::VAR_GET { + 1 + } else if arith[..3].contains(&f) { + 8 // IntAdd / IntSub / IntMult — one arithmetic locus + } else if f == arith[3] { + 9 // IntXor — the bitwise locus + } else { + 11 // Store — the memory-write locus + } +} + +/// One witness event, derived from one executed call. +#[derive(Clone, Copy)] +struct Event { + t: f64, // position within the statement, (idx + 0.5) / stmt_len + locus: usize, + delta: f64, // 1 + immediate/64 — the immediate matters, order matters +} + +/// The factual program: `n` statements of the R2IL store idiom +/// `NUMBER:a VAR_GET:p VAR_GET:q Store` — operand run early, +/// consumer late, exactly the stack shape `statement_bounds` segments. +fn factual_program(n: usize, arith: &[FnIndex]) -> FunctionBody { + let mut calls = Vec::new(); + for s in 0..n { + calls.push(Call::with_value( + FnIndex::NUMBER, + (7 + 13 * s as u32 % 200) as u8, + )); + calls.push(Call::with_value(FnIndex::VAR_GET, (s % 8) as u8)); + calls.push(Call::new(arith[s % 4])); + calls.push(Call::with_value(FnIndex::VAR_GET, (s % 5) as u8)); + calls.push(Call::new(r2il("Store"))); + } + FunctionBody::from_calls(SHAPE, &calls).expect("factual body fits") +} + +/// Rebuild the body with `edit` applied to the call list — the counterfactual +/// constructor. Edits are index-local; the body is re-validated by +/// `from_calls`, so an edit that broke the ABI would refuse loudly. +fn edited(body: &FunctionBody, edit: impl Fn(&mut Vec)) -> FunctionBody { + let mut calls: Vec = body.calls().collect(); + edit(&mut calls); + FunctionBody::from_calls(SHAPE, &calls).expect("edited body fits") +} + +/// Events per statement — the witness stream, one window per statement. +fn windows(body: &FunctionBody, bounds: &[StatementBounds], arith: &[FnIndex]) -> Vec> { + let calls: Vec = body.calls().collect(); + bounds + .iter() + .map(|b| { + (0..b.call_count) + .map(|i| { + let c = calls[b.first_call + i]; + Event { + t: (i as f64 + 0.5) / b.call_count as f64, + locus: locus(c.function, arith), + delta: 1.0 + f64::from(c.values[0]) / 64.0, + } + }) + .collect() + }) + .collect() +} + +fn pair_idx(k: usize, l: usize) -> usize { + k * D - k * (k + 1) / 2 + (l - k - 1) +} + +/// Coarse path + exact per-window areas + 24×i4 registers, from the stream. +struct Coarse { + pts: Vec>, + areas_exact: Vec>, + regs: Vec<[i8; D]>, + scale: f64, +} + +fn coarsen(wins: &[Vec]) -> Coarse { + let mut nets_all = Vec::new(); + for w in wins { + let mut net = [0.0f64; D]; + for e in w { + net[e.locus] += e.delta; + } + nets_all.extend(net.iter().copied().filter(|v| *v > 0.0)); + } + let scale = nets_all.iter().sum::() / nets_all.len().max(1) as f64; + + let mut x = vec![0.0f64; D]; + let mut pts = vec![x.clone()]; + let mut areas_exact = Vec::new(); + let mut regs = Vec::new(); + for w in wins { + let x0 = x.clone(); + let mut a = vec![0.0f64; N_PAIRS]; + let mut net = [0.0f64; D]; + let mut t_sum = [0.0f64; D]; + for e in w { + // A_kl += ½·rel_k·δ (moving l) / −½·rel_l·δ (moving k), rel to + // the window start — the D-SK-STREAM accumulation, verbatim. + for k in 0..D { + if k != e.locus { + let rel_k = x[k] - x0[k]; + if k < e.locus { + a[pair_idx(k, e.locus)] += 0.5 * rel_k * e.delta; + } else { + a[pair_idx(e.locus, k)] -= 0.5 * rel_k * e.delta; + } + } + } + x[e.locus] += e.delta; + net[e.locus] += e.delta; + t_sum[e.locus] += e.t * e.delta; + } + let mut reg = [0i8; D]; + for l in 0..D { + if net[l] > 0.0 { + let o: i8 = if t_sum[l] / net[l] < 0.5 { -1 } else { 1 }; + reg[l] = o * (net[l] / scale).round().clamp(1.0, 7.0) as i8; + } + } + pts.push(x.clone()); + areas_exact.push(a); + regs.push(reg); + } + // Normalize total drift to 1 so the Goursat scheme stays in its stable + // regime; registers are scale-free (they carry their own codebook scale). + let s: f64 = pts + .last() + .unwrap() + .iter() + .map(|v| v * v) + .sum::() + .sqrt(); + for p in &mut pts { + for v in p.iter_mut() { + *v /= s; + } + } + for a in &mut areas_exact { + for v in a.iter_mut() { + *v /= s * s; + } + } + Coarse { + pts, + areas_exact, + regs, + scale: scale / s, + } +} + +/// Register-cast area surrogate (the E-MONOTONE-STREAM… construction): +/// Â_kl = ¼·v_k·v_l·(o_l − o_k), a pure function of the stored register. +fn surrogate_areas(regs: &[[i8; D]], scale: f64) -> Vec> { + regs.iter() + .map(|reg| { + let mut a = vec![0.0f64; N_PAIRS]; + for k in 0..D { + if reg[k] == 0 { + continue; + } + let (ok, vk) = ( + f64::from(reg[k].signum()), + f64::from(reg[k].unsigned_abs()) * scale, + ); + for l in (k + 1)..D { + if reg[l] == 0 { + continue; + } + let (ol, vl) = ( + f64::from(reg[l].signum()), + f64::from(reg[l].unsigned_abs()) * scale, + ); + a[pair_idx(k, l)] = 0.25 * vk * vl * (ol - ok); + } + } + a + }) + .collect() +} + +/// First-order Goursat recursion with level-2-augmented coefficients — +/// the same scheme the lance-graph D-SK arc measured; inlined because this +/// crate deliberately stays sibling-free (see its module docs on deps). +fn kernel(x: &[Vec], ax: &[Vec], y: &[Vec], ay: &[Vec]) -> f64 { + let (n, m) = (x.len(), y.len()); + let mut k = vec![vec![1.0f64; m]; n]; + for i in 0..n - 1 { + let dx: Vec = (0..D).map(|d| x[i + 1][d] - x[i][d]).collect(); + for j in 0..m - 1 { + let mut c = 0.0; + for d in 0..D { + c += dx[d] * (y[j + 1][d] - y[j][d]); + } + let mut area = 0.0; + for t in 0..N_PAIRS { + area += ax[i][t] * ay[j][t]; + } + c += 2.0 * area; + k[i + 1][j + 1] = k[i + 1][j] + k[i][j + 1] - k[i][j] + c * k[i][j]; + } + } + k[n - 1][m - 1] +} + +fn zeros_like(a: &[Vec]) -> Vec> { + a.iter().map(|v| vec![0.0; v.len()]).collect() +} + +/// Normalized-kernel distance under a chosen area carrier. +fn nk_dist(cx: &Coarse, ax: &[Vec], cy: &Coarse, ay: &[Vec]) -> f64 { + let kxy = kernel(&cx.pts, ax, &cy.pts, ay); + let kxx = kernel(&cx.pts, ax, &cx.pts, ax); + let kyy = kernel(&cy.pts, ay, &cy.pts, ay); + 1.0 - kxy / (kxx * kyy).sqrt().max(1e-300) +} + +/// `I₀(2√⟨u,v⟩)` — the linear-path closed form, G0's anchor. +fn bessel_i0_of_2sqrt(dot: f64) -> f64 { + let mut sum = 0.0; + let mut term = 1.0f64; + for k in 0..60 { + if k > 0 { + term *= dot / ((k * k) as f64); + } + sum += term; + if term.abs() < 1e-18 * sum.abs() { + break; + } + } + sum +} + +/// Byte-tier distance: differing call slots between two slabs, read through +/// the masked lane projection — loco's own diff, no bespoke byte walk. +fn byte_tier_diff(a: &[u8; VALUE_SLAB_LEN], b: &[u8; VALUE_SLAB_LEN]) -> usize { + let all = CallMask::all(SHAPE); + project(a, SHAPE, &all) + .zip(project(b, SHAPE, &all)) + .filter(|((_, ca), (_, cb))| ca != cb) + .count() +} + +fn slab_of(body: &FunctionBody) -> [u8; VALUE_SLAB_LEN] { + let mut slab = [0u8; VALUE_SLAB_LEN]; + body.write_into_value_slab(&mut slab); + slab +} + +fn main() { + // ── G0: solver anchor ──────────────────────────────────────────────── + println!("== 0. G0: inline Goursat solver vs I0 closed form, d={D} =="); + let n0 = 256; + let u: Vec = (0..D) + .map(|i| 0.4 * ((i * 37 + 11) % 100) as f64 / 100.0 - 0.1) + .collect(); + let v: Vec = (0..D) + .map(|i| 0.4 * ((i * 53 + 29) % 100) as f64 / 100.0 - 0.1) + .collect(); + let lin = |dir: &[f64]| -> Vec> { + (0..=n0) + .map(|i| dir.iter().map(|d| d * i as f64 / n0 as f64).collect()) + .collect() + }; + let (lu, lv) = (lin(&u), lin(&v)); + let dot: f64 = u.iter().zip(&v).map(|(a, b)| a * b).sum(); + let zl: Vec> = vec![vec![0.0; N_PAIRS]; n0 + 1]; + let k_pde = kernel(&lu, &zl, &lv, &zl); + let k_cf = bessel_i0_of_2sqrt(dot); + let g0 = (k_pde - k_cf).abs() / k_cf.abs().max(1e-12); + println!(" rel err {g0:.2e}"); + assert!(g0 < 2e-2, "G0 FAIL — STOP"); + + // ── The factual program, segmented by the real machinery ───────────── + let arith = [ + r2il("IntAdd"), + r2il("IntSub"), + r2il("IntMult"), + r2il("IntXor"), + ]; + let vocab = validate(R2ILVocabulary).expect("R2IL vocabulary conforms"); + let n_stmt = 24usize; + let factual = factual_program(n_stmt, &arith); + let bounds = statement_bounds(&vocab, &factual).expect("mixed core+R2IL body segments"); + println!("\n== 1. G1: the loco statement IS the Markov window =="); + println!( + " body: {} calls under {:?}; statement_bounds → {} statements (5 calls each)", + factual.len(), + SHAPE, + bounds.len() + ); + assert_eq!(bounds.len(), n_stmt, "G1 FAIL: segmentation drifted"); + + // ── The three counterfactuals, statement-local, mask-confined ──────── + // The edited statement: pick one mid-body whose consumer is IntSub + // (s % 4 == 1), so CF-1's operand swap is a real minuend/subtrahend + // exchange, not a commutative no-op. + let s_star = (0..n_stmt) + .find(|s| s % 4 == 1 && *s >= n_stmt / 2) + .unwrap(); + let b = bounds[s_star]; + + // CF-1a: swap the two operand-producing calls (NUMBER ↔ first VAR_GET) — + // both sit BEFORE the statement midpoint, so this dataflow edit is below + // the register's 1-bit orientation resolution by construction. + let cf1a = edited(&factual, |calls| { + calls.swap(b.first_call, b.first_call + 1); + }); + // CF-1b: swap NUMBER (position 0, early) with the second VAR_GET + // (position 3, late) — the dataflow edit CROSSES the midpoint, which is + // exactly what the register's orientation bit encodes. Still a valid + // stack program (the segmentation below re-proves it). + let cf1b = edited(&factual, |calls| { + calls.swap(b.first_call, b.first_call + 3); + }); + // CF-2: substitute the consuming operator IntSub → IntAdd. + let cf2 = edited(&factual, |calls| { + calls[b.first_call + 2] = Call::new(arith[0]); + }); + // CF-3: the identical program. + let cf3 = edited(&factual, |_| {}); + + // Mask-confinement lens (G1's second half): outside the statement's + // CallMask, factual and CF-1 project identically; inside, they differ. + let mut stmt_mask = CallMask::empty(SHAPE); + for i in 0..b.call_count { + stmt_mask.set((b.first_call + i) as u32); + } + let outside = stmt_mask.not(); + let (slab_f, slab_1) = (slab_of(&factual), slab_of(&cf1a)); + let same_outside = project(&slab_f, SHAPE, &outside) + .zip(project(&slab_1, SHAPE, &outside)) + .all(|((_, a), (_, b))| a == b); + let diff_inside = project(&slab_f, SHAPE, &stmt_mask) + .zip(project(&slab_1, SHAPE, &stmt_mask)) + .filter(|((_, a), (_, b))| a != b) + .count(); + assert!( + same_outside, + "G1 FAIL: the edit leaked outside its statement mask" + ); + assert!( + diff_inside > 0, + "G1 FAIL: the masked statement shows no edit" + ); + println!( + " G1 PASS: edit confined — outside mask identical, {} differing calls inside (statement {s_star})", + diff_inside + ); + + // ── Streams + three-tier distances ─────────────────────────────────── + let w_f = windows(&factual, &bounds, &arith); + let c_f = coarsen(&w_f); + let a_f = surrogate_areas(&c_f.regs, c_f.scale); + let z_f = zeros_like(&a_f); + + let tier = |cf: &FunctionBody, name: &str| -> (usize, f64, f64, f64) { + let bounds_cf = statement_bounds(&vocab, cf).expect("counterfactual segments"); + let w = windows(cf, &bounds_cf, &arith); + let c = coarsen(&w); + let a = surrogate_areas(&c.regs, c.scale); + let z = zeros_like(&a); + let byte = byte_tier_diff(&slab_of(&factual), &slab_of(cf)); + let incr = nk_dist(&c_f, &z_f, &c, &z); + let exact = nk_dist(&c_f, &c_f.areas_exact, &c, &c.areas_exact); + let reg = nk_dist(&c_f, &a_f, &c, &a); + println!( + " {name}: byte {byte} | increment {incr:.3e} | exact-area {exact:.3e} | register {reg:.3e}" + ); + (byte, incr, exact, reg) + }; + + println!("\n== 2. four-tier visibility of semantic counterfactuals =="); + let (b1a, i1a, e1a, r1a) = tier(&cf1a, "CF-1a intra-run operand swap "); + let (b1b, i1b, e1b, r1b) = tier(&cf1b, "CF-1b cross-midpoint swap "); + let (b2, i2, e2, r2) = tier(&cf2, "CF-2 IntSub→IntAdd (rule) "); + let (b3, i3, e3, r3) = tier(&cf3, "CF-3 identical (silence) "); + + // G2a — the fine dataflow edit: exact areas see it; the register's + // midpoint-granular orientation bit is BLIND to it (named limit #1); + // increments blind by construction. The probe's first formulation + // expected the register to see this and was falsified — an intra-run + // swap never crosses the midpoint the orientation bit encodes. + assert!(b1a > 0, "G2a FAIL: byte tier blind"); + assert!( + i1a.abs() < 1e-12, + "G2a FAIL: increments see a pure reordering ({i1a:.3e})" + ); + assert!( + e1a > 1e-9, + "G2a FAIL: exact areas blind to a real order change ({e1a:.3e})" + ); + assert!( + r1a.abs() < 1e-12, + "G2a FAIL: register claims sub-midpoint resolution it does not have ({r1a:.3e})" + ); + println!( + " G2a PASS: intra-run dataflow edit — exact areas see it; the register's midpoint bit is honestly blind" + ); + + // G2b — the midpoint-crossing dataflow edit: NOW the register sees it. + assert!(b1b > 0, "G2b FAIL: byte tier blind"); + assert!( + i1b.abs() < 1e-12, + "G2b FAIL: increments see a pure reordering ({i1b:.3e})" + ); + assert!(e1b > 1e-9, "G2b FAIL: exact areas blind ({e1b:.3e})"); + assert!( + r1b > 1e-9, + "G2b FAIL: register blind to a midpoint-crossing swap ({r1b:.3e})" + ); + println!( + " G2b PASS: midpoint-crossing dataflow edit — register sees it; increments provably blind" + ); + + // G3 — the rule counterfactual below locus resolution: ONLY bytes see + // (named limit #2 — IntSub and IntAdd share the arithmetic locus). + assert!(b2 > 0, "G3 FAIL: byte tier blind to the substitution"); + assert!( + i2.abs() < 1e-12 && e2.abs() < 1e-12 && r2.abs() < 1e-12, + "G3 FAIL: a stream tier claims to see below locus resolution (i {i2:.3e} e {e2:.3e} r {r2:.3e})" + ); + println!( + " G3 PASS: rule counterfactual below locus granularity — visible ONLY at the byte tier" + ); + + // G4 — silence. + assert!( + b3 == 0 && i3.abs() < 1e-12 && e3.abs() < 1e-12 && r3.abs() < 1e-12, + "G4 FAIL: phantom distance on identity" + ); + println!(" G4 PASS: identical program reads zero on every tier"); + + println!( + "\nPROBE GREEN — visibility ladder measured on semantic program edits: \ +byte ⊇ exact-area ⊇ register ⊇ increment; the register's two blind spots \ +(locus granularity, midpoint granularity) are named, not hidden." + ); +} diff --git a/crates/ogar-r2il/src/lib.rs b/crates/ogar-r2il/src/lib.rs index c74318a8..48780ded 100644 --- a/crates/ogar-r2il/src/lib.rs +++ b/crates/ogar-r2il/src/lib.rs @@ -335,6 +335,18 @@ impl Vocabulary for R2ILVocabulary { R2ILFn::ordinal(f).and_then(|o| ARITY[o]) } + /// Wires the [`PUSHES`] column into the segmentation seam. + /// + /// Without this override, `ogar_loco::statement_bounds` refuses every + /// R2IL call as `Uncovered` — the crate shipped the pushes data (the + /// inherent [`R2ILVocabulary::pushes_result`]) but never answered the + /// trait hook that the statement walk actually reads, leaving R2IL + /// bodies lowerable but not segmentable. Found by the counterfactual + /// witness probe on its first run against a mixed core+R2IL body. + fn domain_pushes_result(&self, f: FnIndex) -> Option { + Self::pushes_result(f) + } + /// **Zero, for every R2IL opcode** — and that is a semantic statement, not /// a stub. /// @@ -722,6 +734,66 @@ mod tests { } } + /// The pushes column is WIRED into the segmentation seam — an R2IL body + /// segments into statements. The probe that found the gap: a mixed + /// core+R2IL body refused with `Uncovered(IntAdd)` because the crate + /// shipped [`PUSHES`] but never answered `domain_pushes_result`. + #[test] + fn r2il_bodies_are_segmentable() { + use ogar_loco::{Call, FunctionBody, LaneShape, statement_bounds}; + let v = ogar_loco::vocabulary::conformance::validate(R2ILVocabulary).unwrap(); + let int_add = R2ILFn::from_ordinal( + R2ILFn::MNEMONICS + .iter() + .position(|m| *m == "IntAdd") + .unwrap(), + ) + .unwrap() + .0; + let store = R2ILFn::from_ordinal( + R2ILFn::MNEMONICS + .iter() + .position(|m| *m == "Store") + .unwrap(), + ) + .unwrap() + .0; + // NUMBER, NUMBER, IntAdd (pushes), NUMBER, Store (arity 2, no push). + let body = FunctionBody::from_calls( + LaneShape::Pairs, + &[ + Call::with_value(ogar_loco::FnIndex::NUMBER, 5), + Call::with_value(ogar_loco::FnIndex::NUMBER, 3), + Call::new(int_add), + Call::with_value(ogar_loco::FnIndex::NUMBER, 9), + Call::new(store), + ], + ) + .unwrap(); + let bounds = statement_bounds(&v, &body).expect("R2IL body must segment"); + assert_eq!( + bounds.len(), + 1, + "one Store statement spanning all five calls" + ); + assert_eq!(bounds[0].call_count, 5); + // …and the silence twin: a variadic op still refuses (arity None + // short-circuits before pushes is ever consulted). + let call_other = R2ILFn::from_ordinal( + R2ILFn::MNEMONICS + .iter() + .position(|m| *m == "CallOther") + .unwrap(), + ) + .unwrap() + .0; + let bad = FunctionBody::from_calls(LaneShape::Pairs, &[Call::new(call_other)]).unwrap(); + assert!( + statement_bounds(&v, &bad).is_err(), + "variadic stays refused" + ); + } + /// Variadic opcodes are REFUSED, not reported as nullary. #[test] fn variadic_opcodes_are_refused_rather_than_called_nullary() { diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index c477b5c8..ea474274 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -377,7 +377,7 @@ fn resolve_concept_row(id: u16) -> Option<(&'static str, u16)> { mod the_canon_carries_no_palette_rows { #[test] fn no_0x17xx_row_reached_the_globally_mirrored_codebook() { - assert_eq!(crate::class_ids::ALL.len(), 98); + assert_eq!(crate::class_ids::ALL.len(), 112); for (_, id) in crate::class_ids::ALL { assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook"); } diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index 627a31d4..eeac0540 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1210,19 +1210,68 @@ const CODEBOOK: &[(&str, u16)] = &[ ("pricelist", 0x0209), ("pricelist_rule", 0x020A), ("unit_of_measure", 0x020B), - // ── 0x03XX — Ontology domain: ZERO vocabulary rows BY DESIGN ── - // Public OBO biomedical reference ontologies (MONDO disease · HPO - // phenotype · Uberon anatomy · PATO quality · RO relations). Same posture - // as the 0x07XX OSINT and 0x0EXX Genetics blocks: the domain slot is - // RESERVED (`ConceptDomain::Ontology`) so `canonical_concept_domain` - // returns a stable tag, but the concept ids are NOT minted as shared - // CODEBOOK rows — they live in the producer crate `ogar-obo` - // (`Namespace::concept_id`: mondo 0x0301 · hpo 0x0302 · uberon 0x0303 · - // pato 0x0304 · ro 0x0305). This keeps the OBO reference PLUG-AND-PLAY: - // only a consumer that deps `ogar-obo` compiles the concepts; ERP / project - // consumers (odoo-rs, openproject-nexgen-rs, …) never pull them into their - // concept space. Public reference, firewall-separated from `0x09` Health - // PHI — same reference≠PHI split as Anatomy (0x0A). Do NOT mint rows here. + // ── 0x03XX — Ontology domain ── + // + // ⊘ SUPERSEDED (operator ruling, 2026-08-22): "the domains should be minted + // in ogar-vocab". The prior note here read "ZERO vocabulary rows BY DESIGN + // … Do NOT mint rows here", with the ids held in the producers instead. It + // is reversed, not deleted — the reasoning it carried is below, and so is + // what it cost. + // + // The old posture was plug-and-play: only a consumer deping `ogar-obo` + // compiled the OBO concepts, so ERP / project consumers never pulled them + // into their concept space. What it also produced: + // + // 1. `concepts_in_domain(ConceptDomain::Ontology)` returned an EMPTY set. + // An empty set is indistinguishable from "this domain has nothing to + // reason about", and that is how it was read. + // 2. The domain had three independent mint sites and no enumeration + // spanning them — `ogar-obo::registry::OBO_CORE` (0x0301..0x0305), + // `ogar-obo::registry::META_STUDY_SPINE` (0x0340..0x0347) and + // `ogar-ro::RELATION_BODY_CONCEPT_ID` (0x0306). `META_STUDY_SPINE` was + // once minted OVER 0x0306 and nothing failed; the collision was found + // by hand-enumerating the domain during an unrelated audit. A guard + // now exists in `ogar-ro` (the only crate that can see both sides, + // since the dependency runs ogar-ro → ogar-obo), but a guard per pair + // does not scale and cannot see a site nobody thought to add. + // 3. A consumer with no enumeration to call computed the ids instead: + // `0x0300 | (ns + 1)`, right only because five variants happen to be + // declared in the order of five contiguous ids — and it yields 0x0306, + // i.e. `ogar-ro`'s relation body, for any ordinal past the end. + // + // One mint site makes (2) structurally impossible — `codebook_has_no_ + // duplicate_ids_or_zero` covers the whole domain in one table — and (1) + // and (3) stop being reachable, because there is now something to ask. + // + // The producers keep their typed surfaces (`Namespace::concept_id`, + // `RELATION_BODY_CONCEPT_ID`); those are the ergonomic reading. This table + // is the mint of record, and the agreement between them is a test, not a + // convention. + // + // Public reference, firewall-separated from `0x09` Health PHI — same + // reference≠PHI split as Anatomy (0x0A). Unchanged by this reversal. + // + // OBO core — mirrored by `ogar_obo::Namespace` / `registry::OBO_CORE`. + ("mondo", 0x0301), + ("hpo", 0x0302), + ("uberon", 0x0303), + ("pato", 0x0304), + ("ro", 0x0305), + // Relation BODIES — `ogar_ro::RELATION_BODY_CONCEPT_ID`. One slot past the + // OBO core; distinct from `ro` (0x0305), which is the RO term-node + // namespace. This is the row whose absence from any shared table let + // META_STUDY_SPINE be minted over it. + ("ro_relation_body", 0x0306), + // Meta-study spine — `ogar_obo::registry::META_STUDY_SPINE`. Deliberately + // banded at 0x0340 to sit clear of the core band's growth. + ("bfo", 0x0340), + ("cob", 0x0341), + ("iao", 0x0342), + ("obi", 0x0343), + ("obcs", 0x0344), + ("sepio", 0x0345), + ("eco", 0x0346), + ("fbbi", 0x0347), // ── 0x04XX — Weather / Atmosphere domain ── // Canonical atmospheric/grid concepts consumed by WeatherNext and the // lance-graph weather SoA bake. These are shared meanings; renderer / @@ -1923,6 +1972,43 @@ pub mod class_ids { /// `uom.uom` (`qudt:Unit`). pub const UNIT_OF_MEASURE: u16 = 0x020B; + // ── 0x03XX — Ontology domain (minted 2026-08-22, operator ruling: "the + // domains should be minted in ogar-vocab"; see the CODEBOOK block for + // the posture this reverses and what the prior split cost) ── + + /// `mondo` (`0x0301`) — MONDO disease. `ogar_obo::Namespace::Mondo`. + pub const MONDO: u16 = 0x0301; + /// `hpo` (`0x0302`) — human phenotype. `ogar_obo::Namespace::Hpo`. + pub const HPO: u16 = 0x0302; + /// `uberon` (`0x0303`) — anatomy spine. `ogar_obo::Namespace::Uberon`. + pub const UBERON: u16 = 0x0303; + /// `pato` (`0x0304`) — phenotypic quality. `ogar_obo::Namespace::Pato`. + pub const PATO: u16 = 0x0304; + /// `ro` (`0x0305`) — RO term nodes. `ogar_obo::Namespace::Ro`. + pub const RO: u16 = 0x0305; + /// `ro_relation_body` (`0x0306`) — RO relation BODIES, distinct from the + /// term-node namespace [`RO`]. `ogar_ro::RELATION_BODY_CONCEPT_ID`; the row + /// whose absence from any shared table let `META_STUDY_SPINE` be minted + /// over it once. + pub const RO_RELATION_BODY: u16 = 0x0306; + /// `bfo` (`0x0340`) — Basic Formal Ontology. Meta-study spine, banded at + /// 0x0340 to sit clear of the core band's growth. + pub const BFO: u16 = 0x0340; + /// `cob` (`0x0341`) — Core Ontology for Biology and Biomedicine. + pub const COB: u16 = 0x0341; + /// `iao` (`0x0342`) — Information Artifact Ontology. + pub const IAO: u16 = 0x0342; + /// `obi` (`0x0343`) — Ontology for Biomedical Investigations. + pub const OBI: u16 = 0x0343; + /// `obcs` (`0x0344`) — Ontology of Biological and Clinical Statistics. + pub const OBCS: u16 = 0x0344; + /// `sepio` (`0x0345`) — Scientific Evidence and Provenance Information. + pub const SEPIO: u16 = 0x0345; + /// `eco` (`0x0346`) — Evidence and Conclusion Ontology. + pub const ECO: u16 = 0x0346; + /// `fbbi` (`0x0347`) — Biological Imaging Methods Ontology. + pub const FBBI: u16 = 0x0347; + // ── 0x04XX — Weather / Atmosphere domain ── /// `weather_cell` (`0x0401`) — one dynamic atmospheric/forecast grid @@ -2247,6 +2333,20 @@ pub mod class_ids { ("pricelist", PRICELIST), ("pricelist_rule", PRICELIST_RULE), ("unit_of_measure", UNIT_OF_MEASURE), + ("mondo", MONDO), + ("hpo", HPO), + ("uberon", UBERON), + ("pato", PATO), + ("ro", RO), + ("ro_relation_body", RO_RELATION_BODY), + ("bfo", BFO), + ("cob", COB), + ("iao", IAO), + ("obi", OBI), + ("obcs", OBCS), + ("sepio", SEPIO), + ("eco", ECO), + ("fbbi", FBBI), // 0x04XX — Weather / Atmosphere ("weather_cell", WEATHER_CELL), ("weather_static_cell", WEATHER_STATIC_CELL), @@ -2385,7 +2485,7 @@ pub mod class_ids { // Pin the number here so a bump is never silent. assert_eq!( ALL.len(), - 98, + 112, "class_ids::ALL count changed — update this pin AND land the \ corresponding row in lance-graph's \ crates/lance-graph-contract/src/ogar_codebook.rs::CODEBOOK \ @@ -3204,6 +3304,20 @@ pub fn all_promoted_classes() -> Vec { pricelist(), pricelist_rule(), unit_of_measure(), + mondo(), + hpo(), + uberon(), + pato(), + ro(), + ro_relation_body(), + bfo(), + cob(), + iao(), + obi(), + obcs(), + sepio(), + eco(), + fbbi(), // 0x07XX — OSINT arm: ZERO vocabulary rows BY DESIGN (operator // ruling 2026-07-02, corrects PR #145's hallucinated // `osint_system` / `osint_person` mints); no calls follow — OGAR @@ -4897,6 +5011,143 @@ pub fn joint() -> Class { // the canonical class carries identity, never the weather payload layout. // ───────────────────────────────────────────────────────────────────── +// ───────────────────────────────────────────────────────────────────── +// 0x03XX — Ontology domain builders (public OBO biomedical reference). +// +// Reference concepts, deliberately ATTRIBUTE-FREE — the same shape as the +// 0x04XX weather cells. An OBO namespace's content is terms and edges, and +// those live in the bake (`ogar-obo`: `TermId`, the label slab, the edge +// block), not in a shared class shape. Asserting attributes here would be +// inventing a model for data this crate does not own; the mint records the +// concept's IDENTITY, which is the thing that has to be collision-free. +// ───────────────────────────────────────────────────────────────────── + +/// `mondo` (`0x0301`) — MONDO disease. +#[must_use] +pub fn mondo() -> Class { + let mut c = Class::new("Mondo"); + c.language = Language::Unknown; + c.canonical_concept = Some("mondo".to_string()); + c +} + +/// `hpo` (`0x0302`) — HPO human phenotype. +#[must_use] +pub fn hpo() -> Class { + let mut c = Class::new("Hpo"); + c.language = Language::Unknown; + c.canonical_concept = Some("hpo".to_string()); + c +} + +/// `uberon` (`0x0303`) — Uberon anatomy spine. +#[must_use] +pub fn uberon() -> Class { + let mut c = Class::new("Uberon"); + c.language = Language::Unknown; + c.canonical_concept = Some("uberon".to_string()); + c +} + +/// `pato` (`0x0304`) — PATO phenotypic quality. +#[must_use] +pub fn pato() -> Class { + let mut c = Class::new("Pato"); + c.language = Language::Unknown; + c.canonical_concept = Some("pato".to_string()); + c +} + +/// `ro` (`0x0305`) — RO term nodes. +#[must_use] +pub fn ro() -> Class { + let mut c = Class::new("Ro"); + c.language = Language::Unknown; + c.canonical_concept = Some("ro".to_string()); + c +} + +/// `ro_relation_body` (`0x0306`) — RO relation BODIES — distinct from the `ro` term-node namespace. +#[must_use] +pub fn ro_relation_body() -> Class { + let mut c = Class::new("RoRelationBody"); + c.language = Language::Unknown; + c.canonical_concept = Some("ro_relation_body".to_string()); + c +} + +/// `bfo` (`0x0340`) — Basic Formal Ontology. +#[must_use] +pub fn bfo() -> Class { + let mut c = Class::new("Bfo"); + c.language = Language::Unknown; + c.canonical_concept = Some("bfo".to_string()); + c +} + +/// `cob` (`0x0341`) — Core Ontology for Biology and Biomedicine. +#[must_use] +pub fn cob() -> Class { + let mut c = Class::new("Cob"); + c.language = Language::Unknown; + c.canonical_concept = Some("cob".to_string()); + c +} + +/// `iao` (`0x0342`) — Information Artifact Ontology. +#[must_use] +pub fn iao() -> Class { + let mut c = Class::new("Iao"); + c.language = Language::Unknown; + c.canonical_concept = Some("iao".to_string()); + c +} + +/// `obi` (`0x0343`) — Ontology for Biomedical Investigations. +#[must_use] +pub fn obi() -> Class { + let mut c = Class::new("Obi"); + c.language = Language::Unknown; + c.canonical_concept = Some("obi".to_string()); + c +} + +/// `obcs` (`0x0344`) — Ontology of Biological and Clinical Statistics. +#[must_use] +pub fn obcs() -> Class { + let mut c = Class::new("Obcs"); + c.language = Language::Unknown; + c.canonical_concept = Some("obcs".to_string()); + c +} + +/// `sepio` (`0x0345`) — Scientific Evidence and Provenance Information. +#[must_use] +pub fn sepio() -> Class { + let mut c = Class::new("Sepio"); + c.language = Language::Unknown; + c.canonical_concept = Some("sepio".to_string()); + c +} + +/// `eco` (`0x0346`) — Evidence and Conclusion Ontology. +#[must_use] +pub fn eco() -> Class { + let mut c = Class::new("Eco"); + c.language = Language::Unknown; + c.canonical_concept = Some("eco".to_string()); + c +} + +/// `fbbi` (`0x0347`) — Biological Imaging Methods Ontology. +#[must_use] +pub fn fbbi() -> Class { + let mut c = Class::new("FBbi"); + c.language = Language::Unknown; + c.canonical_concept = Some("fbbi".to_string()); + c +} + /// `weather_cell` (`0x0401`) — one dynamic atmospheric / forecast-grid cell. #[must_use] pub fn weather_cell() -> Class { diff --git a/docs/DISCOVERY-MAP.md b/docs/DISCOVERY-MAP.md index 1bf5ee4c..20440bec 100644 --- a/docs/DISCOVERY-MAP.md +++ b/docs/DISCOVERY-MAP.md @@ -2058,3 +2058,42 @@ isolation. The map's job is to keep them visible. a *mask over the activities* (one `&self` pass), never the retired actor/pump surface (`E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1`); and the 0/1/2/n distribution measurement that would calibrate `diffuse_floor`. + +- **[D-CFW-LADDER] Counterfactual visibility is a four-rung ladder over the + stored program — byte ⊇ exact-area ⊇ register ⊇ increment — measured on + SEMANTIC loco/r2il edits, with the register's two blind spots named** — + `[G]` (CODED, 2026-08-31) — home: + `crates/ogar-r2il/examples/probe_counterfactual_witness_kernel.rs` — + depends: `ogar-loco` `statement_bounds` (the R5 maskable unit), + `R2ILVocabulary`, `CallMask`/`project` (#288's inline-words form), + lance-graph E-MONOTONE-STREAM-LEVEL2-IS-DISCRIMINATION-NOT-MAGNITUDE-1 + (the register-cast construction this probe re-grounds). **What changed + against the predecessor:** the lance-graph stream probe generated witness + events from a seeded RNG and called a seed-preserving reshuffle an + intervention — measuring that a mechanical dummy is mechanically + invisible. Here the stream source is a real `FunctionBody` (24 statements + of the R2IL store idiom, mixed shared-core + domain calls), **the Markov + window IS the loco statement** (intra-window order = the stack + discipline), and each counterfactual is a statement-local program edit + proven confined under the masked lane projection (outside the statement's + `CallMask` the two slabs project identically; inside, they differ). + Measured: CF-1a (operand swap within the operand run) — byte 2 / + exact-area 2.4e-7 / register 0 / increment 0: the register's 1-bit + orientation is MIDPOINT-granular and honestly blind to sub-midpoint order + (blind spot #1; the probe's first gate expected otherwise and was + falsified). CF-1b (midpoint-crossing swap) — register 3.1e-7 sees it, + increments exactly 0 (provably blind to any pure reordering). CF-2 + (IntSub→IntAdd) — visible ONLY at the byte tier: both ops share the + arithmetic locus, so a rule counterfactual below locus granularity never + reaches any stream tier (blind spot #2). CF-3 identity — all four tiers + exactly 0. G0 anchors the inline Goursat solver at 8.0e-5 vs the + I₀(2√⟨u,v⟩) closed form. **Crate fix shipped with the probe:** ogar-r2il + declared `PUSHES` but never answered `Vocabulary::domain_pushes_result`, + leaving R2IL bodies lowerable but UNSEGMENTABLE (`Uncovered(IntAdd)` on + first contact); the override now delegates to the table, with the + segmentable + variadic-still-refused test pair. **Consequence:** at + register resolution, counterfactual reasoning over stored thinking is a + DETECTION instrument for dataflow-order interventions at or above + statement-midpoint scale; rule substitutions within a locus class and + sub-midpoint reorderings require the byte tier (the program itself) or a + finer orientation encoding — a deliberate future mint, not a patch.