Skip to content

Commit 2240528

Browse files
committed
jc: corrective slice — fix omega sign erasure + R-squared scale dependence
External review of the merged #887 found two P1 numerical defects. Both were REPRODUCED before fixing; both now carry regression tests. 1. omega_total erased loading SIGNS. The triad identity yields lambda_i^2, hence only |lambda_i|, and the code took the positive root for every item. omega depends on (sum lambda)^2, where a negatively-keyed item must SUBTRACT. On an exact signed-congeneric fixture (loadings [+1,-1,+1], equal orthogonal residuals) the true value is 0.25 and the shipped code reported 0.75. Fixed by recovering signs from the covariance row: under one factor sign(sigma_ij) = s_i*s_j, so anchor s_0 = +1 and read the rest off row 0. The anchor is free because the model is identified only up to a global flip and (sum lambda)^2 is invariant to one — asserted as its own test. Sign CONSISTENCY is now also checked, which is a genuine partial one-factor structure test. At k=3 it is provably subsumed by the existing negative- lambda^2 guard (the sign product of the single triad is -1 exactly when the pattern is inconsistent); it only becomes reachable at k>=4 where lambda^2 is averaged. The can-it-fire test therefore uses a k=4 fixture and pre-registers that every lambda^2 and psi is non-negative, so only the sign guard can be what rejects. 2. multiple_r_squared was SCALE-DEPENDENT. Normal equations were built on raw columns and a pivot was called singular below an ABSOLUTE 1e-12 — a statement about units, not rank. Measured: an exact linear fit at 1e-8 magnitude returned None, while the identical relationship at unit scale returned 1.0. Fixed by centering the response and every predictor and scaling predictors to unit norm. R-squared is affine-invariant so no correct answer changes, the rank test becomes relative (diagonal is exactly 1), and centering absorbs the intercept — so "collinear with the intercept" correctly reappears as two identical centered columns. The [0,1] clamp is now bounded to a rounding-scale band: a materially out-of-range value means the solve failed and surfaces as None rather than as a plausible 0 or 1. Also corrected / added: - omega's doc claimed rejection means "the congeneric model does not fit". It checks three NECESSARY conditions and cannot certify a one-factor matrix; the vanishing-tetrad constraints are not tested and k>=4 misfit can still return a number. Doc now states what is and is not verified. - BinaryAssociation + binary_association: counts, BOTH marginals, p_o/p_e alongside kappa and phi. The shipped phi doc said marginals are required to interpret it while the function returned a lone scalar; that was an internal contradiction. - kr20(&[Vec<bool>]): the dichotomous naming surface C2 asked for and C1b did not ship. Enforces binary input by type, delegates arithmetic to cronbach_alpha, does not duplicate it. - betacf now returns Option and reports non-convergence instead of presenting the 300th iterate as a p-value; reg_inc_beta uses ln_1p(-x) near x=1 and clamps only a rounding-scale excursion. Board corrections in the same commit: - The "unblocks D3's fusion falsifier" claim was WRONG and contradicted the plan's own C3 (validity requires an external criterion). kappa is chance- corrected agreement under the observed marginals; it measures overlap, not incremental value. D3 is split: D3a (descriptive overlap, unblocked) and D3b (held-out fusion falsifier, BLOCKED on an external criterion and a criterion-appropriate scoring rule). - C2's Spearman wording: on non-constant binary variables the average-rank transform is affine, so rho is REDUNDANT with phi, not degenerate. - C4's Jirak scope: jc::stats p-values are classical independent-sample p-values; jirak.rs is a fingerprint-specific empirical probe, not a general uncertainty engine. A dependent-cohort claim needs its own justified dependence model. - The exact-zero epiphany was too broad. Narrowed to theoretically non-negative FITTED quantities needing a scale-aware tolerance; it is not a licence to stop testing zero, since a zero determinant or zero within-group variance is a real degeneracy. - New epiphany E-THE-CROSS-IDENTITY-SUITE-INHERITED-MY-BLIND-SPOT-1: five independent cross-identities all passed while both defects were live, because every omega fixture had positive loadings and every R-squared fixture was O(1). A cross-identity suite inherits the blind spots of its fixtures silently, since every check reports green. The practice that would have caught both: name the invariances the estimator should have (affine rescaling, global sign flip) and write one test per dimension. 116 lib tests + 13 doctests green; stats.rs clippy-clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3RyLEbuNSHxxB3NTTrGki
1 parent 6b975b6 commit 2240528

4 files changed

