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
43 changes: 43 additions & 0 deletions .claude/board/EPIPHANIES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
## 2026-08-04 — E-A-CRITERION-MUST-BE-IN-THE-SAME-UNITS-AS-THE-TEST-IT-FEEDS-1 — the ω anchor was wrong three times, each time for the same reason

**Status:** FINDING (three measured defects, each reproduced before fixing). **Confidence:** High. Code: `crates/jc/src/stats.rs::omega_total`.

**Three anchors, three rejections of valid one-factor data.** ω recovers loading signs by reading `sign(σ_ij) = s_i·s_j` off one item's covariance row. Which item:

| # | criterion | how it failed | measured |
|---|---|---|---|
| 1 | **item 0** (arbitrary) | λ₀ = 0 ⟹ the whole row is ~0 ⟹ every sign defaults `+` | λ = `[0,+1,+1,−1]` → 2 false conflicts |
| 2 | **largest raw \|λ\|** | the materiality cutoff is correlation-scaled, so a huge residual variance can pair the largest raw loading with a row entirely *beneath its own cutoff* | λ = `[0.9,1.0,−0.8]`, var = `[1,1e20,1]` → `None` on exact one-factor data |
| 3 | **highest communality** `λ_i²/σ_ii` | dimensionless — the same units the cutoff measures in | accepted |

**The class, which is the reusable part.** A *selection* criterion and the *test* it feeds must be in the same units. Anchor #2 was not wrong about loadings — 1.0 really is the largest `|λ|`. It was wrong because "strongest" was measured in raw covariance units while "material" was measured in correlation units, so the anchor could be strong by one yardstick and invisible to the other. Dimensional mismatch between a chooser and a threshold is invisible in every fixture where the two happen to agree, which is every fixture with comparable variances — i.e. all of mine.

**This is the third distinct form of one deeper error.** The absolute pivot in R², the four `.max(1.0)` floors in ω, and now the raw-`|λ|` anchor are all *scale-dependence*: a number compared against a constant, or against a quantity in different units. Each was fixed as an instance; the class kept re-appearing one layer over. **The rule that would have caught all three at once:** every comparison in a numerical routine has two operands — write down the units of both, and if they differ, the comparison is a bug regardless of what the tests say.

**Sibling check for future numeric work:** any `max_by` / `min_by` selecting an input for a downstream threshold; any `if x < TOL` where `TOL` is a literal; any criterion phrased as "strongest / largest / best" without naming the yardstick.

## 2026-08-04 — E-THE-CAN-IT-FIRE-TEST-WAS-FIRING-ON-THE-BUG-1 — a guard's proof-of-life passed because of a second defect in the same function

**Status:** FINDING (measured; both defects fixed, test rebuilt on a correct criterion). **Confidence:** High — the anchor dependence is enumerated over all four choices below.

**What happened.** Fixing ω's sign erasure introduced a sign-consistency check, and the falsifiability rule demands proof that a new guard can fire. I searched for a fixture that trips it, found one, and wrote a careful can-it-fire test that even pre-registers that neither pre-existing guard could be the rejecter. It passed. **It was passing on a bug.**

Signs were read from item 0's covariance row. If item 0 is weakly loaded that row is near-zero, every sign defaults to `+1`, and the consistency check manufactures conflicts on data that is perfectly one-factor. Enumerating the found fixture over every possible anchor:

| anchor | ‖λ‖ | conflicts |
|---|---|---|
| 0 | 0.47 | **2** |
| 1 | 1.32 | 0 |
| 2 | 0.82 | **2** |
| 3 | **1.84** | 0 |

A valid sign assignment exists — so the fixture is **consistent**, and the "conflict" was an artifact of anchoring on a weakly-determined row. The guard I was proving alive had never been shown to fire; what fired was the anchor defect. A separate check confirmed the same defect **wrongly rejects** a genuine one-factor set (λ = `[0,+1,+1,−1]`, two false conflicts).

**The two-defect interlock is the point.** A test written to prove defect-free behaviour of feature X passed because of unnoticed defect Y in the same function. Neither the test nor the guard was individually wrong-looking; the *pair* was self-confirming. This is the reason a can-it-fire test needs its trigger condition defined **independently of the implementation's own decision procedure** — mine reused the implementation's anchor convention, so it could only ever agree with it.

**The corrected criterion, and why it is the right one.** Sign-consistency is a property of the *pattern*, not of a chosen reference: the honest question is whether **any** assignment of item signs reproduces every covariance sign. A pattern where none does is **frustrated** (an odd cycle of negative edges — the classic sign-graph condition). The rebuilt test asserts frustration **from every anchor**, so no anchor choice could rescue it, and it is a fixture no implementation convention can accidentally satisfy.

**The fix that fell out is principled rather than a patch.** Anchor on the **strongest-loading** item. Under a genuine single factor `σ_ij = λ_iλ_j`, a near-zero covariance against the strongest item implies the *other* item's loading is ~0 — whose sign then cannot matter, since it contributes ~0 to `Σλ`. So the strongest anchor is correct under the model, not merely more robust.

