core/docs/research/cga-hot-path-measurement-2026-07-25.md
Claude 71bf04fb44
feat(provenance,teaching): close the lateral gaps the assessment actually found
Squashes the arc's work into one commit; the workflow-file edit it originally
carried is excluded (see the end of this message).

## Lane 1 — Workbench recorded a proved answer as ungrounded

With deduction_serving_enabled ratified ON (ADR-0256), workbench/api.py's live
chat route builds a bare ChatRuntime(), so the deduction composer decides
Workbench turns and stamps grounding_source="deduction" — but
_coerce_grounding_source carried a hand-copied whitelist of the six pre-arc
labels and silently rewrote anything else to "none". The runtime comment
reasoned this was inert because "REPL turns do not flow through Workbench's
CognitivePipelineRecord path". True, and irrelevant: the traffic flows the
other way. Stale since 2026-07-24.

Scope is one field. workbench/api.py:818 prefers TurnEvent.epistemic_state,
which read epistemic_state_needed — honest. So the UNregistered path degraded
honestly while the hand-copied whitelist asserted a falsehood; a second copy of
a closed enum was worse than no copy. Hence registration AND derivation:
GROUNDING_SOURCES exposes the Literal's members, and the coercion reads it.
workbench-ui badges/tokens/snapshot follow; enumCoverage.test.ts forces atomicity.

## Lane 2 — the ratification ceremony

The discovery loop was instrumented but not closed. teaching/ratification.py
turns a reviewed decision into a chain record, a corpus commit, and a receipt.

Its design turns on one observation: _ratified_rows DROPS unadmissible rows
silently — correct when serving, a trap when ratifying, because the file grows,
the commit lands, and the band count does not move. So the ceremony refuses to
call an append a ratification until it has re-read the curriculum through the
real loader and seen the chain arrive; a non-admitted append is rolled back.
Validation is a pre-flight courtesy, admission is the proof.

Arena queue entry and ledger reseal are deliberately NOT performed (bridge rule
1); the receipt names them. Front door: `core proposal-queue ratify`, a sibling
of `review` rather than a flag on it.

## Lane 3 — structural closures

- ADR-0263 gains rule 5: absence policy is DECLARED in CAPABILITY_LEDGERS, not
  passed at the call site. An AST-matched test fails if a serving path passes
  missing_ok again.
- Deductive suite added WHOLE to the pre-push gate: 285 tests in 29s against
  smoke's 216 in 62s, so no coverage trade was needed.
- Smoke/CI parity assertion made bidirectional. It was one-directional, and had
  drifted.
- test_prior_surface_deduction_binding.py pins correction binding on the
  deduction path. The review's diagnosis did NOT reproduce — hash_surface moves
  in lockstep — so it pins what is there. Mutation-checked.
- Domain-keyed ADR index over 312 flat-numbered files, explicitly partial.
- Arc-close brief template, plus this arc's own brief filled in against it.

## Lanes 4 and 5 — two premises falsified by measurement, one of them mine

Math 4.2: baseline reproduced (correct=5 wrong=0 refused=495); all four named
cases traced to one seam with each gap isolated by one-variable probes. Then the
number that changes the recommendation: the gap blocking case 0000 affects 1
case in 500, the 'than' gap blocking 0001 affects 2. ADR-0251's prohibition on
per-case growth now rests on a count. No reader change made.

CGA: versor_condition is 0.22% of a turn, not the "~10x proof latency" I claimed
— that multiplied an isolated microbenchmark by a call count and compared it to
a single verdict's latency. The real cost is geometric_product at 33,986
calls/turn (~73%) via cga_inner in search paths. The obvious closed form is NOT
bit-exact (954/4000 in f32); backend.vault_recall's serial fold IS (3000/3000,
worst-rel 0) and is the correct target. cargo test could not run —
static.crates.io is denied by the sandbox network policy — so the Rust parity
question stays open and the typestate lane is carried forward, not shipped
uncompiled.

## Not landed: three lines owed to .github/workflows/smoke.yml

