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
176 lines
7.5 KiB
Markdown
176 lines
7.5 KiB
Markdown
# 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_inner` → `geometric_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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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 (`UnverifiedClaim` → `VersorClaim`)** 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 (§5–6) 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]].
|