From 242c025a37de3c3594a962b4e2930a58f2261e0c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 06:31:53 +0000 Subject: [PATCH 1/2] Co-research map: Simpson's paradox as an epistemic signal Exploration map from the co-research council. Finds that NARS revision of stratum beliefs equals the union recount, so it reproduces Simpson's reversal, and that NAL's overlap guard does not stop it. Lists the seams that keep the revision gate pure set algebra and nine pre-registered probes. Ratifies nothing. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh --- ...2026-10-17-coresearch-simpson-epistemic.md | 125 ++++++++++++++++++ .claude/board/entries/README.md | 3 +- 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 .claude/board/entries/2026-10-17-coresearch-simpson-epistemic.md diff --git a/.claude/board/entries/2026-10-17-coresearch-simpson-epistemic.md b/.claude/board/entries/2026-10-17-coresearch-simpson-epistemic.md new file mode 100644 index 000000000..82c925cfa --- /dev/null +++ b/.claude/board/entries/2026-10-17-coresearch-simpson-epistemic.md @@ -0,0 +1,125 @@ +# 2026-10-17 — Co-research: Simpson's paradox as an epistemic signal + +**Status:** OPEN. This is an exploration map from the co-research council +(`.claude/agents/coresearch-council.md`). It ratifies nothing; the operator picks +what to take forward. + +## Question + +Should Simpson's paradox (an association that reverses when the population is +stratified) be an epistemic signal for NARS revision? If so, in what form and +where? The question opened when D-CGW-1 (#1594) removed the per-edge Simpson +carrier (CE64 bits 43..45, `detect_simpsons_paradox`). + +## Premise gate: the question was split + +The premise check (verified against `certification.rs`) found four concepts +under one name: + +| concept | home today | +|---|---| +| stratification reversal (Simpson proper) | `lance-graph-contract/src/certification.rs`: `strata: [M;4]`, rung 3 `Supports` = "in every declared stratum exposure does not lower the outcome". The Simpson fixture certifies `Related`, not `Supports`. | +| observational-vs-trial disagreement | planner `Operation::ConfounderCheck`. `CausalMask::simpsons_paradox_risk` is a misnomer: it detects confounding. | +| evidential overlap | revision gate Echo / ClosedCycle (G1, G2). Not Simpson. | +| reference class | no carrier | + +## Load-bearing findings + +1. **NARS revision is the amalgamation Simpson warns about.** + - `isa::truth` revision computes `f = (f1·w1 + f2·w2)/(w1+w2)`. With `w = n` (c = n/(n+1)), revising stratum beliefs equals the union recount. + - On the Simpson fixture: S0 exposed 6/8 vs unexposed 2/2, S1 exposed 0/2 vs unexposed 1/8. Both strata lower the outcome. Revised: exposed 0.6, unexposed 0.3, so the outcome is raised. + - At u8 codes the result is 0.601 vs 0.299, the same direction (hand-computed; probe P-pool pins it). + - Mantel-Haenszel weighting would not reverse; NAL revision does. + - Sources: NAL draft Def 3.7 / §4.1 (SECTION-READ); Blyth via Vellaisamy 1403.6329 (READ-IN-FULL). +2. **Nothing in NARS stops it.** + - NAL and ONA guard revision only by evidential overlap. Disjoint strata pass that check trivially. + - ONA has no subgroup or reference-class check (ONA `Inference.c`, SECTION-READ, MIT). +3. **The data cannot pick a side.** + - Pooled vs stratified is decided by the causal graph, not the counts (Pearl R-414 SECTION-READ; Lindley-Novick and Hernán SECONDHAND). + - So the only admissible reaction is Suspend or refusal. Demoting is already rejected: production `pearl` never demotes an intervention-certified `Causes`. +4. **"Pool only when every stratum agrees" is not sufficient.** + - The reversal also runs the other way: all strata raise while the marginal lowers, when exposure shares differ across strata. + - Sure-thing is about the causal effect, not the marginal association. Admission also needs the balance condition (row 3). +5. **Everything is presence bits and popcount.** + - `compare` decides direction by `popcount(Y∧a)·|b|` vs `popcount(Y∧b)·|a|`, with no division and no float. + - Each stratum costs 4 popcounts. The folds are generic over `PopulationMask` (u64 .. `[u64;1024]`). + +## Crosswalk and verdicts + +| # | idea | relation | firewall | verdict | +|---|---|---|---|---| +| 1 | Stratum-aware pool guard: refuse/suspend revising beliefs of one question from different declared strata when a reversal is present | NEW | CONFLICT, fits via SEAM-C2M + SEAM-STRATUM-PROVENANCE | **PROBE** (P-pool, P-guard) | +| 2 | Admit pooling at `Supports` **and** balanced exposure (row 3) | PARTIAL | CONFLICT, fits via SEAM-C2M | **PROBE** (with row 3) | +| 3 | Mask-only collapsibility pre-check: `|E∧Sᵢ|·|¬E| == |¬E∧Sᵢ|·|E|` for all i, no outcome counts | NEW | CONFLICT, lives in `certification.rs` as a fold beside `compare` | **PROBE** (sweep the D-GSO-7a 2,500-model family; sufficient only, rarely fires on real data) | +| 4 | Explicit reversal report: every reversing stratum, not first-fail; the caller sets the pooled claim's bit in `contradictions` → ContradictionPreserved → Suspend | PARTIAL | CONFLICT (nearest PASS), via SEAM-C2M | **PROBE**; trap below | +| 5 | Reference class by specificity: narrowest declared stratum with adequate statistics | NEW | CONFLICT, via SEAM-ADMISSIBLE-MASK | **PARK** behind row 10 (collider counter-case required) | +| 6 | Specious-rule filter in arm-discovery (Hämäläinen & Webb 1709.03915): leverage given X and ¬X, integer δ prefilter, top-K only | NEW | PASS, conditional on integer form and declared X | **PROBE** | +| 7 | Candidate strata from a concept lattice (FCA) | NEW | TRAP without SEAM-PROPOSE-RESEAL | **PARK** (proposer only; re-seal before certifying) | +| 8 | Stratum as a coordinate range (layout sorted by stratum; popcount over slices, no AND with S) | PARTIAL | PASS (one sort order per layout; overlapping strata stay AND remainder) | **PROBE** (parity vs AND; `fold_ranges` is quack-private, verify first) | +| 9 | Rename `simpsons_paradox_risk` / `Measured::confounded` to say obs-vs-trial disagreement | ALREADY-HAVE (misnamed) | PASS | **ADOPT-NOW** (~12 sites, no production consumer) | +| 10 | Enforce strata declared before the seal (mirror "receipt after seal does not count") | PARTIAL | PASS | **ADOPT-NOW** as a test-first prerequisite for 1, 5, 6, 7 | +| 11 | Per-edge Simpson field | — | TRAP (A1) | SKIP | +| 12 | Demote certification on reversal | — | TRAP (prior ruling D-PEARL-PROD-0) | SKIP | +| 13 | Partial pooling (hierarchical, MCMC) | — | TRAP (float, A5) | SKIP | +| 14 | Dempster-Shafer combination | lesson already in gate tension | import TRAP (A5) | SKIP | + +## The seams (firewall critic), all keeping the gate pure set algebra + +- **SEAM-C2M (certification→mask, two-pass).** + - Certification folds in the counting layer. Its verdict leaves only as a mask and enters `revise` as `contradictions` or claims. + - The gate never calls certification, and the two modules do not import each other. + - The adapter lives in the planner or in a free contract fn outside `GadamerRevision`. +- **SEAM-STRATUM-PROVENANCE.** + - Stratum of origin travels in the existing claim/root masks, scoped by `question` (G7). + - It is a fourth axis, separate from evidential independence (A6, PR #854's unbuilt `EvidentialBase`). + - The gate gains no stratum field. +- **SEAM-ADMISSIBLE-MASK.** "Adequate statistics" is pre-reduced by the counting layer to an admissible-class mask. +- **SEAM-PROPOSE-RESEAL.** Data-derived strata only seed a fresh sealed model. + +**Index-space warning.** Certification masks index units (rows); gate masks index claims. Only the pooled claim's bit crosses, never the stratum masks themselves. + +**Trap inside row 4.** In the gate, contradictions plus any new root give HorizonFusion (IncreaseEligible). More pooled evidence of the same kind must not count as that root. Only stratum-qualified evidence counts: an intervention, or a new declared stratification. Without that restriction G2 holds formally and is violated in meaning. Probe P-guard checks this. + +## Probes (pre-registered; each has a silence twin) + +- **P-pool (S).** Revise the Simpson fixture's two stratum beliefs with `isa::truth` (f32 and u8). + - Kill row 1: revised exposed ≤ unexposed. + - Expected: 0.6 vs 0.3, reversed, equal to the union recount. + - Twins: the robust fixture (one stratum) must not reverse; two strata that both raise with balanced exposure must admit. +- **P-guard (M).** Gate `revise` with the bridge (rows 1 + 4). + - Simpson → Suspend; same-stratum disjoint evidence → IncreaseEligible. + - Kill: the decision needs a count in the gate, or fires on same-stratum operands, or a pooled-kind "new root" lifts the Suspend. +- **P-balance (S/M).** The row-3 pre-check over the 2,500-model family. + - Kill: "collapsible" on any reversing model. + - Twin: equal exposure share per stratum → collapsible and no reversal. +- **P-report (M).** Add a third reversing stratum. + - Kill: the report is not exactly {S0, S1, S2}. + - Twin: two agreeing non-empty strata → empty report, no Suspend. +- **P-collider (M, row 5).** Stratum S = E ∨ Y over 20 units with E independent of Y. + - Kill: specificity picks the S-belief. + - Twin: a declared confounder stratum must win. +- **P-specious (S, row 6).** RowMasks over the Simpson units, items E, Y, S0. + - Kill: E→Y not flagged, or the δ prefilter drops a rule the full test flags. + - Twin: a robust rule is not flagged. +- **P-range (M, row 8).** Sort the fixture by stratum. + - Kill: range counts ≠ AND counts, or not ns-range on `[u64;1024]` at v4. +- **P-seal (S/M, row 10).** Union a mask into `strata[1]` after the seal. + - Kill: certification changes. + - Twin: pre-seal strata still certify Simpson as `Related`. +- **P-dredge (L, row 7).** Lattice strata on noise must not find reversals above a shuffled-label null. + +## Not searched + +- Multi-level drill-down beyond one partition: Fabris-Freitas only at ABSTRACT-ONLY. `[M;4]` is a single-level scope limit. +- Pei Wang's own writing on reference class in NARS: not found. +- Commercial OLAP reversal warnings: none found. +- Read grades: + - Kyburg was read only through Loui. + - Hernán, Touretzky/Horty and Greenland are SECONDHAND or ABSTRACT-ONLY; they need a read before citing in a ratified doc. + - VLDB 2511.00748 (20–48% redundant paradoxes): ABSTRACT-ONLY. + +## OPEN + +- Which ideas to take forward: operator decision. +- D-GSO-7a's "strata declared before data" and D-PEARL-PROD-0's "which contradiction lowers a certification" remain open; rows 10 and 4 address them. +- Stratum-of-origin vs the unbuilt `EvidentialBase` (#854): kept as separate axes, design not started. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 52cd24fd3..a64bf616a 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,10 +25,11 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -364 entries, 2026-08-06 .. 2026-10-17. +365 entries, 2026-08-06 .. 2026-10-17. | date | entry id | finding | file | |---|---|---|---| +| 2026-10-17 | `coresearch-simpson-epistemic` | | [2026-10-17-coresearch-simpson-epistemic.md](2026-10-17-coresearch-simpson-epistemic.md) | | 2026-10-17 | `ce64-one-layout-moore-synapse-membrane` | | [2026-10-17-ce64-one-layout-moore-synapse-membrane.md](2026-10-17-ce64-one-layout-moore-synapse-membrane.md) | | 2026-10-16 | `wcoj-probes-1-3` | | [2026-10-16-wcoj-probes-1-3.md](2026-10-16-wcoj-probes-1-3.md) | | 2026-10-16 | `wcoj-closing-atoms-council` | | [2026-10-16-wcoj-closing-atoms-council.md](2026-10-16-wcoj-closing-atoms-council.md) | From 4241f541b842ac98e02f4662ff8edcb08acb5682 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 06:49:35 +0000 Subject: [PATCH 2/2] =?UTF-8?q?test(planner):=20P-pool=20probe=20=E2=80=94?= =?UTF-8?q?=20NAL=20revision=20of=20stratum=20beliefs=20is=20the=20union?= =?UTF-8?q?=20recount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three cases over isa::truth revision (f32) and CausalEdge64::revision (u8): the certification Simpson fixture reverses (0.6 vs 0.3 against both strata); balanced agreeing strata do not reverse (silence twin); agreeing but exposure-unbalanced strata still reverse, so 'pool only at Supports' is not sufficient on its own. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh --- .../tests/simpson_revision_pooling.rs | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 crates/lance-graph-planner/tests/simpson_revision_pooling.rs diff --git a/crates/lance-graph-planner/tests/simpson_revision_pooling.rs b/crates/lance-graph-planner/tests/simpson_revision_pooling.rs new file mode 100644 index 000000000..5174ce384 --- /dev/null +++ b/crates/lance-graph-planner/tests/simpson_revision_pooling.rs @@ -0,0 +1,167 @@ +//! Probe P-pool (coresearch 2026-10-17, entry +//! `2026-10-17-coresearch-simpson-epistemic.md`): NAL revision of stratum +//! beliefs equals the union recount, so it reproduces Simpson's reversal. +//! +//! A stratum belief "exposure → outcome" carries `f = hits / n` and +//! `c = n / (n + 1)`, so its evidence weight `c / (1 - c)` is `n`. Revision +//! pools `f·w`, the positive evidence, which is the union recount. Nothing in +//! the revision arithmetic looks at strata; the probe pins that the reversal +//! happens, at f32 and through the packed u8 register, and pins the two twins +//! that bound any guard built on it. + +use causal_edge::isa::truth; +use causal_edge::CausalEdge64; + +/// One stratum cell: `n` units, `hits` of them with the outcome. +#[derive(Clone, Copy)] +struct Cell { + n: u32, + hits: u32, +} + +/// One stratum: the exposed and the unexposed cell. +#[derive(Clone, Copy)] +struct Stratum { + exposed: Cell, + unexposed: Cell, +} + +fn f_of(c: Cell) -> f32 { + c.hits as f32 / c.n as f32 +} + +fn c_of(c: Cell) -> f32 { + c.n as f32 / (c.n as f32 + 1.0) +} + +/// Direction by exact integer cross-multiplication, as `certification::compare` +/// does: `true` when the outcome is more frequent among the exposed. +fn raises(e: Cell, u: Cell) -> bool { + u64::from(e.hits) * u64::from(u.n) > u64::from(u.hits) * u64::from(e.n) +} + +/// Revise the stratum beliefs of one arm at f32. +fn revise_f32(cells: &[Cell]) -> (f32, f32) { + let mut acc = (f_of(cells[0]), c_of(cells[0])); + for &c in &cells[1..] { + acc = truth::revision(acc.0, acc.1, f_of(c), c_of(c)).expect("evidence"); + } + acc +} + +fn pack(v: f32) -> u8 { + (v.clamp(0.0, 1.0) * 255.0).round() as u8 +} + +/// Revise the stratum beliefs of one arm through the packed CE64 register. +fn revise_u8(cells: &[Cell]) -> u8 { + let edge = |c: Cell| { + let mut e = CausalEdge64(0); + e.set_frequency_u8(pack(f_of(c))); + e.set_confidence_u8(pack(c_of(c))); + e + }; + let mut acc = edge(cells[0]); + for &c in &cells[1..] { + acc = acc.revision(edge(c)); + } + acc.frequency_u8() +} + +fn union(cells: &[Cell]) -> Cell { + cells.iter().fold(Cell { n: 0, hits: 0 }, |a, c| Cell { + n: a.n + c.n, + hits: a.hits + c.hits, + }) +} + +struct Outcome { + every_stratum_raises: Vec, + union_raises: bool, + revised_f32: (f32, f32), + revised_u8: (u8, u8), +} + +fn run(strata: &[Stratum]) -> Outcome { + let e: Vec = strata.iter().map(|s| s.exposed).collect(); + let u: Vec = strata.iter().map(|s| s.unexposed).collect(); + Outcome { + every_stratum_raises: strata + .iter() + .map(|s| raises(s.exposed, s.unexposed)) + .collect(), + union_raises: raises(union(&e), union(&u)), + revised_f32: (revise_f32(&e).0, revise_f32(&u).0), + revised_u8: (revise_u8(&e), revise_u8(&u)), + } +} + +/// The `certification` Simpson fixture: S0 exposed 6/8, unexposed 2/2; +/// S1 exposed 0/2, unexposed 1/8. +fn simpson() -> [Stratum; 2] { + [ + Stratum { + exposed: Cell { n: 8, hits: 6 }, + unexposed: Cell { n: 2, hits: 2 }, + }, + Stratum { + exposed: Cell { n: 2, hits: 0 }, + unexposed: Cell { n: 8, hits: 1 }, + }, + ] +} + +#[test] +fn revising_stratum_beliefs_is_the_union_recount_and_reverses() { + let o = run(&simpson()); + // Exposure lowers the outcome in every stratum... + assert_eq!(o.every_stratum_raises, [false, false]); + // ...and raises it in the union recount (6/10 vs 3/10). + assert!(o.union_raises); + // f32 revision equals the union recount exactly. + assert!((o.revised_f32.0 - 0.6).abs() < 1e-6, "{:?}", o.revised_f32); + assert!((o.revised_f32.1 - 0.3).abs() < 1e-6, "{:?}", o.revised_f32); + // The packed register reverses too. + assert!(o.revised_u8.0 > o.revised_u8.1, "{:?}", o.revised_u8); +} + +/// Silence twin: both strata raise and exposure is spread evenly across +/// them, so pooling must agree with the strata. +#[test] +fn balanced_strata_that_agree_do_not_reverse() { + let o = run(&[ + Stratum { + exposed: Cell { n: 5, hits: 4 }, + unexposed: Cell { n: 5, hits: 2 }, + }, + Stratum { + exposed: Cell { n: 5, hits: 3 }, + unexposed: Cell { n: 5, hits: 1 }, + }, + ]); + assert_eq!(o.every_stratum_raises, [true, true]); + assert!(o.union_raises); + assert!(o.revised_f32.0 > o.revised_f32.1, "{:?}", o.revised_f32); + assert!(o.revised_u8.0 > o.revised_u8.1, "{:?}", o.revised_u8); +} + +/// Counter-case for "pool only at Supports": every stratum raises, but the +/// exposed sit mostly in the low-outcome stratum, so the pool lowers. Agreement +/// alone does not license pooling; exposure balance is needed too. +#[test] +fn agreeing_but_unbalanced_strata_still_reverse() { + let o = run(&[ + Stratum { + exposed: Cell { n: 90, hits: 18 }, + unexposed: Cell { n: 10, hits: 1 }, + }, + Stratum { + exposed: Cell { n: 10, hits: 9 }, + unexposed: Cell { n: 90, hits: 72 }, + }, + ]); + assert_eq!(o.every_stratum_raises, [true, true]); + assert!(!o.union_raises); + assert!(o.revised_f32.0 < o.revised_f32.1, "{:?}", o.revised_f32); + assert!(o.revised_u8.0 < o.revised_u8.1, "{:?}", o.revised_u8); +}