CB-WP-0012-T02: adversarial review — not approvable as written

Four of six challenges conceded; the survey's two main arguments are
replaced rather than clarified.

C1  The survey concluded the sub-100k region was empty without ever
    measuring it. It is not empty: tiny-skia fits at 83,956, and the
    real windowed floor is fltk at 140,079 — 1.5x corrected headroom,
    not 128x. "Two orders of magnitude" is withdrawn.

C1b Following that concession: wgpu+winit is 1,741,979 marginal lines
    against a 250,000 target. AM-4a is incompatible with INTENT stage 2
    by 7x, and has been since both were written. Raised for the
    maintainer; deliberately not decided by this pass.

C2  "Marginal cost zero" was scored on an axis chosen to produce zero,
    and the same relocation already flattered sdl2 and fltk in the
    survey's own table. One acquisition rule now covers all three, and
    it raises two candidates' cost rather than lowering the winner's.

C3  The survey contradicted itself on the second-use rule. cb-render-api
    and cb-render-null are withdrawn; cb-render-html ships against the
    existing Project trait, and the port waits for stage 2's second use.

C6  The candidate measurements did carry a positive control. The C1
    batch did not — it copied dep-weight.py's measurement function
    without its guards — and was re-measured under them before being
    cited. Figures reproduced unchanged. The HTML row is relabelled
    0 by construction, not by measurement.

C4/C5 become six controls binding on ADR-0007.

The recommendation stands. The reasoning that reached it does not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-02 04:12:22 +02:00
parent 53abbaf68f
commit c5fa610e59
5 changed files with 665 additions and 27 deletions

View file

