CB-WP-0013-T02/T03: retire SH-3 as a gate; correct AM-4a and its target

ADR-0008, tier M (survey and ADR merged).

D1 — SH-3 retired as a gate, kept as a diagnostic. Investigating it
found a third defect, deeper than the two this pass was declared on.
Re-deriving batching from the raw transcripts, independently of cb-cost:

  CB-WP-0011 pass      54 with tools    0 batched    0.0%
    gap -> next decl   16 with tools    6 batched   37.5%
  CB-WP-0012 pass      86 with tools    0 batched    0.0%
    gap -> next decl   10 with tools    1 batched   10.0%
  CB-WP-0013 so far    10 with tools    0 batched    0.0%

Zero batched turns in 150 in-pass responses; 37.5% in one gap, above the
20% floor. Batching needs two calls whose inputs are known at once —
orientation work. Implementation consumes each step's result before the
next. SH-3's window is since the last commit, which during a pass is
always implementation. The metric could not read above ~0% in the window
it was gated on. A floor the window structurally excludes is not a
target.

This pass's own declaration was also wrong: it claimed batching "has got
worse" (7.8-8.6% vs 1.1-6.3%). Differently-placed windows, not different
behaviour. Withdrawn — the same class of error, in the pass written to
correct it.

Not retargeting to match the measurement: the floor was not moved to 6%,
the gate was removed on an argument about what the quantity is worth.
The number is still reported; only the verdict is gone.

D2/D3 — AM-4a counts --edges normal,no-proc-macro: 157,202, not 246,250.
The target moves down with it, 250,000 -> 161,000, so the correction
hands back essentially nothing (headroom 3,750 -> 3,798). Three controls:
the exclusion drops exactly the five expected crates, only removes and
never adds, and is not a no-op.

The DFD gate then caught the follow-on it exists for — three historical
documents carrying live fact tags for a number that had changed. Not
rewritten; untagged, with a supersession banner.

AM-4b is deliberately not corrected: its proc-macro share is unmeasured.

gate-review now reads 0 due, 0 silent, 0 drifted — GATE-REVIEW earns its
first caught entry by forcing SH-3's re-justification, and the registry
has no silent gates left.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-02 07:30:14 +02:00
parent 6080c4cf60
commit d2c3f340f9
9 changed files with 306 additions and 24 deletions

View file

@ -1,5 +1,13 @@
# ADR-0004: ratify AM-4a and AM-4b # ADR-0004: ratify AM-4a and AM-4b
> **Superseded measurement (ADR-0008 D2/D3, 2026-08-02).** The AM-4a
> figures below were taken with an instrument that counted proc-macro
> crates — 89,048 lines, 36.2% — which run in the compiler and never
> reach a binary. Corrected, the same tree measures **157,202** against
> a target moved to **161,000**. The numbers below are left as the
> record of what was measured then, and are no longer live facts.
status: accepted status: accepted
date: 2026-07-31 date: 2026-07-31
decided by: maintainer (Bernd Worsch), 2026-07-31 decided by: maintainer (Bernd Worsch), 2026-07-31
@ -49,7 +57,7 @@ At the time of the retarget, `make dep-weight`:
| configuration | crates | third-party LOC | | configuration | crates | third-party LOC |
|---|---|---| |---|---|---|
| shipped-runtime (`--no-default-features`) | 23 | **246,250** | <!-- fact:am4a_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** | <!-- fact:am4b_loc -->
| own source | — | 3,443 | | own source | — | 3,443 |

View file