The CI smoke gate is narrower than the local one —
test_pack_draft_serve_boundary.py (ADR-0253 INV-33) has been local-only, unseen
because the parity pin checked one direction. The edit was authored and rejected
at push for lacking the `workflow` OAuth scope, so it is recorded as a named,
dated PENDING_IN_CI exception rather than dropped: the assertion still fires on
any new divergence, and a second guard fires once the three land.

[Verification]: pre-push gates all green — smoke 236 passed, warmed_session 10
passed, deductive 285 passed. Ratification 14, ADR index 5, CLI suites 10.
Grounding/epistemic sweep 741 passed 1 skipped. workbench-ui 598 passed across
73 files, tsc -b clean. capability index 11 passed, digest unchanged. Math
holdout correct=5 wrong=0 refused=495. Committed chain corpora byte-unchanged
after the tests that write to them.
Environment caveat: the repo pins requires-python ==3.12.13, which uv cannot
fetch for linux-x86_64, so `uv sync --locked` fails. All Python runs used a
scratch venv on 3.12.11 with declared deps — not the locked universe, not the
full ~12k suite. The pin was left untouched. Re-run on a 3.12.13 host before
treating this as merge evidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FduW6Krm3PPQv3P5iwBYtx
2026-07-25 04:51:15 +00:00

7.5 KiB
Raw Blame History

Where the per-turn CGA cost actually is — measured, 2026-07-25