@ -0,0 +1,204 @@
# 260802 — challenge to CB-RES-0006
Adversarial review, one round, per InnerLoop §Step 2.
**Fidelity note, first, because it caps everything below.** The spec asks
for a *separate session or agent*. This review was run in the same session
as the survey, because this environment's standing instruction is not to
spawn agents unless asked. So it does not have the property the step exists
to provide: the reviewer inherits the author's sampling, the author's
framing, and the author's blind spots. Every prior instance of this class in
the repo — CB-WP-0002's dedup sample, CB-WP-0005's AM-7 assertion — was a
shared blind spot between author and reviewer who sampled the same way.
Treat the challenges below as a lower bound on what a genuinely separate
reviewer would find.
The claim rests on numbers, so per the table in §Step 2 the reviewer's job
is to reproduce them independently and to mutate the assertions behind
them — not to argue with the prose.
---
## C1 — The survey concluded the sub-100k region was empty without looking
**This is the challenge that lands.**
The survey measures seven candidates. The cheapest windowed one costs
480,501. It then reports the corrected headroom as 92,798 and concludes
that no windowed toolkit fits — a conclusion about the interval
`0 < x < 92,798` drawn from a sample whose smallest windowed member is
480,501. Nothing was measured in the interval the conclusion is about.
The candidate list is not random, either. It is the list a 2026 Rust
developer reaches for, which is a list of GPU-era stacks: everything on it
pulls `wgpu`, `winit`, or `rustix`. Cheap, old, thin bindings to system
libraries were structurally absent from the sample.
Measured, same method, marginal against the same base:
| candidate | marginal lines | vs 92,798 corrected headroom |
|---|---:|---:|
| `tiny-skia` alone | **83,956** | **fits**, at 90% of it |
| `fltk` | **140,079** | 1.5× |
| `sdl2` | **191,973** | 2.1× |
| `sdl3` | 276,445 | 3.0× |
| `raqote` | 286,852 | 3.1× |
| `minifb` | 774,389 | 8.3× |
| `pixels` | 863,528 | 9.3× |
| `speedy2d` | 1,328,008 | 14.3× |
**The survey's central rhetorical move — "this is not a near miss to be
negotiated; it is two orders of magnitude" — is false.** It is two orders
of magnitude for the candidates the survey chose. The real floor for a
windowed toolkit is `fltk` at 140,079, which is **1.5×** the corrected
headroom. That is exactly a near miss to be negotiated.
`tiny-skia` at 83,956 actually fits — it is a pure-Rust CPU rasterizer with
no windowing, so it is not by itself a table, but it disproves the survey's
stated interval claim on its own.
**Required:** the survey must either withdraw the "two orders of magnitude"
framing and re-argue against a 1.5× floor, or concede that its
recommendation rests on something other than dependency cost.
## C2 — "Marginal cost zero" is scored on an axis chosen to produce zero
AM-4a measures vendored third-party **Rust**. The HTML option scores zero
because it relocates the renderer into a runtime AM-4a cannot see. The
browser is tens of millions of lines of unaudited third-party code. Calling
that zero is true under the metric and false in substance.
If a proposal may satisfy a dependency target by moving the dependency
somewhere the target does not look, then the target constrains nothing, and
this pass is the first to demonstrate it. That is a worse outcome for the
project than buying `macroquad` honestly.
**And the same critique convicts C1's cheap candidates.** `sdl2` at 191,973
and `fltk` at 140,079 are thin Rust bindings to large C libraries; their
`.rs` line counts exclude the actual implementation for exactly the same
reason the browser's is excluded. So the metric already permits this move,
and the survey's own table already rewards it — it simply did not notice,
because it never measured a binding.
**Required:** one rule, applied to all three. Either relocating an
implementation into an unmeasured runtime is permissible (and then `sdl2`
and `fltk` are legitimately cheap and must be compared on their merits, not
excluded on cost), or it is not (and then HTML is not free either). The
survey cannot have it one way for the browser and another for `sdl2`
without stating the distinction. It states none.
## C3 — The survey contradicts itself on the second-use rule
Recommendation 1 declares `cb-render-api` a canonical port now.
Recommendation 4 says `cb-render-wgpu` at stage 2 is "where the port
interface gets its second use and only then becomes canonical."
Both cannot hold. INTENT: *"No concept becomes canonical merely because it
looks general. It becomes canonical after surviving a second concrete
use."* A port interface written against exactly one implementation, which
emits whole documents, will acquire a document's shape — retained mode,
full state per update, string identity, no frame timing — and stage 2 will
find it unimplementable and rewrite it. The survey names this risk in §6
and mitigates it with "write it to be implemented twice," which is a wish.
`CommitWindow` is already on record in this repo as the concept that looked
general, has a delete-by date, and has declined two second-use
opportunities.
**Required:** either defer `cb-render-api` until stage 2 supplies a second
implementation, and ship `cb-render-html` directly against the existing
`Project` trait — or state why this port is exempt from a rule INTENT
states without exception.
## C4 — The 250-line HTTP server is undersold, and "loopback only" is not a boundary
The survey bounds the risk with "listens on loopback only and speaks to a
page it emitted itself." Neither clause holds:
- Any process on the machine can reach a loopback listener. Hot-seat play
is explicitly *several people at one machine*.
- **Any web page the user visits can reach it too.** A page on the open
internet can issue requests to `127.0.0.1` — that is what DNS rebinding
and cross-origin POST are. The browser running the table is the same
browser reading the internet; that is the whole premise of the option.
- "A page it emitted itself" is an assumption the server cannot check
unless it is written to check it, and the survey specifies no such check.
An unauthenticated loopback endpoint that accepts `POST /command` and
mutates authoritative game state is a remote-controlled game from any tab
the user has open.
**Required, as concrete controls the survey lacks:** an unguessable token
minted per process and required on every request; `Origin` and
`Sec-Fetch-Site` checked and rejected by default; the listener bound to
`127.0.0.1` explicitly rather than `0.0.0.0`; and a test that a request
without the token is refused. The last one is the one that matters — the
other three are claims until something asserts them.
## C5 — The proposal regresses the finding the previous pass paid for
CB-WP-0011's finding, in its own words: a renderer's defect class is
**silent omission**, no natural renderer assertion catches it, and the fix
is to walk the shape of the input and make silence cost a build.
This proposal moves drag hit-testing and command construction into
JavaScript embedded in emitted strings — a language `cargo test` cannot
run, `clippy` cannot lint, and `M-D1-MUT` cannot mutate. The gate that
CB-WP-0011 built stops at the language boundary, and the survey proposes
putting the interactive half of stage 1 on the far side of it, one pass
after paying to learn why that is dangerous.
§6 lists this as "the strongest argument against, and the one T02 should
press." Naming a challenge in advance is not answering it.
**Required, as controls:**
- **JS may not construct commands.** The emitted page reports raw pointer
facts — "pointer down on element `id`, up on element `id`" — and Rust
decides what command that is. That confines JS to input transport and
makes the decision testable by feeding the Rust side synthetic events.
- **The coverage gate must cross the boundary.** `every_view_field_is_
classified` must have an HTML counterpart asserting over the *parsed
emitted document*, not over the Rust that emits it. Emitting a `<div>`
per field is not evidence the field is visible; the assertion must be
that the token is in the document.
## C6 — One number in the survey is reproduced; one is not independently checkable
Reproduced: the proc-macro delta. `--edges normal,no-proc-macro` gives 18
crates / 157,202 lines against 23 / 246,250. The 89,048 / 36.2% figure is
correct, and the five crates named are the five that differ.
**Not independently checkable:** every candidate figure, because the
reviewer used the author's script. Per §Step 2 this is precisely the
failure mode — "the reviewer re-derives on a different sample than the
author used" — and no different sample was used. The C1 table above is
*new* data from the same instrument, which tests the survey's coverage but
not its instrument.
**What the harness would report if it silently stopped:** `source_lines()`
returns 0 for a crate it cannot locate, and the probe scripts sum without
a positive control. A registry path change would report every candidate as
0 marginal lines — i.e. as *fitting comfortably* — which is the direction
that flatters the recommendation. `tools/dep-weight.py` has exactly this
control (`unlocatable crate measures zero (so the guard fires)`); the probe
scripts copied the measurement function and not the guard.
The `svg-emit` row in the survey's table reads `0` marginal for reasons
that are correct, but a reader cannot distinguish that 0 from a harness
that stopped. **A zero that means "costs nothing" and a zero that means
"measured nothing" are printed identically.** That is HDN, in the survey's
own instrument, in the row carrying the recommendation.
---
## Verdict
**Not approvable as written.** C1, C2, C3 and C6 each require a change to
the survey, not a clarification. C4 and C5 require controls that must land
in ADR-0007 as conditions on the implementation.
The recommendation may well survive — none of these establishes that a
windowed toolkit is affordable, only that the survey's argument for why it
is not was overstated and partly unmeasured. But the survey as written
argues from a false interval claim, an inconsistently applied metric rule,
an internal contradiction, and a zero that cannot be distinguished from a
dead harness.