@ -0,0 +1,158 @@
# ADR-0008: retire SH-3 as a gate; correct AM-4a to count what ships
status: accepted
date: 2026-08-02
decided by: agent, under the standing loop authorization. One item is
reserved for the maintainer — see §Reserved.
tier: M (structural M — changes how the loop constrains its own operation:
a gate floor and a budget instrument, InnerLoop v1.6 / ADR-0006 D5;
chaos d4=3 → no override). Tier M merges survey and ADR into one document,
which this is; the adversarial review is optional and was not run.
references: [CB-WP-0013](../workplans/CB-WP-0013-instrument-corrections.md),
[SessionShape.md](../specs/SessionShape.md) §4,
[ADR-0006](ADR-0006-adaptive-control-gates.md) D1/D2/D4,
[ADR-0007](ADR-0007-render-html-not-a-port.md) D4,
[CB-EV-0010](../evidence/CB-EV-0010-render-port.md) §5
## Context
Two instruments were found wrong by the passes that used them. Neither
error was in the direction that flattered us, and one of them has been
producing a standing breach report for seven passes.
---
## Decision 1 — SH-3 is retired as a gate and kept as a diagnostic
### The survey: three defects, each deeper than the last
**First (CB-WP-0013 T01): the sample.** `--shape-budget` windows since the
last commit and is read while writing the evidence file — right after a
commit, when the window holds one or two responses. SH-1/SH-2 are location
statistics and survive that. SH-3 is a rate; at n=2 its only values are
0%, 50%, 100%. Six evidence files quoted the resulting `0.0%` as a
measurement. Fixed: below a derived minimum of 14 the tool now prints
`insufficient sample` and no verdict.
**Second: the claimed decline was also an artifact.** This workplan's own
Purpose stated that batching *"has got worse"* — pinned history 7.88.6%
against per-pass windows of 1.16.3%. **That was wrong**, and by the same
class of error it was written to correct. The two figures come from
differently-placed windows, not from different behaviour. Withdrawn.
**Third, and the one that decides this: the window and the metric are
systematically anti-correlated.** Measured, by re-deriving batching
independently of `cb-cost` from the raw transcripts:
| window | responses with tools | batched | rate |
|---|---:|---:|---:|
| CB-WP-0011 pass | 54 | 0 | **0.0%** |
| gap → next declaration | 16 | 6 | **37.5%** |
| CB-WP-0012 pass | 86 | 0 | **0.0%** |
| gap → next declaration | 10 | 1 | **10.0%** |
| CB-WP-0013 so far | 10 | 0 | **0.0%** |
**Zero batched turns in 150 in-pass responses across three passes, and
37.5% in the gap between two of them** — comfortably above the 20% floor.
The cause is not discipline. Batching requires two tool calls whose inputs
are both known at once. Orientation and survey work is full of those: read
four files, run three probes. Implementation has almost none, because each
step consumes the previous step's result — read, edit, test, read the
failure, fix. **So SH-3 measures what kind of work is happening.**
And SH-3's window is *since the last commit*, which during a pass is
always in-pass work. **The metric is structurally incapable of reading
above ~0% in the window it is gated on.** It is not a strict gate — it can
only be met by not working.
### The decision
**SH-3 stops being a gate.** The 20% floor and the `[SOFT]` verdict are
removed. `cb-cost` continues to report the batching rate as a diagnostic,
over the whole session, where it reads 5.8% (57 batched of 984 responses
with tool calls).
Four reasons, strongest first:
1. **It cannot report what it gates on.** §above. A floor that the
measurement window structurally excludes is not a target.
2. **It measures composition, not waste.** Penalising an implementation
pass for not batching penalises it for being an implementation pass.
3. **Its own spec bounds its value.** `specs/SessionShape.md` §4:
*"eliminating every avoidable single-call turn would remove at most a
few percent of turns, worth roughly $24 on a $93 pass"*, and *"batch
because it is free, not because it is the lever."* It also calls SH-3
*"the least valuable"* of the three.
4. **Seven passes, seven breach reports, zero actions.** ADR-0006 D4:
rituals cash out or go.
### This is not retargeting to match the measurement
`specs/SessionShape.md` forbids that explicitly, and CB-WP-0003 T07 exists
to prevent it, so the distinction has to be stated rather than assumed:
- **Retargeting** would be moving the floor from 20% to 6% because 6% is
what we measure. That is not done and is still forbidden.
- **This** removes the gate on the argument that the *quantity* does not
deserve one — established before the disposition, from the window
structure and from the spec's own valuation.
And the obvious objection — *retiring a metric right when it shows a
regression looks like burying it* — is answered by the second defect: the
regression was an artifact of window placement and has been withdrawn.
There is no established decline to bury. The number is still reported;
only the verdict is gone.
## Decision 2 — AM-4a counts `--edges normal,no-proc-macro`
ADR-0007 D4, owed. AM-4a's figure includes proc-macro crates that run in
the compiler and never reach a binary: **89,048 lines, 36.2%** of the
shipped-runtime figure, `syn` alone 66,916.
`tools/dep-weight.py` measures `--edges normal,no-proc-macro` for the
shipped-runtime configuration.
## Decision 3 — the target moves down with the instrument
Correcting the metric from 246,250 to 157,202 while leaving the target at
250,000 would hand this project **89,048 lines of headroom it did not
earn**, in the same commit that reveals the error. That is the shape of
change this repo has twice refused, and refusing it here costs nothing:
> **AM-4a's shipped-runtime target moves from 250,000 to 161,000.**
161,000 preserves the *ratio* the ratified target expressed — 250,000 was
set against a 246,250 measurement, i.e. ~1.5% of headroom; 161,000 against
157,202 is ~2.4%. Rounding to a round number slightly *above* strict
proportionality is deliberate and is the only place this decision gives
anything back, because a target that leaves 1.5% of room is a target that
fails on a dependency's patch release.
**AM-4b (dev-toolchain) is left alone**, at 350,000 against a 317,021
measurement. It is not corrected in this pass because its proc-macro share
has not been measured, and correcting a second instrument on the strength
of the first one's ratio is exactly the error this ADR exists to fix.
Filed as owed.
## Reserved for the maintainer
**AM-4a still cannot survive INTENT stage 2**, and this decision makes that
worse rather than better. `wgpu` + `winit` is 1,741,979 marginal lines
against a target now proposed at 161,000 — **10.8×**, up from 7×. ADR-0007
raised it; nothing here settles it. The correction below is honest
book-keeping, not headroom for stage 2, and it should not be read as
having made the stage-2 problem smaller.
## Consequences
- One fewer gate, and the gate registry gets its first retirement. That is
also **`GATE-REVIEW`'s first `caught` entry** — the registry's
`retire_if` says it must have *"retired, tightened, or forced the
re-justification of"* something by its review date, and it now has.
- Every AM-4a figure in this repo's history overstates the load by 36%.
Those documents are history and are not rewritten; the correction lives
in `specs/GameKernel.md` §4 and here.
- The batching diagnostic stays visible, so if in-pass batching ever
becomes possible the number will show it without a gate having to be
re-litigated first.