Lines changed: 587 additions & 47 deletions

File tree

‎.claude/board/EPIPHANIES.md‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
## 2026-08-04 — E-THE-CROSS-IDENTITY-SUITE-INHERITED-MY-BLIND-SPOT-1 — five independent cross-checks all passed while ω was inflated 3× and R² was unit-dependent
2+
3+
**Status:** FINDING (both defects reproduced, fixed, regression-tested). **Confidence:** High — measured before and after. Found by external review of merged code, not by the suite that shipped with it.
4+
5+
**What the suite could not see.** D-KIA-C1b's headline discipline was that every new estimator is cross-checked against an *independently computed* quantity — φ vs `pearson`, R² vs `pearson²`, η² vs R² on a dummy, η² vs `t²/(t²+df)`, ω vs α. All five held. Two P1 defects were live the whole time:
6+
7+
- **ω erased loading signs.** The triad identity returns `λ_i²`, so the positive root was taken for every item; ω uses `(Σλ)²`, where a negatively-keyed item must *subtract*. On a signed fixture: **0.75 reported against a true 0.25**.
8+
- **R² was unit-dependent.** Normal equations on raw columns plus an *absolute* `1e-12` pivot cutoff — a statement about units, not rank. An exact linear fit at 1e-8 magnitude returned **`None`**; the identical relationship at unit scale returned **1.0**.
9+
10+
**Why the cross-identities were blind, and this is the transferable part.** A cross-identity checks that two computations *agree*, and agreement is only informative over the region the fixtures span. **Every ω fixture I wrote had all-positive loadings; every R² fixture was O(1).** The identities were not weak — they were evaluated at a single point of a space with unexplored dimensions. `ω = α` under tau-equivalence is true whether or not signs are handled, because tau-equivalent items are all positively keyed. `R² = pearson²` is true at any scale the guard happens to admit.
11+
12+
**So the rule is not "add more cross-checks."** It is: **name the parameter space of the estimator, then confirm each dimension is actually varied by some fixture.** For these two the dimensions were obvious in hindsight and absent in fact — *sign* of the loadings, *magnitude* of the predictors. A cross-identity suite inherits the blind spots of the fixtures it runs on, and inherits them silently, because every check reports green.
13+
14+
**The concrete practice, cheap enough to be worth doing:** for any numeric estimator, before claiming coverage, list the transformations under which the result should be **invariant** (affine rescaling, global sign flip, permutation, unit change) and the ones under which it should **change**, and write one test for each. Those tests found both defects immediately once written, and one of them — `omega_is_invariant_to_a_global_sign_flip` — is now the thing that proves the *fix* introduced no new bias.
15+
16+
**Second lesson, on the earlier self-congratulation.** #887's write-up said the doc examples caught two defects the unit tests missed, and drew the moral that readable examples sample the degenerate case. True — but incomplete, and I stopped there. The fuller statement is that **fixtures are a sample of the input space and every suite is only as good as that sample's coverage**; doc examples happened to cover *one* uncovered corner. Treating that as the lesson rather than as an instance is why the next two corners stayed uncovered until someone else looked.
17+
118
## 2026-08-04 — E-A-STATISTIC-THAT-RETURNS-ZERO-FOR-UNDEFINED-CANNOT-FAIL-VISIBLY-1 — consolidating a utility does nothing while 56 copies with a *weaker contract* survive
219

320
**Status:** FINDING (measured workspace-wide at `a9f813c`). **Confidence:** High — a grep census, reproducible. Filed as `TD-STATS-DEGENERACY-CONTRACT-DIVERGENCE`; **not paid** in this pass.
@@ -18,7 +35,7 @@
1835

1936
**The defect.** `omega_total` guards against a **Heywood case** — estimated common variance exceeding an item's total variance, `ψ_i = σ_ii − λ_i² < 0`, which means the single-factor model does not fit. Written as `if psi < 0.0 { return None }`, that guard **rejects the best-fitting input there is**: items that are exact multiples of one factor have `ψ = 0` in real arithmetic and land a few ulps *either side* of zero in f64. The perfect model was reported as misfit. Fix: a tolerance **relative to the item's own variance** (`1e-9 · max(|σ_ii|, 1)`), then clamp — only a violation larger than rounding is a real one.
2037