Result: the substrate-performance premise in circulation (mine and an external hardware blueprint's) was wrong. versor_condition is 0.22% of a turn. The real cost is cga_innergeometric_product at 33,986 calls per turn, ~73% of turn time, driven by nearest-neighbour and salience search. The obvious algebraic shortcut is not bit-safe and must not ship. A different, already-ratified pattern in this repo is bit-exact and is the correct target.

1. What was claimed

Two claims were in play going into this measurement, and both were reasoned from microbenchmarks rather than from a turn:

  1. An external Apple-Silicon blueprint proposed custom .metal Cl(4,1) kernels, SoA layouts, MLX kernel-fusion of the versor invariant, and bfloat16 routing — arguing the CPU is the bottleneck.
  2. My own assessment claimed versor_condition is the hot spot: three calls per turn × the benchmark report's p50 = 0.536 ms ≈ 1.6 ms/turn, "~10× the entire proof latency."

Claim 2 is wrong, and it is worth naming the error precisely because it is easy to repeat: it multiplies an isolated microbenchmark by a call count and compares the product against FrameVerdict TTFV (0.151 ms) — which is the latency of a single proof verdict, not of a turn. Those are not comparable quantities. The correct denominator is the turn.

2. Direct measurement

versor_condition wrapped in a counter, three real ChatRuntime.chat() turns:

turns                  : 3
versor_condition calls : 9   (3.0 per turn)
time inside it         : 1.34 ms total (0.448 ms/turn)
total chat() wall      : 601.5 ms (200.5 ms/turn)
share of turn          : 0.22%

The call count (3/turn) was right. The cost conclusion was not. Switching the Rust backend on would recover 0.22% of a turn through this path. That is not a reason to do it, and it removes the only quantified justification the substrate track had.

3. Where the time actually goes

cProfile, three turns, post-warmup:

calls tottime cumtime function
3 0.001 2.242 chat/runtime.py::chat
33,986 1.631 1.656 algebra/cl41.py::geometric_product
16,418 0.029 1.608 algebra/cga.py::cga_inner
24 0.009 1.156 generate/proposition.py::_nearest_by_cga
3 0.023 0.626 generate/salience.py::compute
4,577 0.003 0.458 algebra/backend.py::cga_inner

So the CGA algebra is the dominant per-turn cost — roughly 73% — but through cga_inner in nearest-neighbour and salience search, not through the versor invariant. Both documents aimed at the wrong function.

The multiplier is structural. algebra/cga.py::cga_inner is:

XY = geometric_product(X, Y)
YX = geometric_product(Y, X)
return 0.5 * scalar_part(XY + YX)

Two full 32×32 products — ~2,048 multiply-adds — to read one scalar. At 16,418 calls per turn that is the entire profile.

4. The tempting shortcut, and why it must not ship

algebra/backend.py already documents the closed form: for Cl(p,q) basis blades e_i * e_j is scalar only when i == j, so

cga_inner(X, Y) == sum_i metric[i] * X[i] * Y[i]

32 multiply-adds instead of 2,048. Tested against the scalar path, 4,000 random pairs per dtype, using np.sum:

float32: bit-exact  954/4000   worst-rel 1.037e-04
float64: bit-exact  929/4000   worst-rel 2.283e-13

Not bit-exact. A 1e-4 relative divergence in f32 would move surfaces that trace_hash folds, break the committed lane SHA pins, and violate the Rust/Python parity contract (core-rs/tests/test_crdt_hash_parity.rs). This is the same class of error as proposing bfloat16 against a 1e-6 versor gate: mathematically identical, numerically not.

5. The pattern that is exact

The divergence above is not the identity's fault — it is the reduction order. np.sum reduces pairwise. algebra/backend.py::vault_recall folds serially in component order:

scores = np.zeros(M.shape[0], dtype=np.float32)
for i in range(M.shape[1]):
    scores += (_CGA_INNER_METRIC[i] * M[:, i]) * q[i]

Its docstring claims this is "bit-identical to the scalar cga_inner path because the per-versor sum is folded in the same serial component order (ADR-0019 Stage 1)". Tested directly, 3,000 random f32 pairs:

serial-fold vs scalar cga_inner (f32): bit-exact 3000/3000  worst-rel 0.000e+00

The claim holds exactly. So this repo already contains a proven, ratified, bit-exact vectorization of the very operation that dominates the profile — scoring one query against N versors without a Python-level product loop.

6. The actual next step

Apply the vault_recall serial-fold pattern to the nearest-neighbour search paths that dominate the profile — generate/proposition.py::_nearest_by_cga and generate/salience.py::compute — which today call scalar cga_inner in a Python loop over candidates.

Why this is the right target:

  • No GPU, no Metal, no MLX, no Rust, no new numerics. Nothing in the determinism doctrine has to move.
  • The exactness is proven, not assumed (§5), and the acceptance test is falsifiable and cheap: N turns before/after must produce byte-identical surfaces and identical trace_hash values.
  • It attacks 73% of turn time rather than 0.22%.

Not done in this session, deliberately. These functions feed articulation, and the local-first merge bar is the full suite; this session could run curated suites only (see §7). The measurement and the exactness proof are the expensive parts and they are done — the change itself should land where it can be gated properly.

7. What could not be verified here, and why

cargo test in core-rs/ could not run: the sandbox network policy denies static.crates.io (the proxy's noProxy list covers index.crates.io, so the sparse index resolves but crate tarballs 403 at the gateway). No crates are cached. So:

  • "Does core_rs still hold bit-exact parity?" — the question the plan put first, and the one the hardware blueprint skipped — remains open. It is still the right question; §2 just removes the urgency the performance argument was supplying.
  • The Rust typestate lane (UnverifiedClaimVersorClaim) was not written. It is still the strongest of the blueprint's five proposals — the only one with no determinism cost, and it needs no GPU — but shipping Rust that cannot be compiled or tested in the session that wrote it is not something to do. It carries forward unchanged.

Python measurements above ran on a scratch venv at 3.12.11; the repo pins ==3.12.13, which uv cannot fetch for linux-x86_64. The pin was left untouched.

8. Standing verdicts on the blueprint's five items

Unchanged by this measurement except where noted.

Proposal Verdict
Rust typestate Adopt — carried forward, blocked only on a build.
SoA layouts Resequence — and now further down: the copy boundary is not what the profile shows.
Custom sparse .metal Cl(4,1) kernel Premature, and now doubly so: there is a bit-exact CPU win (§56) available before any GPU question is live.
MLX lazy kernel fusion of the versor invariant Rejected on target selection, additionally to the doctrine objection: the function it fuses is 0.22% of a turn.
bfloat16 asymmetric precision Reject — §4 is the empirical form of the same argument at a much coarser epsilon.

Relates to project-generalization-arc.