View file

@ -1,5 +1,13 @@
# CB-EV-0001 — GROUND game kernel: acceptance evidence # CB-EV-0001 — GROUND game kernel: acceptance evidence
> **Superseded measurement (ADR-0008 D2/D3, 2026-08-02).** The AM-4a
> figures below were taken with an instrument that counted proc-macro
> crates — 89,048 lines, 36.2% — which run in the compiler and never
> reach a binary. Corrected, the same tree measures **157,202** against
> a target moved to **161,000**. The numbers below are left as the
> record of what was measured then, and are no longer live facts.
Status: **T08 complete. AM-4 remediated and re-measured 2026-07-31.** Status: **T08 complete. AM-4 remediated and re-measured 2026-07-31.**
Recorded: 2026-07-31. Amended 2026-07-31 — §4 gains measured savings per Recorded: 2026-07-31. Amended 2026-07-31 — §4 gains measured savings per
remediation option, and §5 corrects AM-12 from "uncomputable" to remediation option, and §5 corrects AM-12 from "uncomputable" to
@ -28,7 +36,7 @@ the property is false? A row can be *measured* and still enforce nothing.
| AM-1 rule coverage (GR only) | 100% of GR-rules | 58/58 (100%) | **met** | **yes** | | AM-1 rule coverage (GR only) | 100% of GR-rules | 58/58 (100%) | **met** | **yes** |
| AM-1b link, ground | 58 claimed rules named in the aggregate | 49/58 | **unmet** | reported | | 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-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** | <!-- fact:am4a_loc --> | 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** | <!-- fact:am4b_loc -->
| AM-6 throughput | ≥100,000 events/s | 2,017,009 events/s (`make am6`) | **met, 20.2×** | **yes** — CB-WP-0006 T01 | | 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 scaling | ≥0.9× at 20× workload | 1.08× | **met** | **no** — no code computes the ratio |
@ -194,7 +202,7 @@ retargeted onto third-party source under audit.
| Configuration | Crates | Third-party LOC | Target | Verdict | | Configuration | Crates | Third-party LOC | Target | Verdict |
|---|---|---|---|---| |---|---|---|---|---|
| Shipped runtime (`--no-default-features`) | 23 | 246,250 | ≤250,000 | **met** | <!-- fact:am4a_loc --><!-- fact:am4a_target --> | 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** | <!-- fact:am4b_loc --><!-- fact:am4b_target -->
| Our own source | — | 3,408 | — | — | | Our own source | — | 3,408 | — | — |