**Carry-over rule:** *a can-it-fire test must define its trigger from the specification, never from the implementation's own procedure* — and when a guard rejects, ask whether the rejection is a property of the data or of an arbitrary choice the code made along the way.

## 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.
Expand Down
41 changes: 35 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,8 @@ adapters / codegen'd Rust) is only ever as clean as the Core it targets, so the
**OGAR Core is shaped first, deliberately** — never treated as the residue of
"what we couldn't codegen." Emit **thin classid-keyed adapters that ASSUME the
Core** (identity = `classid`; state = SoA value tenants per the #511
`SoaMemberSpec` calibration; relations = `EdgeBlock`;
`SoaMemberSpec` calibration; relations = `EdgeBlock` (the 16-byte block is canon; its 12+4 reading is
V1-legacy — resolve `ClassView::edge_codec_flavor`);
composition/inheritance = `classid → ClassView`; invocation = `UnifiedStep`),
and treat the `ruff_cpp_spo` SPO harvest (`has_function` / `inherits_from` /
`virtually_overrides`) as the **ClassView method-resolution manifest** — the
Expand Down Expand Up @@ -1463,6 +1464,8 @@ All findings in `.claude/knowledge/session_autocomplete_cache.md`.
> `.claude/board/ISSUES.md` `ISS-V1-TAIL-RESIDUE`.

**Node = 4096 bit = 512 byte:** `key(16) | edges(16) | value(480)`.
*(The stride is CANON and survives V3 unchanged; it is the KEY's u24 tail and
the EDGE block's 12+4 reading that are V1-legacy — see the two ⊘ notes.)*

**Key (16 byte, little-endian):**
```
Expand All @@ -1477,10 +1480,33 @@ All findings in `.claude/knowledge/session_autocomplete_cache.md`.
the prefix is resolved. **[V1-LEGACY — reads only; new mints use the V3
content-blind facet.]**

**Edge block (16 byte):** 12 in-family + 4 out-of-family, one byte per slot.
Canonical, NOT mandatory — always reserved (zeroed when unused), never shrunk; a
class opting out of edges is resolved via `classid → ClassView`, never by a stride
change.
**Edge block (16 byte):** always reserved (zeroed when unused), never shrunk;
a class opting out of edges is resolved via `classid → ClassView`, never by a
stride change.

> **⊘ V1-LEGACY READING — the "12 in-family + 4 out-of-family, one byte per
> slot" carving is NOT the layout (marked 2026-08-04).** The **16-byte
> reservation and the 512-byte stride are CANON and are REUSED unchanged by
> V3** (`.claude/v3/COMPONENT-MAP.md`: `NodeGuid/EdgeBlock/NodeRow` 16|16|480
> = *REUSE — CANON, const-asserted*; `.claude/v3/soa_layout/le-contract.md`
> §316: the 512-byte row "stays authoritative"). **Do NOT shrink, widen, or
> re-stride the block on the strength of this note.**
>
> What is V1 is presenting **12+4 one-byte slots as THE reading**. Under V3 it
> is one interpretation among several, selected per class:
> `EdgeCodecFlavor::{CoarseOnly, CoarseResidue, Pq32x4}` via
> `ClassView::edge_codec_flavor`. `CoarseOnly` — each byte a palette/centroid
> index, i.e. the 12+4 block read literally — is merely the **zero-fallback
> default**; `Pq32x4` reads the SAME 16 bytes as 32 × 4-bit product-quantizer
> codes, and `CoarseResidue` carries a per-dimension signed-4-bit residue in
> the reserved value slab. Every flavor leaves `NODE_ROW_STRIDE` untouched, so
> adopting one needs **no `ENVELOPE_LAYOUT_VERSION` bump**.
>
> **Consequence for a session reading this block:** never assume a node's edge
> bytes mean "12 in-family + 4 out-of-family" — resolve the flavor through the
> class's `ClassView` first. Kernels: `ndarray::hpc::edge_codec`; per-flavor
> fidelity: `ndarray::hpc::reliability` (`edge_codec_compare`). Append-only:
> the V1 line is regraded in place, not deleted.

**Zero-fallback ladder (monotonic — zero = fall through to the broader default):**
- `classid == 0x0000_0000` → default class, no prefix routing (dormant)
Expand All @@ -1498,4 +1524,7 @@ dash-groups are the only semantics; the tail is plain `family|identity`.

Reference impl: `canonical_node.rs` (`NodeGuid` / `EdgeBlock` / `NodeRow`, with
`const _` size asserts at 16/16/512 and `is_default_class` / `is_unbasined` /
`is_bootstrap_address` guards).
`is_bootstrap_address` guards). The types are live and V3-current; what is
V1-legacy is the *reading* of two fields — `NodeGuid`'s u24 tail (mint via
`mint_for`, never `NodeGuid::new`) and `EdgeBlock`'s 12+4 carving (resolve via
`ClassView::edge_codec_flavor`, never assumed).
Loading
Loading