View file

@ -0,0 +1,75 @@
# 260802 — how CB-RES-0006 was conducted
Unpolished trail for `research/CB-RES-0006-render-port.md`, per
InnerLoop §Step 2 documentation requirement.
## What was measured vs cited
**Everything quoted is measured.** No line count in the survey comes from
a README, a crates.io page, or memory. Method: a scratch cargo crate per
candidate, `cargo add`, then `cargo tree --edges normal --prefix none`
over the resolved graph, then `\n` counted in every `.rs` file of the
vendored source under `~/.cargo/registry/src/*/`.
That method is copied out of `tools/dep-weight.py` rather than
re-invented, because the survey's whole purpose is to subtract candidate
sizes from a budget produced by that tool, and two methods produce two
numbers that cannot be subtracted.
Scripts: `$CLAUDE_JOB_DIR/tmp/survey/{measure,breakdown,gap}.py`.
Deliberately not committed — a throwaway probe promoted to a repo tool is
how the second-use rule gets broken by accident. If a later pass needs
these numbers refreshed, it should re-derive them, and the fact that it
costs ten minutes is a feature.
## Dead ends and corrections during the survey
**First reading was on the wrong axis.** The initial run reported headline
totals: `egui+eframe` 2,946,121, `ratatui+crossterm` 1,146,363,
`macroquad` 480,729. Those are the numbers a survey normally quotes and
they are the wrong ones — AM-4a charges what a candidate *adds* to a graph
that already holds 23 crates. Recomputed as marginal against the real
base. For `macroquad` the difference is negligible (480,729 → 480,501);
for `egui+eframe` it is 163,272 lines of overlap.
**A network 403 that was not a network failure.** `curl` to crates.io
returned 403; the sandbox looked like it was blocking egress. It was a
missing user-agent — crates.io rejects requests without one. Two further
probes (sparse index, static.crates.io) returned 200. Worth recording
because "the environment is blocking me" is a conclusion that ends
surveys, and it was wrong here by one header.
**Checked whether `cargo tree` was including non-host targets.** The
`ratatui` figure was dominated by `linux-raw-sys` (479,901) and looked
like it might be an all-platform artifact — a Windows-bindings inflation
of a Linux measurement. Re-ran with `--target x86_64-unknown-linux-gnu`:
identical, 1,146,363 both ways. The number is real, and `ratatui` really
does cost more than a 2D game framework.
**The proc-macro finding was not what the survey set out to look for.** It
came from asking why `syn` was the largest single entry in a
"shipped-runtime" figure. `--edges normal,no-proc-macro` answered it:
89,048 lines, 36.2%, none of which reaches a binary.
Order matters here, and it went the right way by luck rather than design:
the candidate measurements were complete *before* the instrument defect
was found, so the recommendation could be checked against the corrected
number instead of being derived from it. Had it gone the other way the
survey would have been unusable — a budget correction discovered while
looking for headroom is not evidence, whatever it says.
## What the survey did not do
- **It did not sample the region between 0 and 480,501 lines.** The
candidate list was assembled from what one reaches for — modern GPU-era
Rust GUI stacks — and every one of them landed two orders of magnitude
over. The conclusion "no windowed toolkit fits" was therefore drawn from
a sample that contained no small windowed toolkits. This is the gap the
challenge (T02) attacks, and it is a real one.
- **It did not run any candidate.** No candidate was built, drawn with, or
benchmarked. Per InnerLoop §Step 1 the comparisons here are dependency
cost only; nothing in the survey claims one toolkit draws better than
another, and no `better` verdict is available on that axis.
- **It did not measure the browser.** The HTML option is scored zero on
AM-4a because AM-4a measures vendored Rust. What the browser costs on
any other axis is unmeasured and the survey says so in §5.