21-
**The generalisation, which is the reusable part.** A guard testing a quantity that is **exactly zero at the ideal** must never use an absolute `< 0` (or `== 0`) comparison. The failure is invisible on ordinary noisy inputs — where `ψ` is comfortably positive — and appears **only on the cleanest possible data**, which is exactly the case a doc example or a smoke test reaches for. Sibling instances to check for: any `variance < 0`, `residual < 0`, `determinant == 0`, or `SS_within <= 0` test in a fitting path.
38+
**The generalisation, which is the reusable part** *(narrowed 2026-08-04 after external review — the first statement was too broad).* The rule is about **theoretically non-negative FITTED quantities**: a residual variance, an estimated error term, a sum of squared residuals. Such a quantity must distinguish **rounding-scale negativity** (accept, clamp) from a **material model violation** (reject) using a SCALE-AWARE tolerance — never a bare `< 0`. It is **not** a licence to stop testing zero: a zero determinant may genuinely mean singularity, zero within-group variance genuinely leaves `F` undefined, and zero variance genuinely leaves `t` undefined. Those are real degeneracies to reject, not rounding to absorb. The earlier phrasing — *"a quantity exactly zero at the ideal must never use an absolute comparison"*, with determinants and `SS_within` listed as siblings — invited exactly that misreading. The failure is invisible on ordinary noisy inputs — where `ψ` is comfortably positive — and appears **only on the cleanest possible data**, which is exactly the case a doc example or a smoke test reaches for. Sibling instances to check for: any `variance < 0`, `residual < 0`, `determinant == 0`, or `SS_within <= 0` test in a fitting path.
2239

2340
**The second defect, same commit, different lesson.** The `multiple_r_squared` doc example used predictors `a` and `a + 1` and asserted `R² = 1`. It returned `None` — **correctly**: the design carries an intercept, so `[1, a, a+1]` is rank-deficient. Here the *example* was wrong and the code was right. A duplicated column is the collinearity everyone remembers; **collinearity with the intercept is the one that gets written by accident**, because the two predictor columns are visibly different.
2441