View file

@ -10,14 +10,14 @@ generated = "2026-08-02"
pin = "fc76445" pin = "fc76445"
[am4a_loc] [am4a_loc]
value = 246250 value = 157202
text = "246,250" text = "157,202"
fmt = "{:,}" fmt = "{:,}"
by = "tools/dep-weight.py" by = "tools/dep-weight.py"
[am4a_target] [am4a_target]
value = 250000 value = 161000
text = "250,000" text = "161,000"
fmt = "{:,}" fmt = "{:,}"
by = "tools/dep-weight.py TARGETS" by = "tools/dep-weight.py TARGETS"

View file

@ -36,14 +36,15 @@ caught = [
retire_if = "two consecutive passes never approach the soft line, or commits get small enough that the window is always trivial" retire_if = "two consecutive passes never approach the soft line, or commits get small enough that the window is always trivial"
[[gate]] [[gate]]
id = "SH-1/SH-2/SH-3" id = "SH-1/SH-2"
name = "session-shape budget" name = "session-shape budget"
target = "shape-budget" target = "shape-budget"
checks = "mean and p90 context, and batching rate, since the last commit" checks = "mean and p90 context since the last commit (SH-3 retired as a gate, ADR-0008 D1 — still reported as a diagnostic)"
added = "2026-08-01" added = "2026-08-01"
review_by = "2026-11-30" review_by = "2026-11-30"
caught = [ caught = [
"first run fired HARD at 656,574 against a 300,000 ceiling, which is what prompted the compaction before CB-WP-0008", "first run fired HARD at 656,574 against a 300,000 ceiling, which is what prompted the compaction before CB-WP-0008",
"CB-WP-0013: SH-3 retired from this gate. Its window is *since the last commit*, which during a pass is implementation work, and batching needs two calls whose inputs are known at once — which is orientation work. Measured: 0 batched turns in 150 in-pass responses across three passes, against 37.5% in the gap between two of them. A floor the window structurally excludes is not a target (ADR-0008 D1)",
] ]
retire_if = "context stops correlating with cost, or the model's context handling makes the number unactionable" retire_if = "context stops correlating with cost, or the model's context handling makes the number unactionable"
@ -133,5 +134,7 @@ target = "gate-review"
checks = "gates past their review date, and gates that have caught nothing" checks = "gates past their review date, and gates that have caught nothing"
added = "2026-08-01" added = "2026-08-01"
review_by = "2026-12-31" review_by = "2026-12-31"
caught = [] caught = [
"CB-WP-0013: forced SH-3's re-justification and then its retirement. `make gate-review` had reported SH-3 as a standing breach for seven passes with zero actions taken, which is this registry's own ritual test (ADR-0006 D4); asking what it had ever caught is what exposed that the metric could not read above ~0% in the window it was gated on (ADR-0008 D1)",
]
retire_if = "it has retired, tightened, or forced the re-justification of nothing by its review date — then it is a ritual, and ADR-0006 D4 says rituals cash out or go" retire_if = "it has retired, tightened, or forced the re-justification of nothing by its review date — then it is a ritual, and ADR-0006 D4 says rituals cash out or go"

View file

@ -204,8 +204,8 @@ evidence lands in `evidence/CB-EV-0001-game-kernel.md` with no
| AM-1 | M-D1-COV: GR-rules covered by ≥1 passing scenario | no candidate has any (observation) | **100%** of GR + U rules | measured by runner report | | AM-1 | M-D1-COV: GR-rules covered by ≥1 passing scenario | no candidate has any (observation) | **100%** of GR + U rules | measured by runner report |
| 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-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-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`) | boardgame.io: 120 npm packages / 3.9M LOC | **250,000 lines** | measured (`make dep-weight`) | | 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** | 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-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-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-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 | | 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 |

View file

@ -632,7 +632,7 @@ def render(rep, by_task=False, composition=False):
f"need {SH3_MIN_SAMPLE}]") f"need {SH3_MIN_SAMPLE}]")
else: else:
print(f"{indent}SH-3 batching rate {100*sh['SH-3_batching_rate']:>11.1f}% " print(f"{indent}SH-3 batching rate {100*sh['SH-3_batching_rate']:>11.1f}% "
f"[{'ok ' if sh['SH-3_batching_rate']>=0.20 else 'FAIL'} target 20.0%]") f"[diagnostic, no floor — ADR-0008 D1]")
print(f"{indent} {sh['tool_calls']} tool calls in " print(f"{indent} {sh['tool_calls']} tool calls in "
f"{sh['responses_with_tools']} responses; " f"{sh['responses_with_tools']} responses; "
f"{sh['calls_in_batched_turns']} in batched turns") f"{sh['calls_in_batched_turns']} in batched turns")
@ -808,8 +808,14 @@ def self_test():
below.strip()) below.strip())
check("SH-3 still reports at exactly the minimum sample", check("SH-3 still reports at exactly the minimum sample",
"insufficient sample" not in at and "0.0%" in at, at.strip()) "insufficient sample" not in at and "0.0%" in at, at.strip())
check("SH-3 reports a real rate above the floor", check("SH-3 reports a real rate, with no verdict attached",
"40.0%" in good and "ok" in good, good.strip()) "40.0%" in good and "no floor" in good and "FAIL" not in good,
good.strip())
# ADR-0008 D1: the gate is gone. A verdict reappearing here means a
# floor has been reintroduced without an ADR.
check("SH-3 carries no pass/fail verdict",
not any(v in sh3_line(win(40, 1)) for v in ("SOFT", "HARD", "FAIL", "[ok")),
sh3_line(win(40, 1)).strip())
# The one that matters: a refusal must not read as a measured zero. # The one that matters: a refusal must not read as a measured zero.
check("a refusal is distinguishable from a genuine 0.0%", check("a refusal is distinguishable from a genuine 0.0%",
below != sh3_line(win(SH3_MIN_SAMPLE, 0))) below != sh3_line(win(SH3_MIN_SAMPLE, 0)))
@ -853,9 +859,20 @@ SH3_MIN_SAMPLE = 14
def sh3_line(win, min_sample=SH3_MIN_SAMPLE): def sh3_line(win, min_sample=SH3_MIN_SAMPLE):
"""SH-3, or an explicit refusal when the window cannot support a rate. """SH-3 as a DIAGNOSTIC — no floor, no verdict (ADR-0008 D1).
Returns the line rather than printing it so the refusal is testable Retired as a gate because the window and the metric are systematically
anti-correlated: batching needs two tool calls whose inputs are known
at once, which is orientation work, and this window is *since the last
commit*, which during a pass is implementation work. Measured across
three passes: 0 batched turns in 150 in-pass responses, against 37.5%
in the gap between two of them. A floor the window structurally
excludes is not a target.
The minimum-sample refusal is kept. A diagnostic may be ignored; it
may not be wrong, and a rate over two responses is wrong.
Returns the line rather than printing it so both branches are testable
without capturing stdout a guard that can only be checked by reading without capturing stdout a guard that can only be checked by reading
output is a guard that will be checked by nobody. output is a guard that will be checked by nobody.
""" """
@ -865,7 +882,7 @@ def sh3_line(win, min_sample=SH3_MIN_SAMPLE):
f"[----] {n} response(s) with tool calls, need {min_sample}") f"[----] {n} response(s) with tool calls, need {min_sample}")
rate = win["SH-3_batching_rate"] rate = win["SH-3_batching_rate"]
return (f" SH-3 batching {100*rate:>9.1f}% " return (f" SH-3 batching {100*rate:>9.1f}% "
f"[{'ok ' if rate >= 0.20 else 'SOFT'}] floor 20.0%") f"[diag] no floor — retired as a gate, ADR-0008 D1")
def shape_budget(slug): def shape_budget(slug):

View file

@ -28,15 +28,30 @@ import sys
from repo import cargo_bin, enter_root from repo import cargo_bin, enter_root
PACKAGE = "games-ground" PACKAGE = "games-ground"
# 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.
PROC_MACRO_EXCLUDED = ["--edges", "normal,no-proc-macro"]
CONFIGS = { CONFIGS = {
"shipped-runtime": ["--no-default-features"], "shipped-runtime": ["--no-default-features"] + PROC_MACRO_EXCLUDED,
"dev-toolchain": [], "dev-toolchain": [],
} }
# AM-4a / AM-4b targets from specs/GameKernel.md §4. Breaching one fails # AM-4a / AM-4b targets from specs/GameKernel.md §4. Breaching one fails
# the build: a gate that only reports is a suggestion. # the build: a gate that only reports is a suggestion.
# ADR-0008 D3: the target moves down with the instrument. Leaving it at
# 250,000 against a corrected 157,202 would hand this project 89,048 lines
# of headroom it did not earn, in the same change that revealed the error.
# 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.
TARGETS = { TARGETS = {
"shipped-runtime": 250_000, "shipped-runtime": 161_000,
"dev-toolchain": 350_000, "dev-toolchain": 350_000,
} }
@ -55,8 +70,8 @@ def crates(extra_args):
) )
sys.exit(1) sys.exit(1)
out = subprocess.run( out = subprocess.run(
[cargo, "tree", "-p", PACKAGE, "--edges", "normal", "--prefix", "none"] [cargo, "tree", "-p", PACKAGE, "--prefix", "none"]
+ extra_args, + (extra_args if "--edges" in extra_args else ["--edges", "normal"] + extra_args),
capture_output=True, capture_output=True,
text=True, text=True,
check=True, check=True,
@ -125,6 +140,23 @@ def self_test():
check("cargo resolves without caller PATH setup", bool(cargo_bin()), check("cargo resolves without caller PATH setup", bool(cargo_bin()),
cargo_bin() or "NOT FOUND") cargo_bin() or "NOT FOUND")
# ADR-0008 D2. The exclusion must remove exactly the proc-macro crates
# and nothing else — a flag that quietly dropped a runtime dependency
# would shrink the number in the direction this metric can be gamed.
with_pm = crates(["--no-default-features"])
without_pm = crates(["--no-default-features"] + PROC_MACRO_EXCLUDED)
dropped = set(with_pm) - set(without_pm)
check("the proc-macro exclusion drops exactly the expected crates",
dropped == {"syn", "quote", "proc-macro2", "unicode-ident", "serde_derive"},
f"dropped {sorted(dropped)}")
check("the exclusion only ever removes crates, never adds",
set(without_pm) <= set(with_pm),
f"{len(with_pm)} -> {len(without_pm)}")
# And it must actually remove something: an exclusion that excluded
# nothing would leave the old figure while claiming the new meaning.
check("the exclusion is not a no-op",
len(dropped) > 0, f"{len(dropped)} crate(s) dropped")
check("targets defined for every configuration", check("targets defined for every configuration",
set(TARGETS) == set(CONFIGS) and all( set(TARGETS) == set(CONFIGS) and all(
isinstance(v, int) and v > 0 for v in TARGETS.values()), isinstance(v, int) and v > 0 for v in TARGETS.values()),

View file

@ -125,7 +125,7 @@ eighth instance, with the real per-pass figures.
```task ```task
id: CB-WP-0013-T02 id: CB-WP-0013-T02
status: todo status: done
priority: high priority: high
``` ```
@ -151,11 +151,43 @@ already forbids exactly that, and CB-WP-0003 T07 exists to prevent it. If
the floor moves, it moves on an argument about what batching is worth, and the floor moves, it moves on an argument about what batching is worth, and
the argument is written down before the number changes. the argument is written down before the number changes.
**Done 2026-08-02.** [ADR-0008](../decisions/ADR-0008-instrument-corrections.md)
D1 — **SH-3 is retired as a gate and kept as a diagnostic.**
Investigating it turned up a third defect, deeper than the two this
workplan was declared on. Measured by re-deriving batching from the raw
transcripts, independently of `cb-cost`:
| window | with tools | batched | rate |
|---|---:|---:|---:|
| CB-WP-0011 pass | 54 | 0 | **0.0%** |
| gap → next declaration | 16 | 6 | **37.5%** |
| CB-WP-0012 pass | 86 | 0 | **0.0%** |
| gap → next declaration | 10 | 1 | **10.0%** |
| CB-WP-0013 so far | 10 | 0 | **0.0%** |
**Zero batched turns in 150 in-pass responses; 37.5% in one gap.**
Batching needs two calls whose inputs are known at once — orientation
work. Implementation consumes each step's result before the next. And
SH-3's window is *since the last commit*, which during a pass is always
implementation. **The metric could not read above ~0% in the window it was
gated on.**
**And this workplan's own Purpose was wrong.** It stated batching *"has
got worse"* (7.88.6% historical against 1.16.3% per-pass). Those come
from differently-placed windows, not different behaviour. Withdrawn — the
same class of error, in the pass written to correct it.
The retirement is *not* retargeting to match the measurement: the floor
was not moved to 6%, the gate was removed on an argument about what the
quantity is worth, established before the disposition. The number is still
reported; only the verdict is gone.
## Task: correct AM-4a to count what ships ## Task: correct AM-4a to count what ships
```task ```task
id: CB-WP-0013-T03 id: CB-WP-0013-T03
status: todo status: done
priority: high priority: high
``` ```
@ -175,6 +207,30 @@ be able to drop crates it should have counted: assert that the proc-macro
set is exactly the five expected crates and that removing the flag set is exactly the five expected crates and that removing the flag
restores the old figure. restores the old figure.
**Done 2026-08-02.** ADR-0008 D2/D3.
```
shipped-runtime 18 crates 157,202 lines [ok target 161,000]
```
**The target moved down with the instrument**, 250,000 → 161,000, so the
correction hands back essentially nothing: headroom goes from 3,750 to
3,798. 161,000 keeps ~2.4% of room where 250,000 kept ~1.5%; that small
rounding up is the only thing given back, because a target with 1.5% of
room fails on a dependency's patch release.
Three controls, all green: the exclusion drops **exactly** the five
expected crates, only ever removes and never adds, and is not a no-op.
**The DFD gate then caught the follow-on**, which is what it is for: three
historical documents carried live `fact:am4a_loc` tags for a number that
had changed. They are history and were not rewritten — the tags were
removed and both documents carry a supersession banner instead.
**AM-4b is deliberately not corrected.** Its proc-macro share is
unmeasured, and correcting a second instrument on the strength of the
first one's ratio is the error this task exists to fix. Filed as owed.
## Task: evidence ## Task: evidence
```task ```task