View file

@ -0,0 +1,193 @@
# 260802 — response to the challenge on CB-RES-0006
One round, per InnerLoop §Step 2. Four challenges conceded, two answered
with evidence, and one of the concessions turned up something larger than
this pass.
---
## C1 — the unmeasured interval — **conceded, and the framing withdrawn**
The challenge is right and the survey's rhetoric was wrong. "Two orders of
magnitude" described the candidates I picked, and I picked them from what a
2026 Rust developer reaches for, which is a list of GPU-era stacks. The
real floor for a windowed toolkit is `fltk` at **140,079** marginal lines —
**1.5×** the corrected headroom, not 128×.
The C1 table is folded into the survey as measured data, including
`tiny-skia` at 83,956, which fits inside 92,798 and disproves the interval
claim by itself.
**So dependency cost alone does not decide this.** The survey's argument as
written is withdrawn. What replaces it is an allocation argument, and it is
stronger:
INTENT stage 2 names `wgpu` explicitly. That is a **1,741,979-line**
marginal cost, already measured, already unavoidable, and it arrives one
stage from now. Against that, spending 1.5× the entire remaining budget on
a stage-1 windowing toolkit that stage 2 immediately discards is the wrong
allocation — not because it does not fit, but because it buys a thing with
a known expiry using money already committed elsewhere.
That argument does not need AM-4a's target to be exactly 250,000. It only
needs stage 2's bill to be larger than stage 1's benefit, which is
measured, and true by a factor of twelve.
## C1b — what the concession exposed: **AM-4a cannot survive stage 2**
Following C1 honestly produces a finding neither the survey nor the
challenge was looking for.
| | marginal lines |
|---|---:|
| AM-4a target, total | 250,000 |
| current shipped-runtime (corrected, proc-macro excluded) | 157,202 |
| headroom | 92,798 |
| `wgpu` + `winit`, which INTENT stage 2 names by name | **1,741,979** |
Stage 2 exceeds the entire AM-4a target by **7×**, and exceeds the
remaining headroom by **19×**. No sequencing, no feature-gating and no
metric correction closes a gap that size. **AM-4a as targeted is
incompatible with INTENT as written**, and has been since both were
written; nothing in this pass caused it.
That is a maintainer's decision, not mine — either the target moves, or the
port stays out of the AM-4a configuration by an argued rule, or stage 2
changes. It is recorded here and carried to the evidence file, and it is
deliberately **not** resolved by this pass, because a pass that discovers a
budget conflict and also decides it has reviewed nothing.
It does, however, retire one bad reason for the recommendation. "HTML,
because we cannot afford anything else" is false. "HTML, because stage 1
should not spend stage 2's money on something stage 2 throws away" is true.
## C2 — the metric is gameable by relocation — **conceded; one rule, stated**
The challenge is right that the survey scored the browser at zero on an
axis chosen to produce zero, and right that the same move already makes
`sdl2` and `fltk` look cheap in the survey's own table. I had not noticed,
because I had measured no bindings.
The rule, which belongs in AM-4's definition and not in this pass's
reasoning, applied consistently to all three:
> **AM-4 counts third-party code the project causes to be acquired.** It
> does not count runtimes the user already has independently of us — the
> kernel, the system C library, the shell, the terminal, a web browser. It
> *does* count a library our build or install instructions cause to be
> fetched, pinned, or linked, whether or not its source is Rust.
Under that rule, consistently:
- a **browser** is not counted — nothing we ship or instruct causes it to
be acquired;
- **`sdl2`/`fltk`** are counted at more than their Rust binding, not less —
they oblige a user to install a `-dev` package they would not otherwise
have, so the survey's 191,973 and 140,079 **understate** them;
- and the rule makes the metric *harder* to satisfy by relocation, not
easier, which is the direction a rule proposed by the party it benefits
has to go before it is worth anything.
This makes the HTML row honest rather than merely favourable. It also
means the survey's table understates two candidates, and that correction is
folded in.
## C3 — the port contradicts the second-use rule — **conceded entirely**
Recommendations 1 and 4 cannot both stand, and 4 is the one INTENT
supports. `CommitWindow` is already in this repo with a delete-by date for
exactly this mistake.
**`cb-render-api` is withdrawn from this pass.** `cb-render-html` ships
against the existing `Project` trait, which is a real interface with real
implementations rather than a speculative one. The port gets declared at
stage 2, when `wgpu` supplies the second concrete use and the interface can
be derived from two implementations instead of imagined from one.
This also removes `cb-render-null`: a null implementation of an interface
that does not exist yet is a null implementation of nothing.
**And it changes the pass's tier.** The structural trigger for tier L was
"creates a new capability port." This pass no longer creates one. That is
recorded rather than acted on — the declaration and its roll stand, and the
pass runs at the weight it declared. Re-rolling a tier because review made
the work smaller would make the tier a function of the outcome, which is
the one thing a tier declaration must not be. **The CHAOS calibration
window gets a second entry from this**, and it is a more interesting one
than the first: tier L at full weight deleted its own structural trigger.
## C4 — the loopback server — **conceded; controls carried to ADR-0007**
The challenge is right that "loopback only" is not a boundary against a
browser that also reads the internet, and right that "a page it emitted
itself" is an assumption no code checks. All four controls are adopted as
conditions on the implementation, and the fourth is the load-bearing one:
1. an unguessable token minted per process, required on every request;
2. `Origin` / `Sec-Fetch-Site` checked, rejected by default;
3. bound explicitly to `127.0.0.1`;
4. **a test that a request without the token is refused** — and, per
M-D1-MUT, a mutation that removes the check and turns that test red.
Controls 13 without 4 are three claims and no evidence, which is the
class this project keeps finding in its own work.
## C5 — the gate stops at the language boundary — **conceded; both controls adopted**
Naming a challenge in the survey and calling it pressed was not an answer,
and the challenge says so correctly.
1. **JavaScript may not construct commands.** The page reports raw pointer
facts; Rust decides what command they mean. JS becomes input transport,
and the decision becomes testable in Rust against synthetic events.
2. **The coverage gate crosses the boundary.** The HTML counterpart to
`every_view_field_is_classified` asserts over the *parsed emitted
document* — the token is in the document, or the build fails. Asserting
over the Rust that emits the document would reproduce CB-WP-0011's
original defect one layer up.
## C6 — the zeros — **partly answered, partly conceded**
**Answered:** the candidate measurements did carry the positive control the
challenge says they lacked. `measure.py` tracks an `unlocated` list per
candidate and it is empty for all seven — verified in `results.json`, not
asserted. So no candidate figure is a silently-stopped harness.
**Conceded, twice over:**
- `gap.py`, which produced the C1 table, copied the measurement function
**without** the guard. The C1 numbers therefore have no positive control.
They are load-bearing now, since they are what withdrew the survey's main
argument, so they must be re-measured under the guard before ADR-0007
cites them.
- The survey's HTML row is **0 by construction, not by measurement**
there is no crate to fail to locate. The challenge is right that a
reader cannot tell that zero from a dead one, and the survey printed them
identically in a table whose other rows are measurements. It is relabelled
rather than left to be read as a measured figure.
The general observation is worth keeping: a probe script that copies a
tool's measurement function and not its controls is how a positive control
gets lost, and it happened here inside one pass, in a repo whose whole
discipline is positive controls.
---
## What changed in the survey
- The "two orders of magnitude" framing is **withdrawn**; the real floor is
`fltk` at 1.5× corrected headroom.
- The C1 candidates are folded in as measured rows, re-measured under the
positive control.
- The recommendation's justification changes from *affordability* to
*allocation against stage 2's known 1,741,979-line bill*.
- The AM-4 acquisition rule is stated, and it **raises** the cost of two
candidates rather than lowering the cost of the recommended one.
- **`cb-render-api` and `cb-render-null` are withdrawn from this pass.**
- The HTML row is relabelled *0 by construction*.
- C4 and C5's six controls become conditions in ADR-0007.
- **AM-4a's incompatibility with stage 2 is raised as a maintainer
decision** and explicitly not decided here.
**Approvable after these changes.** The recommendation stands; the argument
that reached it does not, and has been replaced.