Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion .claude/board/EPIPHANIES.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
## 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

**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.

**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:

- **ω 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**.
- **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**.

**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.

**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.

**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.

**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.

## 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

**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.

**The census.** Hand-rolled `pearson` / `spearman` / `cronbach_alpha` definitions outside `jc`: **47 returning bare `f64`, 9 returning bare `f32`, 3 returning `Option`** — and all three `Option`s are `jc::reliability`'s own. At least one copy (`perturbation-sim/src/stats.rs:11`) returns **`0.0`** for ragged input, for `n < 2`, *and* for zero variance.

**Why `0.0` is the sharp end.** It is byte-identical to a real "perfectly uncorrelated" result. A caller cannot distinguish *"r = 0 because these variables are unrelated"* — a finding — from *"r = 0 because I handed you a constant vector"* — a bug in the caller. The undefined case is not merely unreported; it is **disguised as the most publishable value in the range**. Several copies also omit the non-finite guard, so a `NaN` input yields a finite-looking number rather than a rejection.

**This is the falsifiability rule one level down.** That rule governs guards that cannot fire; this is a *measurement* that cannot fail. Both defects share the shape: **the artifact has no way to say "no".** A statistic returning `Option` can; a statistic returning `f64` has spent its entire output range on answers and kept none for "I don't know".

**The second-order finding, which is the transferable one.** `reliability.rs`'s module header states that callers rolled their own "**until now**" — presenting consolidation as accomplished. Measurement says consolidation reached the *four new* callers and **zero of the 56 pre-existing definitions**. The doc was written from the intent of the change rather than from the state of the tree, and nothing forced a re-read. **Generalisation: a "we consolidated X" claim is an inventory claim, and an inventory claim decays silently** — it is true on the day it is written and drifts every day after, because new duplicates cost nothing to add. Such claims need either a census in the same breath or the word *aspiration*. Cf. `CLAUDE.md` § falsifiability, "a doc-comment claim is not a behaviour".

**Consequence, deliberately narrow:** the debt is recorded with a risk-ordered paydown (silent-`0.0` copies first, missing-non-finite-guard second, plain `f64` third, `f32` hot paths last or never) and explicitly **not** auto-fixable — each migration is a decision about what that call site should do when the estimate is undefined, which is exactly the information the current code discards.

## 2026-08-04 — E-EXACT-FIT-IS-WHERE-ABSOLUTE-ZERO-GUARDS-BREAK-1 — a misfit guard written as `x < 0.0` rejects the PERFECT case, and the doc example is what caught it

**Status:** FINDING (two measured defects, both fixed in the same commit). **Confidence:** High — reproduced, fixed, and each now carries a regression test. Code: `crates/jc/src/stats.rs` (D-KIA-C1b).

**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.

**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.
**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.

**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.

Expand Down
6 changes: 4 additions & 2 deletions .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Plan: `.claude/plans/kanban-64k-inverted-awareness-v1.md` (operator anchors a/b;
|---|---|---|---|---|
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| D-KIA-B1 | catalog binary-range criterion contract type + generalized catalog-mirror drift guard | lance-graph | Queued | plan W3 |
| D-KIA-C5 | cohort-statistic witness type under the ELEVATED carve-out + held-out anti-circularity gate | lance-graph | Queued | plan W4 |
| D-KIA-D1 | observer/observed as two Locus categories over one arena (cheapest-first) | lance-graph | Queued | plan W5 |
| 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 |
| 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 |
| 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 |
| 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 |

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

Expand Down
45 changes: 45 additions & 0 deletions .claude/board/TECH_DEBT.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Technical Debt Log — Open + Paid (double-entry, append-only)

## TD-STATS-DEGENERACY-CONTRACT-DIVERGENCE (2026-08-04)

**Measured, not estimated** (at `a9f813c`, workspace-wide grep over `crates/`):

| return type of hand-rolled `pearson` / `spearman` / `cronbach_alpha` | definitions |
|---|---|
| bare `f64` | 47 |
| bare `f32` | 9 |
| **`Option<f64>` (the degeneracy contract)** | **3 — all of them jc's own** |

**The debt is a CONTRACT divergence, not duplication.** `jc::reliability`'s
whole documented design point is that degenerate input (constant series,
ragged lengths, `n < 2`, non-finite values, overflowed denominators) returns
`None`, so a caller can never mistake an undefined estimate for a measured
one. The 56 hand-rolled copies drop that guarantee, and at least one
**collapses every degeneracy to `0.0`** — `perturbation-sim/src/stats.rs:11`
returns `0.0` for ragged input, for `n < 2`, and for zero variance, which is
byte-identical to a genuine "perfectly uncorrelated" result. A caller cannot
distinguish *"r = 0 because the variables are unrelated"* from *"r = 0 because
I handed you a constant vector"*. Several also omit the non-finite guard
entirely, so a `NaN` in the input propagates to a finite-looking or `NaN`
result rather than being rejected.

**This is the falsifiability rule's defect class one level down:** a statistic
that reports `0.0` for "undefined" **cannot fail visibly**, exactly like a
guard that never fires. It is worse than a wrong number because it is a wrong
number wearing the costume of a right one.

**Also a correction to a shipped doc claim.** `reliability.rs`'s module header
says callers rolled their own "until now", implying consolidation happened.
The measurement says otherwise: consolidation happened for *new* callers only
(4 jc examples), while 56 pre-existing definitions remain. The header
overstates; treat it as *aspiration*, not inventory, until this is paid.

**Not paid here, deliberately.** Migrating 56 definitions across ~26 files is
its own wave, each call site owned by its author per the workspace's
clippy-tier convention, and several are `f32` in hot paths where the `Option`
wrapper is a real (if small) signature change. **Suggested paydown order by
risk:** (1) any duplicate returning `0.0` for degeneracy — the silent-wrong
class; (2) duplicates without a non-finite guard; (3) plain `f64` duplicates
that already reject degeneracy some other way; (4) `f32` hot-path copies last,
if at all. **Do not auto-fix** — each migration is a decision about what the
call site should do when the estimate is undefined, which is precisely the
information the current code throws away.

## TD-WORKSPACE-FMT-DRIFT (2026-07-30)

**Measured, not estimated.** `cargo fmt --all -- --check` at
Expand Down
Loading
Loading