CB-WP-0019 T01/T02: AM-4b asks what a contributor acquires
Some checks failed
ci / check (push) Failing after 4s

The two AM-4 budgets had the SAME scope -- one package, no dev edges --
while claiming to bound different things. AM-4b now measures the
workspace with dev edges: 57 crates / 725,258 lines where it read 29 /
317,021, having been blind to 28 crates and 408,237 lines, more source
than its own target.

Target 745,000, ~2.7% of room -- the same margin ADR-0008 D3 gave AM-4a,
applied to a number that grew because the instrument was repaired, not
because anything was added. The target moved to fit the measurement.

T02: proc-macros are COUNTED here and excluded from AM-4a, on purpose.
AM-4a asks what ships and a proc-macro never ships. AM-4b asks what is
acquired, and ADR-0007 D3's acquisition rule counts what the build
fetches -- 'it does not ship' is no answer to 'we downloaded it'. When
the rules disagree, the question each budget asks decides. Measured
share 109,585 lines / 15.1% against AM-4a's 36.2%, so ADR-0008 D2's
refusal to borrow the ratio was right by more than a factor of two.

Caught by this project's own earlier work twice: the mutation
find-string went stale and --self-test reported it BUILD-FREE (the check
CB-WP-0015 added after AM-4a's rotted for two passes), then the DFD gate
caught facts.toml carrying the old numbers.

CB-EV-0001 and ADR-0004 carried live fact: tags on historical readings.
A dated record asserting a CURRENT value is a category error, so those
occurrences are marked as-measured instead of retro-edited, and ADR-0004
gains a supersession note.

make all exits 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-03 19:04:54 +02:00
parent 8793749654
commit 0b6f7c5bc8
7 changed files with 171 additions and 20 deletions

View file

@ -1,3 +1,11 @@
> **Superseded in part, 2026-08-03 (CB-WP-0019 T01/T02).** This ADR
> ratified AM-4b at ≤350,000 against a graph measuring 317,021 — but that
> graph was **one package with no dev edges**, blind to 28 crates and
> 408,237 lines. AM-4b now asks *what a contributor acquires*
> (`--workspace --edges normal,dev`, proc-macros counted) and its target is
> **745,000**. The reasoning here still stands; the scope it was applied to
> did not. See `specs/GameKernel.md` §5c.
# ADR-0004: ratify AM-4a and AM-4b
> **Superseded measurement (ADR-0008 D2/D3, 2026-08-02).** The AM-4a
@ -58,7 +66,7 @@ At the time of the retarget, `make dep-weight`:
| configuration | crates | third-party LOC |
|---|---|---|
| shipped-runtime (`--no-default-features`) | 23 | **246,250** | <!-- historical: measured under the pre-ADR-0008 instrument; not a live fact -->
| dev-toolchain (default features) | 29 | **317,021** | <!-- fact:am4b_loc -->
| dev-toolchain (default features) | 29 | **317,021** | <!-- as-measured 2026-07-31; AM-4b was rescoped by CB-WP-0019, see GameKernel 5c -->
| own source | — | 3,443 |
## Why these targets bind on future work rather than merely passing present work

View file

@ -37,7 +37,7 @@ the property is false? A row can be *measured* and still enforce nothing.
| AM-1b link, ground | 58 claimed rules named in the aggregate | 49/58 | **unmet** | reported |
| AM-1b link, kernel | 18 K-rules named in source | 15/18 | **unmet** | reported until 2026-08-31 |
| AM-4a dep weight, shipped runtime | ≤250,000 third-party lines | 246,250 (23 crates) | **met** | **yes** | <!-- historical: measured under the pre-ADR-0008 instrument; not a live fact -->
| AM-4b dep weight, dev toolchain | ≤350,000 third-party lines | 317,021 (29 crates) | **met** | **yes** | <!-- fact:am4b_loc -->
| AM-4b dep weight, dev toolchain | ≤350,000 third-party lines | 317,021 (29 crates) | **met** | **yes** | <!-- as-measured 2026-07-31; AM-4b was rescoped by CB-WP-0019, see GameKernel 5c -->
| AM-6 throughput | ≥100,000 events/s | 2,017,009 events/s (`make am6`) | **met, 20.2×** | **yes** — CB-WP-0006 T01 |
| AM-7 scaling | ≥0.9× at 20× workload | 1.08× | **met** | **no** — no code computes the ratio |
| AM-7 replay, timing | 100k events ≤5s | 2.18 ms (CI 2.142.23) | **met, 2,290×** | **yes** |
@ -203,7 +203,7 @@ retargeted onto third-party source under audit.
| Configuration | Crates | Third-party LOC | Target | Verdict |
|---|---|---|---|---|
| Shipped runtime (`--no-default-features`) | 23 | 246,250 | ≤250,000 | **met** | <!-- historical: measured under the pre-ADR-0008 instrument; not a live fact -->
| Dev toolchain (default features) | 29 | 317,021 | ≤350,000 | **met** | <!-- fact:am4b_loc --><!-- fact:am4b_target -->
| Dev toolchain (default features) | 29 | 317,021 | ≤350,000 | **met** | <!-- as-measured 2026-07-31; AM-4b was rescoped by CB-WP-0019, see GameKernel 5c -->
| Our own source | — | 3,408 | — | — |
Scenario tooling costs **70,771 lines that a shipped game never

View file

@ -6,7 +6,7 @@
# `make facts-check` fails if this file disagrees with the
# instruments, or if a tagged artifact disagrees with this file.
generated = "2026-08-02"
generated = "2026-08-03"
pin = "fc76445"
[am4a_loc]
@ -22,14 +22,14 @@ fmt = "{:,}"
by = "tools/dep-weight.py TARGETS"
[am4b_loc]
value = 317021
text = "317,021"
value = 725258
text = "725,258"
fmt = "{:,}"
by = "tools/dep-weight.py"
[am4b_target]
value = 350000
text = "350,000"
value = 745000
text = "745,000"
fmt = "{:,}"
by = "tools/dep-weight.py TARGETS"

View file

@ -205,7 +205,7 @@ evidence lands in `evidence/CB-EV-0001-game-kernel.md` with no
| AM-2 | M-D1-SPL: spec lines per rule in `games/ground` rules code (impl LOC ÷ rule count) | boardgame.io ~36 LOC for the 2-move synthetic game | ≤ 40 LOC/rule, paired with AM-1 (anti-gaming pair) | measured (tokei + rule count) |
| AM-3 | Synthetic-workload definition size: LOC to express the CB-RES-0001 synthetic game on our kernel | ~36 LOC (boardgame.io, measured) | ≤ 50 LOC | measured |
| AM-4a | M-D2-DEP: third-party LOC, **shipped runtime** (`--no-default-features --edges normal,no-proc-macro`) | boardgame.io: 120 npm packages / 3.9M LOC | **≤ 161,000 lines** (ADR-0008 D3, was 250,000) | measured (`make dep-weight`) |
| AM-4b | M-D2-DEP: third-party LOC, **dev toolchain** (default features) | as above | **≤ 350,000 lines** — *proc-macro share unmeasured; correction owed (ADR-0008 D2)* | measured (`make dep-weight`) |
| AM-4b | M-D2-DEP: third-party LOC, **what a contributor acquires** (`--workspace --edges normal,dev`) | as above | **≤ 745,000 lines** (CB-WP-0019; was 350,000 against a graph that measured 317,021 of the real 725,258) | measured (`make dep-weight`) — see §5c |
| ~~AM-4c~~ | M-D2-DEP: own source per third-party 100k lines | — | **WITHDRAWN from the acceptance table 2026-08-01 (CB-WP-0006 T04)** — retained as a reported diagnostic in `make dep-weight`; see §5a | diagnostic |
| AM-5 | M-D2-BLD: clean release build of headless workspace | n/a (npm install ~seconds; not comparable) | ≤ 60 s on bnt-lap001, recorded not gated | measured |
| AM-6 | M-D3-THR: applied events/s, synthetic workload, same machine | boardgame.io ~1,1001,900 moves/s (best config, degrading) | **≥ 100,000/s** (stipulated target, ADR-0002) | measured |
@ -257,6 +257,41 @@ Comparisons against the event-sourcing 10⁵10⁶/s estimate stay **parity**
until a local Rust comparator is measured (open follow-up from the
adversarial review).
### 5c. What each AM-4 budget asks, and why they differ
*(CB-WP-0019 T01/T02, 2026-08-03. Tier M.)*
The two budgets had the **same scope** — one package, no dev edges — while
claiming to bound different things. That left AM-4b blind to **28 crates
and 408,237 lines**, more source than its own target, and it is how
`quick-js` entered in CB-WP-0014 without moving the number that governs
dependencies (ADR-0009 withdrew its own cost argument over it).
| | the question it asks | scope | proc-macros |
|---|---|---|---|
| **AM-4a** | what does a game **ship**? | `-p games-ground --no-default-features` | **excluded** |
| **AM-4b** | what does a contributor **acquire**? | `--workspace --edges normal,dev` | **counted** |
**The proc-macro treatments are opposite on purpose.** AM-4a excludes them
because they run in the compiler and never reach a shipped binary —
counting them in *"what a game ships"* was simply false. AM-4b counts
them, because ADR-0007 D3's acquisition rule counts what the build causes
to be **fetched**, and a proc-macro is fetched, compiled and unaudited on
a contributor's machine like anything else. *"It does not ship"* is no
answer to *"we downloaded it"*.
**When the two rules disagree, the question each budget asks decides.**
That is the rule ADR-0008 D2 left open, and it is why that decision
refused to reuse AM-4a's measured 36.2% share for AM-4b: the real share is
**15.1%** (109,585 lines), so borrowing would have been wrong by more than
a factor of two.
**The target moved to fit the measurement, never the reverse.** 745,000
keeps ~2.7% of room on ADR-0008 D3's reasoning that ~1.5% fails on a
dependency's patch release — the same margin AM-4a received, applied to a
number that grew because the instrument was repaired rather than because
anything was added.
### 5b. Where AM-8's ten runs live, and why not everywhere
*(CB-WP-0015 T02, 2026-08-02. Tier S. The spec value N=10 is **not**

View file

@ -27,19 +27,39 @@ import sys
from repo import cargo_bin, enter_root
# AM-4a asks what a GAME SHIPS, so it measures one package's non-dev
# graph. AM-4b asks what a CONTRIBUTOR ACQUIRES, so it measures the whole
# workspace including dev edges (CB-WP-0019 T01). They had the same scope
# until 2026-08-03, which left AM-4b blind to 28 crates and 408,237 lines
# — more source than its own target.
PACKAGE = "games-ground"
WORKSPACE = "--workspace"
# ADR-0008 D2. `--edges normal` includes proc-macro crates, which run in
# the compiler and never reach a shipped binary — 89,048 lines, 36.2% of
# what this tool used to call "what a game ships", `syn` alone 66,916. The
# shipped-runtime configuration now excludes them.
#
# AM-4b is deliberately NOT corrected here: its proc-macro share has not
# been measured, and correcting a second instrument on the strength of the
# first one's ratio is the error this change exists to fix.
# AM-4b's proc-macro share is now MEASURED: 109,585 lines, 15.1% of its
# real graph (CB-WP-0019 T02). It is deliberately **not** excluded, and
# that is the opposite of AM-4a's treatment for a stated reason:
#
# AM-4a excludes proc-macros because they run in the compiler and never
# reach a shipped binary — counting them in "what a game ships" was
# simply false.
#
# AM-4b counts them, because ADR-0007 D3's acquisition rule counts what
# the build causes to be FETCHED, and a proc-macro is fetched, compiled
# and unaudited on a contributor's machine exactly like any other
# dependency. "It does not ship" is no answer to "we downloaded it".
#
# When the two rules disagree, the question each budget asks decides:
# AM-4a asks what ships, AM-4b asks what is acquired.
#
# ADR-0008 D2 said this share was unmeasured. That is no longer true.
PROC_MACRO_EXCLUDED = ["--edges", "normal,no-proc-macro"]
CONFIGS = {
"shipped-runtime": ["--no-default-features"] + PROC_MACRO_EXCLUDED,
"dev-toolchain": [],
"shipped-runtime": ["-p", PACKAGE, "--no-default-features"] + PROC_MACRO_EXCLUDED,
"dev-toolchain": [WORKSPACE, "--edges", "normal,dev"],
}
# AM-4a / AM-4b targets from specs/GameKernel.md §4. Breaching one fails
@ -50,9 +70,19 @@ CONFIGS = {
# 161,000 keeps ~2.4% of room where 250,000 kept ~1.5% — the small
# rounding up is the only thing this decision gives back, because a target
# with 1.5% of room fails on a dependency's patch release.
# CB-WP-0019 T01: the target moves to fit the measurement, never the
# reverse. AM-4b now measures 725,258 where it used to read 317,021 — not
# because anything was added, but because it started looking at what it
# always claimed to bound. A 350,000 target against a 725,258 reading
# would be a budget that is simply breached, which teaches nothing.
#
# 745,000 keeps ~2.7% of room, on ADR-0008 D3's reasoning that a target
# with ~1.5% fails on a dependency's patch release. It is NOT generosity:
# it is the same margin AM-4a got, applied to a number that grew because
# the instrument was fixed.
TARGETS = {
"shipped-runtime": 161_000,
"dev-toolchain": 350_000,
"dev-toolchain": 745_000,
}
@ -70,7 +100,10 @@ def crates(extra_args):
)
sys.exit(1)
out = subprocess.run(
[cargo, "tree", "-p", PACKAGE, "--prefix", "none"]
# The package/workspace selector now comes from the config, so the
# two budgets can ask different questions. Before CB-WP-0019 both
# were pinned to one package, which is what made AM-4b blind.
[cargo, "tree", "--prefix", "none"]
+ (extra_args if "--edges" in extra_args else ["--edges", "normal"] + extra_args),
capture_output=True,
text=True,
@ -109,6 +142,19 @@ def source_lines(name, version):
return 0
def _dev_only_dependency_is_counted():
"""AM-4b must actually see dev edges — the defect it was blind to.
`quick-js` is a dev-dependency of `cb-render-html` and is the crate
that exposed the scope defect: it landed in CB-WP-0014, AM-4b did not
move, and the ADR that added it withdrew its own cost argument as a
result. If this budget stops seeing it, the blindness is back.
"""
dev = crates(CONFIGS["dev-toolchain"])
shipped = crates(CONFIGS["shipped-runtime"])
return "quick-js" in dev, "quick-js" in shipped
def self_test():
"""Each assertion pins a failure this tool must detect.
@ -119,6 +165,21 @@ def self_test():
"""
results = []
def _check(name, ok, detail=""):
results.append((name, ok, detail))
# CB-WP-0019 T01: the two budgets must ask DIFFERENT questions, and
# `quick-js` is the case that proves it. It is a dev-dependency of
# cb-render-html; it landed in CB-WP-0014, AM-4b did not move, and
# ADR-0009 withdrew its own cost argument as a result. If AM-4b stops
# seeing it the blindness is back; if AM-4a starts seeing it, the
# shipped budget has been widened by accident.
in_dev, in_shipped = _dev_only_dependency_is_counted()
_check("AM-4b sees a dev-only dependency", in_dev,
"quick-js is dev-only and is what exposed the scope defect")
_check("AM-4a does NOT see a dev-only dependency", not in_shipped,
"the shipped budget must stay about what ships")
def check(name, ok, detail=""):
results.append((name, ok, detail))

View file

@ -135,10 +135,15 @@ def rows():
mutate=("tools/dep-weight.py",
'"shipped-runtime": 161_000,', '"shipped-runtime": 1_000,')),
Row("AM-4b", "third-party LOC, dev toolchain <= 350,000",
# CB-WP-0019 T01 widened this to the whole workspace with dev
# edges, and the literal moved with it. The stale find-string was
# caught build-free by `--self-test`, which is the check
# CB-WP-0015 added after AM-4a's mutation rotted unnoticed for two
# passes. Second catch, first one that cost nothing.
Row("AM-4b", "third-party LOC, what a contributor acquires <= 745,000",
verify=py + ["tools/dep-weight.py"],
mutate=("tools/dep-weight.py",
'"dev-toolchain": 350_000,', '"dev-toolchain": 1_000,')),
'"dev-toolchain": 745_000,', '"dev-toolchain": 1_000,')),
# Deliberately RETAINED in this denominator after its withdrawal
# from the acceptance table. Dropping it would move M-D1-MUT from

View file

@ -52,7 +52,7 @@ a factor of more than two.
```task
id: CB-WP-0019-T01
status: todo
status: done
priority: high
state_hub_task_id: "b6b209c5-ecd7-4685-aa96-d0bc4d9c0df1"
```
@ -81,11 +81,36 @@ would not be a budget at all.
dev edges, a dev-only dependency must move the number. `quick-js` is the
case to use, because it is dev-only and it is what exposed this.
**Done 2026-08-03.** **Widened.** AM-4b now asks *what a contributor
acquires* — `--workspace --edges normal,dev` — and reads **57 crates /
725,258** where it read 29 / 317,021.
The two budgets had the **same scope** while claiming to bound different
things, which is the whole defect. They now differ by construction:
AM-4a is `-p games-ground --no-default-features`, AM-4b is the workspace
with dev edges. `GameKernel.md` §5c states which question each asks.
**Target 745,000**, ~2.7% of room — the same margin ADR-0008 D3 gave
AM-4a, applied to a number that grew because the instrument was repaired
rather than because anything was added. **The target moved to fit the
measurement.**
Controls: `quick-js` must be visible to AM-4b and invisible to AM-4a, both
asserted — it is the dev-only crate that exposed the blindness in
CB-WP-0014, when it landed and the number did not move.
**And the pass was caught by its own earlier work, twice.** Changing the
target invalidated AM-4b's mutation find-string; `mutation-check
--self-test` reported it **build-free**, which is the check CB-WP-0015
added after AM-4a's mutation rotted unnoticed for two passes. Second
catch, first that cost nothing. Then the DFD gate caught `facts.toml`
carrying the stale 317,021/350,000.
## Task: the proc-macro share, decided not just measured
```task
id: CB-WP-0019-T02
status: todo
status: done
priority: high
state_hub_task_id: "6a90d069-d300-42ff-9bf6-e793f60bcf5a"
```
@ -105,6 +130,23 @@ Decide it, and say which of the two rules governs when they disagree.
**Whatever is decided, ADR-0008 D2's note that AM-4b's share is unmeasured
must stop being true in the source.**
**Done 2026-08-03. Counted, not excluded — the opposite of AM-4a, on
purpose.**
AM-4a excludes proc-macros because they run in the compiler and never
reach a shipped binary; counting them in *"what a game ships"* was simply
false. AM-4b **counts** them, because ADR-0007 D3's acquisition rule
counts what the build causes to be **fetched**, and a proc-macro is
fetched, compiled and unaudited on a contributor's machine like anything
else. *"It does not ship"* is no answer to *"we downloaded it"*.
**The rule for when they disagree: the question each budget asks decides.**
That is what ADR-0008 D2 left open.
Measured share: **109,585 lines, 15.1%**, against AM-4a's 36.2% — so
ADR-0008 D2's refusal to borrow the ratio was right by more than a factor
of two. The `unmeasured` note is gone from `dep-weight.py` and the spec.
## Task: write the self-quoting rule down
```task