‎.claude/board/STATUS_BOARD.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ Plan: `.claude/plans/kanban-64k-inverted-awareness-v1.md` (operator anchors a/b;
66
|---|---|---|---|---|
77
| D-KIA-0 | jc capability map + dichotomous-statistics decision note (phi/KR-20/kappa naming; Spearman dropped at view 2) | lance-graph | Queued | plan W0 |
88
| D-KIA-A1 | ⊘ RESCOPED 2026-08-04 (E-ACTOR-IS-NOT-THE-PHASE-PATH-1): #879 is the complete phase-progression path; KanbanActor has no assigned architectural responsibility (legacy compatibility code). SHIPPED: held-owner reschedule/wake. OPEN: run_cycle drained-writer retry guard; missing-owner counter in cognitive_pass | lance-graph | Queued | plan W1 |
9-
| D-KIA-C1b | jc additive-only extension: kappa + McDonald's omega + r-family effect size (R/R-squared, eta-squared = explained variance) + t-test (t/df/p) + a named phi wrapper. Cohen's d explicitly OUT — calculated separately if ever wanted. HARD CONSTRAINT: additive only — pearson/spearman/cronbach_alpha/icc keep their arithmetic, signature and semantics; any diff changing an existing jc statistic is an automatic reject. ONE sanctioned edit: widening reliability.rs private helpers (mean/all_finite/average_ranks/pop_var) to pub(crate) for reuse, visibility only, no body change. C1 audit found phi = pearson-on-binaries (already present in substance) and KR-20 = alpha-on-dichotomous (naming only); kappa absent = the real gap. SHIPPED as crates/jc/src/stats.rs: cohen_kappa, omega_total, phi, multiple_r/multiple_r_squared, eta_squared, t_test_one_sample/paired/welch/student, anova_one_way; 31 new tests (107 lib + 11 doctests green), clippy-clean. Existing-file diff is visibility-only (mean/all_finite -> pub(crate); average_ranks/pop_var NOT widened, unused). Unblocks D3's fusion falsifier | lance-graph | In PR | plan W0/C1b |
9+
| D-KIA-C1b | jc additive-only extension: kappa + McDonald's omega + r-family effect size (R/R-squared, eta-squared = explained variance) + t-test (t/df/p) + a named phi wrapper. Cohen's d explicitly OUT — calculated separately if ever wanted. HARD CONSTRAINT: additive only — pearson/spearman/cronbach_alpha/icc keep their arithmetic, signature and semantics; any diff changing an existing jc statistic is an automatic reject. ONE sanctioned edit: widening reliability.rs private helpers (mean/all_finite/average_ranks/pop_var) to pub(crate) for reuse, visibility only, no body change. C1 audit found phi = pearson-on-binaries (already present in substance) and KR-20 = alpha-on-dichotomous (naming only); kappa absent = the real gap. SHIPPED as crates/jc/src/stats.rs: cohen_kappa, omega_total, phi, multiple_r/multiple_r_squared, eta_squared, t_test_one_sample/paired/welch/student, anova_one_way; 31 new tests (107 lib + 11 doctests green), clippy-clean. Existing-file diff is visibility-only (mean/all_finite -> pub(crate); average_ranks/pop_var NOT widened, unused). Unblocks D3a (overlap MEASUREMENT) — NOT a fusion claim: kappa is chance-corrected agreement under the observed marginals and says nothing about incremental value, so fusion still needs D3b's external criterion per the plan's own C3. Corrective slice (external review): omega sign-erasure + R-squared scale-dependence fixed; BinaryAssociation/kr20 added | lance-graph | Shipped (#887) + corrective slice | plan W0/C1b |
1010
| D-KIA-A2 | parallelism falsifier (protocol pre-registered: median-of-5, >=2x at >=4k owners, +/-10% stay-silent; kill = regrade claim (a)) | lance-graph | Queued | plan W2 |
1111
| D-KIA-B1 | catalog binary-range criterion contract type + generalized catalog-mirror drift guard | lance-graph | Queued | plan W3 |
1212
| D-KIA-C5 | cohort-statistic witness type under the ELEVATED carve-out + held-out anti-circularity gate | lance-graph | Queued | plan W4 |
1313
| D-KIA-D1 | observer/observed as two Locus categories over one arena (cheapest-first) | lance-graph | Queued | plan W5 |
1414
| D-KIA-C2 | Name the dichotomous statistics correctly (Pearson->phi, alpha->KR-20, kappa NOT a renamed ICC, Spearman dropped on binaries). AUDIT RESULT 2026-08-04: the jc reliability battery has exactly 4 consumers (style_table_agreement, rung_divergence_reliability, partof_isa_vs_palette256, l9_loci_real_text) and NONE is dichotomous — style columns, rung levels 1-10, palette/taxonomy distances, i4 loci offsets are all continuous/ordinal, so Pearson/alpha/ICC are correctly named at every existing call site and there is ZERO rename work today. The discipline binds PROSPECTIVELY at the first binary-criteria witness (D3). Surfaced instead: TD-STATS-DEGENERACY-CONTRACT-DIVERGENCE | lance-graph | Audited (no rename work; binds at D3) | plan W0/C2 |
15-
| D-KIA-D3 | Horizontverschmelzung fusion falsifier, middle band pre-registered; corpus-side Synthesis producer (un-blocks session task #65 gate 1 — session-local task list, not a GitHub number) | lance-graph | Queued | plan W6 |
15+
| D-KIA-D3a | DESCRIPTIVE binary overlap: contingency counts + BOTH marginals + observed/expected agreement + kappa + phi, via jc::stats::binary_association. Claim ceiling is overlap / disagreement / marginal asymmetry / redundancy-or-complementarity CANDIDATE. No fusion or validity claim | lance-graph | Queued (unblocked) | plan W6 |
16+
| D-KIA-D3b | HELD-OUT fusion falsifier — BLOCKED until an external criterion and a criterion-appropriate scoring rule are chosen. Continuous criterion: pre-registered delta-R-squared = R2(A+B) - max(R2(A),R2(B)). Binary criterion: a proper held-out score, NOT R-squared forced onto it. Reliability is not validity (plan C3) | lance-graph | Blocked (needs external criterion) | plan W6 |
1617

1718
## PROBE-BABEL-STANCES — two Rosetta stones + four-channel phase split (IN PR — slice 2, 2026-07-28)
1819

‎.claude/plans/kanban-64k-inverted-awareness-v1.md‎

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,47 @@ Blocks D3.
271271
>
272272
> **Two defects found by the doc examples, both fixed** — see
273273
> `E-EXACT-FIT-IS-WHERE-ABSOLUTE-ZERO-GUARDS-BREAK-1`.
274+
>
275+
> **⊘ CORRECTIVE SLICE (external review, 2026-08-04) — two P1 numerical
276+
> defects in the shipped module, both reproduced before fixing:**
277+
>
278+
> 1. **ω erased loading SIGNS.** The triad identity yields `λ_i²`, so taking
279+
> the positive root for every item dropped the sign of a negatively-keyed
280+
> one — and ω depends on `(Σλ)²`, where a negative loading must *subtract*.
281+
> Measured on an exact signed-congeneric fixture (λ = [+1,−1,+1]): reported
282+
> **0.75** against a true **0.25**. Signs are now recovered from the
283+
> covariance row (`sign(σ_ij) = s_i·s_j`, anchored `s_0 = +1`, which is free
284+
> because ω is invariant to a global flip — asserted as a test). **Why the
285+
> 31-test suite missed it: every ω fixture had all-positive loadings.**
286+
> 2. **R² was scale-DEPENDENT.** The normal equations were built on raw columns
287+
> and a pivot was called singular below an ABSOLUTE `1e-12` — a statement
288+
> about units, not rank. Measured: an exact linear fit at 1e-8 magnitude
289+
> returned `None` while the identical relationship at unit scale returned
290+
> 1.0. The design is now centered and unit-normed (R² is affine-invariant,
291+
> so no correct answer changes), the rank test is relative, and the `[0,1]`
292+
> clamp is bounded to a rounding-scale band so a failed solve surfaces as
293+
> `None` rather than as a plausible 0 or 1.
294+
>
295+
> **Also corrected:** ω's doc claimed rejections mean "the congeneric model
296+
> does not fit" — it checks three *necessary* conditions (non-negative λ², no
297+
> Heywood case, sign consistency) and **cannot certify a one-factor matrix**;
298+
> the full vanishing-tetrad constraints are not tested and `k ≥ 4` misfit can
299+
> still return a number. **Added:** `BinaryAssociation` + `binary_association`
300+
> (counts + BOTH marginals + p_o/p_e alongside κ and φ — because the shipped
301+
> φ doc said marginals are required to interpret it while the function returned
302+
> one scalar), `kr20` (the dichotomous naming surface C2 asked for and C1b did
303+
> not ship), and `betacf` now returns `None` on non-convergence instead of
304+
> presenting the 300th iterate as a p-value. **116 lib + 13 doctests green.**
274305
275306
**C2 — name the dichotomous statistics correctly.** Over binary catalog
276307
criteria: Pearson→**φ** (report the marginal-capped ceiling), Cronbach's
277308
α→**KR-20**; **κ is a SEPARATE estimator, not a renamed ICC** — where a
278309
continuous workflow would reach for ICC on binary criteria, compute **κ**
279310
instead, and keep **ICC as ICC** for the non-binary jc escalation only;
280-
Spearman **degenerates and is dropped** at view 2 (it returns only in jc's
281-
non-binary escalation). The implementation and every doc name the dichotomous
311+
Spearman is **omitted as redundant** at view 2, not as degenerate: on two
312+
non-constant binary variables the average-rank transform is affine, so ρ
313+
carries exactly the same information as φ. (It returns in jc's non-binary
314+
escalation, where the ranks stop being an affine image of the values.) The implementation and every doc name the dichotomous
282315
forms; reporting "Pearson" while computing φ is the defect class this arm
283316
exists to prevent.
284317

@@ -326,10 +359,17 @@ external criterion** (an external gold-standard criterion, defined on the privat
326359
is NOT claimed until one is wired. The plan's public claim ceiling until then:
327360
*"measurable reliability as a first step toward measurable awareness."*
328361

329-
**C4 — Jirak noise floors.** Binary criteria within one catalog panel are
330-
domain-correlated — weak dependence *by construction*, so every
331-
significance statement cites Jirak 2016 rates per `I-NOISE-FLOOR-JIRAK`;
332-
classical IID Berry-Esseen is forbidden here exactly as for fingerprints.
362+
**C4 — dependence-aware significance (scope corrected 2026-08-04).** Binary
363+
criteria within one catalog panel are domain-correlated, so classical IID
364+
significance is wrong here exactly as it is for fingerprints — that much
365+
stands. **What does NOT follow is that `jirak.rs` is the answer.** The
366+
p-values in `jc::stats` are classical independent-sample p-values and are
367+
labelled as such at the module; `jirak.rs` is a fingerprint-specific empirical
368+
probe, not a general uncertainty engine for κ / α / ω / ICC / R² / η² / φ /
369+
t. A dependent-cohort significance claim therefore needs **its own justified
370+
dependence model**, named at the claim site — citing `I-NOISE-FLOOR-JIRAK` is
371+
a pointer to the *problem*, not a licence to reuse that implementation as the
372+
*solution*.
333373

334374
**C5 — witness storage under the ELEVATED carve-out.** The cohort statistic
335375
is a cross-input derivation of a different KIND than any observation → it may

0 commit comments

Comments
 (0)