CB-REV-0002: round 2, and the corrections were not approvable either
Some checks failed
ci / check (push) Failing after 4s

Three FATAL, five SERIOUS. The substance of round 1's corrections held —
Reactive is genuinely one arm different, the five replacement controls are
non-inert, the inert metric is right, the numbers reproduce. What failed
were the CLAIMS about them, and two defects the corrections introduced.

FATAL 1: the fix for round 1's #11 did not fix it. The assertion was
`games + setup_fails == 200`, and a refused setup increments setup_fails
while skipping games — so the sum is invariant under exactly the failure
it claimed to catch. Injecting setup failures gave exit 0 over 196-game
columns. Now asserts games == GAMES, verified to exit 101.

FATAL 2: the correction to the selective-column FATAL was itself
selective. "81-1000 per cell, baseline AND H1" and "31-1000" twelve lines
apart, both taken from the baseline row; under H1 rank-75 arms are
59/0/0/0. Every cell is now printed rather than summarised, and the
corrected verdict is the opposite of the one it replaced: under rank-75,
H1 REDUCES DARVO arms to zero at 3p and above.

FATAL 3: "DARVO arms 2 per seat per game" is 1 per seat per game, exactly,
at every band.

SERIOUS: the tiebreak oracle asserted only that the winner set CHANGED, so
reversing the tiebreak left it green; the #13 defect's impact was claimed
and never measured (72,000 games: zero divergences — real in principle,
witnessed only by a constructed board); a 29-of-363 citation pointed at a
file that did not contain it (round 1's reviewer did report it, and it was
never transcribed — the record was wrong, not the number); the harnesses
were run by NO GATE, so every published figure came from a manual run of
an ungated binary, including the assertion added for #1; and edition-check's
sibling handling — added by the last correction — was self-certifying,
crashed instead of failing, and counted Markdown lines as coverage. Now
discovered on disk, and it found a real gap on its first run: Rules_Text.csv
vendored with no digest.

Also: "peak Stress held" was dead code kept quiet by `let _ = held;` — the
numbers were right by coincidence.

make panels is now a registered gate. Round 3 is owed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 09:44:00 +02:00
parent 84aa09264c
commit da58e78e4a
10 changed files with 285 additions and 56 deletions

View file

@ -36,7 +36,7 @@ PORT ?= 0
# Every cargo recipe runs at the repo root; the shell does not persist cd.
IN_REPO := cd $(REPO) &&
.PHONY: help ground check test sim bench bench-test coverage dep-weight cost cost-test cost-pin cost-budget shape-budget cost-mix loop-lint self-tests env-test task-done status facts-check facts-gen mutation-check size-metrics runtime-metrics build-time am6 am7 am8 edition-check replay-test loc play gate-review all \
.PHONY: help ground check test sim bench bench-test coverage dep-weight cost cost-test cost-pin cost-budget shape-budget cost-mix loop-lint self-tests env-test task-done status facts-check facts-gen mutation-check size-metrics runtime-metrics build-time am6 am7 am8 edition-check replay-test loc play gate-review panels all \
design difficulty trials
# `design`, `difficulty` and `trials` were added by CB-WP-0022, CB-WP-0025
# and CB-WP-0027 and none was declared here. Only `trials` revealed it, by
@ -228,6 +228,16 @@ trials:
difficulty:
@cargo run --release -q -p games-ground --example difficulty
# CB-REV-0002 #7: the H1 measurement harnesses were run by NO gate. Every
# number in CB-EV-0030 and CB-EV-0031 came from a manual invocation of an
# ungated binary -- so the assertion added to catch short cells was
# unreachable from `make`, and "what would the harness report if the work
# silently stopped" answered: green, and nothing else.
## the variant panels: ATTACK's value, and regulation under H1
panels:
@cargo run --release -q -p games-ground --example attack-value
@cargo run --release -q -p games-ground --example regulation
# CB-WP-0022 T05: the design-finding register, reported over
# specs/GroundRules.md. Shows the QUEUE by default; the log of closed
# findings is a line, not a listing, because a default view that mixes

View file

@ -61,6 +61,7 @@ reported it unverified and it was a real gap.
sha256 eb21fa3237637790fef601fe6668a190a549715e9a78b7dfe47b40cd069b648e ../catalog.yaml
sha256 f58e81f84ea2b0d16e39932261eb3f3d9890345cdf37ad6f0b3abc00636840be ../experiments/h1-problem-stress/rules_delta.yaml
sha256 7b1cc0149122b855e827bc930576ed165bf7dd8d62707e845a9e514ce3521f8e ../experiments/h1-problem-stress/Actions.csv
sha256 62785f5e7e245c60171624d15de2f40187a44fec54f93c7d9705cf52584b1078 ../experiments/h1-problem-stress/Rules_Text.csv
```
**`rules_delta.yaml` is the load-bearing one**: it is the executable

View file

@ -20,18 +20,33 @@ than trusted, and the ATTACK panel re-run for both rule sets.
`ground-game`'s success criteria (design note §3.2), answered one by one.
**Three of four fail.**
> **CORRECTED 2026-08-08 after adversarial review**
> ([`CB-REV-0001`](../reviews/CB-REV-0001-h1.md)). Criteria 1 and 2 were
> rendered as flat failures **on the strength of the greedy column
> alone**, while this pass's own harness printed 311000 DARVO arms per
> cell in the rank-75 and rank-95 columns. That is the selective-column
> move this project says it exists to prevent, committed in the file that
> says so. The verdicts below are restated per column.
> **CORRECTED TWICE.** Round 1 found criteria 1 and 2 rendered as flat
> failures **on the greedy column alone**, while this pass's own harness
> printed non-zero DARVO arms in the rank-75 and rank-95 columns — the
> selective-column move, in the file that names it.
>
> **Round 2 found the correction was itself selective, and its numbers
> wrong** ([`CB-REV-0002`](../reviews/CB-REV-0002-h1.md) #2): it quoted
> "811000 per cell … baseline *and* H1" and, twelve lines away,
> "311000". Both ranges came from the **baseline** rank-75 row. Under
> **H1**, rank-75 arms are `2p 59, 3p 0, 4p 0, 6p 0` — three cells at zero
> and the fourth below the stated floor.
>
> **Every cell is now given**, because two attempts at summarising this
> into a range have now produced three wrong numbers.
**DARVO arms per 200-game cell, SHARED GROUND** (all three modes agree on
arms; they differ only in who wins):
| | greedy | rank-75 | rank-95 |
|---|---:|---:|---:|
| **baseline** 2p / 3p / 4p / 6p | 0 / 0 / 0 / 0 | 81 / 125 / 218 / 31 | 400 / 805 / 1000 / 1000 |
| **H1** 2p / 3p / 4p / 6p | 0 / 0 / 0 / 0 | **59 / 0 / 0 / 0** | 600 / 870 / 941 / 970 |
| # | criterion | verdict |
|---|---|---|
| 1 | DARVO arm rate non-trivial, "not 0/500" | **not a flat fail.** **0** under greedy; **811000 per cell** under rank-75 and rank-95, baseline *and* H1. It fails for a seat that regulates and passes for one that does not — see [CB-EV-0031](CB-EV-0031-a-seat-that-does-not-regulate.md) |
| 2 | ATTACK selection rises for some subpopulation | **mixed.** Greedy attacks **0** times; rank-75's ATTACK *fell* 604 → 180 at 2p; the unregulated seat's rose 0 → 726 |
| 1 | DARVO arm rate non-trivial, "not 0/500" | **depends entirely on the policy, and H1 does not improve it for the middle one.** Zero under greedy in both. Under rank-75, H1 **reduces** arms to zero at 3p and above. Only rank-95 — a seat that always attacks — arms freely, and it does so **under the baseline too** |
| 2 | ATTACK selection rises for some subpopulation | **no, for the policy the criterion is about.** Greedy attacks 0 times under both. rank-75's attacks fall 604 → 180 at 2p and **592/923/389 → 0** at 3p/4p/6p. Only the deliberately unregulated seat of CB-EV-0031 rises, and it never wins |
| 3 | group success does not collapse | **fails hardest** — greedy's wins go **165 → 0** (3p), **190 → 0** (4p), **200 → 0** (6p) |
| 4 | Bond/GROUND stay better than DARVO | holds, but **vacuously**: DARVO never fires |

View file

@ -45,9 +45,10 @@ rather than a claim. SHARED GROUND, 200 games per cell, all 200 ran, and
| 4p | 0 | 0 | 0 | 4 | 0 | 1600 | 800 | **5** |
| 6p | 0 | 0 | 0 | 4 | 0 | 2400 | 1200 | **5** |
**H1's mechanism does reach the unregulated seat.** DARVO arms **2 per
seat per game**, scaling with the table — where the earlier figure was a
flat 400 in every cell, which was an artifact of the broken policy.
**H1's mechanism does reach the unregulated seat.** DARVO arms **exactly
1 per seat per game** — 600/(200×3), 800/(200×4), 1200/(200×6) — and 0.91
at 2p. The first correction said **2** per seat per game, which is wrong
by a factor of two and was caught by round 2 (#3).
**Criterion 3's verdict is corrected.** The original said reactive *"wins
nothing, at any seat count"*. **False.** At 2p it wins **66 against
@ -95,9 +96,11 @@ and a final-Stress signature of `[5, 5, 4, 4, 4, 4]`.
**Withdrawn on two independent grounds, both found by the review.**
1. **The constant was the broken policy.** With the one-arm-override
policy, `darvo` scales with seats (363/600/800/1200). There is no
constant to explain.
1. **The constant was the broken policy** — but a constant remains, and
saying otherwise was wrong (round 2, #12). With the one-arm-override
policy `darvo` is **exactly `seats × games`** at 3p and above. The
constant moved from 400 to `n·games`; it did not dissolve, and whatever
forces exactly one arm per seat per game is still unexplained.
2. **Disabling H1-B changes the arm count by exactly zero**, in every
cell, under the corrected policy. The effect attributed to H1-B's
arithmetic was an interaction with the undeclared preference deltas.
@ -132,13 +135,20 @@ CB-WP-0038 asserted it because the code shares `resolve_attack`; nothing
tested it, and the withdrawn §3 ran straight through this path.
`h1b_soothes_the_darvo_stage_attack_too` now covers it, mutation-verified.
**Round-5 pressure did not reach the score — a real defect, not a
reporting one.** `end_round_events` scored from `self` while H1-A's
pressure went into `work`, and `score` reads Stress for the GR-E03 and
GR-E04 tiebreaks. The final round's pressure was invisible to **the two
modes CB-EV-0030 reports on**. Fixed, and the test uses the case that
actually bites: uniform pressure preserves an ordering, so it takes the
**clamp** at 5 to collapse a gap and change who wins.
**Round-5 pressure did not reach the score.** `end_round_events` scored
from `self` while H1-A's pressure went into `work`, and `score` reads
Stress for the GR-E03 and GR-E04 tiebreaks. Fixed, and the test uses the
case that actually bites: uniform pressure preserves an ordering, so it
takes the **clamp** at 5 to collapse a gap and change who wins.
**Its impact was claimed and never measured, and the measurement is
zero** (round 2, #5). This file said the pressure was invisible to *"the
two modes CB-EV-0030 reports on"* without checking. The reviewer ran
~72,000 games across all three modes with a divergence detector and found
**no divergence at all**; `attack-value`'s output is byte-identical with
the fix and without it. **The defect is real in principle and its only
witness is a hand-constructed board.** It is worth fixing and it changed
nothing that has been reported.
**Inert arms, and a second wrong-subject error caught on the way.** A
DARVO arm at the End of Round 5 can never advance a stage — `GameEnded`
@ -156,8 +166,15 @@ follows immediately — and ground-game's criterion 1 is about DARVO
every cell**, because it tested `g.rounds >= 5` — a property of the
*game*, not of the *event*. Every arm in every completed game was marked
inert, which briefly looked like "criterion 1 is not met after all". An
arm is inert when no `RoundEnded` follows it; that figure matches the
reviewer's independent 29-of-363.
arm is inert when no `RoundEnded` follows it.
**29-of-363 was independently confirmed twice**, and the citation for it
was wrong the first time (round 2, #6): this file credited
`CB-REV-0001`, which does not contain the figure. Round 1's reviewer *did*
report it — 29 of 363 at 2p, 8% — but only in its message, and it was
never transcribed into the review file. **The record is now corrected
there.** Round 2 re-derived it independently, by a different definition
(no later DARVO event names that player), and agrees in every cell.
**Criterion 1 stands as met**: 92% of arms at 2p and all of them above are
live.

View file

@ -20,9 +20,12 @@ use games_ground::{Action, GroundCommand, GroundState, ScoringMode, Variant};
/// Greedy, with GROUND demoted below ATTACK. Nothing else differs.
///
/// **The copy is deliberate.** `GreedyPolicy::rank` is private, and the
/// point is to change one line of it while holding the rest identical —
/// calling into it would make that impossible to demonstrate.
/// **It delegates.** An earlier version re-typed an abridged copy of
/// greedy's ranking and called changing one line of the copy "one
/// preference"; it differed in five, and the seat burned its Freedom
/// token in round one of every game. This comment used to argue for the
/// copy, which gave a future maintainer written cover to restore it
/// (CB-REV-0002 #11).
struct Reactive;
impl Policy for Reactive {
@ -72,6 +75,10 @@ impl Policy for Reactive {
/// inlined so the peak metric cannot silently disagree with setup.
const START_STRESS: u8 = 2;
/// Games per cell. Named once so the assertion and the banner cannot
/// disagree — the banner said "200 games per cell" over 196-game columns.
const GAMES: u32 = 200;
struct Cell {
games: u32,
won: u32,
@ -99,7 +106,7 @@ fn sweep(mode: ScoringMode, variant: Variant, players: u8, reactive: bool) -> Ce
setup_fails: 0,
inert_arms: 0,
};
for seed in 0..200u64 {
for seed in 0..GAMES as u64 {
let Ok(mut st) = GroundState::setup(
&Setup {
players,
@ -174,20 +181,31 @@ fn sweep(mode: ScoringMode, variant: Variant, players: u8, reactive: bool) -> Ce
}
if let games_ground::GroundEvent::StressSet { player, stress } = e {
held.insert(*player, *stress);
c.peak_stress = c.peak_stress.max(*stress);
}
// Read from `held`, which is the point. The first correction
// BUILT `held`, then took the max over event payloads anyway
// and silenced the unused binding with `let _ = held;` — so
// the comment described code that did not exist (CB-REV-0002
// #10). The published figures were right only because the
// START_STRESS floor made the two agree.
c.peak_stress = c
.peak_stress
.max(held.values().copied().max().unwrap_or(START_STRESS));
}
let _ = held;
}
// Reported as a REFUSAL, not left for a reader to notice a short
// column. CB-REV-0001 #11: `games != 200` was printed, never asserted.
// **`games`, not `games + setup_fails`** (CB-REV-0002 #1).
//
// The first version asserted the SUM — and a refused setup increments
// `setup_fails` while skipping `games`, so the sum is invariant under
// exactly the failure it claimed to catch. It could only ever fire on
// a `play` error, the path that was already instrumented. Verified by
// injecting setup failures: green, exit 0, and a full table printed
// over 196-game columns under a banner reading "200 games per cell".
assert_eq!(
c.games + c.setup_fails,
200,
"{players}p {variant:?}: {} games ran and {} setups failed — the cell is short, \
so every number in it is over a sample nobody chose",
c.games,
c.setup_fails
c.games, GAMES,
"{players}p {variant:?}: only {} of {GAMES} games ran ({} setups refused) — \
the cell is short, so every number in it is over a sample nobody chose",
c.games, c.setup_fails
);
if c.setup_fails > 0 {
eprintln!(
@ -202,7 +220,7 @@ fn main() {
println!("CB-WP-0039 — does H1 reach a seat that does not regulate?\n");
println!("`reactive` is greedy with ONE preference changed: GROUND is");
println!("demoted below ATTACK, so the seat never grounds to shed Stress.");
println!("SHARED GROUND; `won` is group success; 200 games per cell.\n");
println!("SHARED GROUND; `won` is group success; {GAMES} games per cell.\n");
for (vlabel, variant) in [
("BASELINE ground-darvo-r0", Variant::Baseline),

View file

@ -2409,12 +2409,21 @@ mod tests {
panic!("round 5 did not end the game");
};
assert!(outcome.group_success, "the fixture must qualify");
// **The oracle names the expected winners** (CB-REV-0002 #4).
// The first version asserted only that the set CHANGED, so
// reversing GR-E03's tiebreak — making HIGHER Stress win —
// left it green: the set still changed, just wrongly.
let pre = s.score();
assert_ne!(
outcome.winners, pre.winners,
"the outcome ignores the final round's pressure — scored from `self`. \
pre={:?} post={:?}",
pre.winners, outcome.winners
assert_eq!(
pre.winners,
vec![seats[1]],
"before the pressure, the seat at Stress 4 wins on the tiebreak"
);
assert_eq!(
outcome.winners,
vec![seats[0], seats[1]],
"after it both are clamped to 5, so they share"
);
}

View file

@ -163,3 +163,17 @@ 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"
[[gate]]
id = "CB-REV-0002/7"
name = "variant panels"
target = "panels"
checks = "the H1 measurement harnesses actually run, and a short cell fails rather than printing a number a reader must notice"
notes = """
Registered because the second adversarial review asked what the harness
would report if the work silently stopped, and the answer was "green, and
nothing else": `attack-value` and `regulation` were wired into no target,
so every figure in CB-EV-0030 and CB-EV-0031 came from a manual run of an
ungated binary -- including the assertion added to catch short cells,
which was unreachable from `make`.
"""

View file

@ -97,7 +97,10 @@ Stress is ever added. ATTACK is the sole inbound pressure.
- **11 — `regulation.rs` still skips setup failures silently.** Conceded
and **fixed**: counted, and a short cell now fails an assertion rather
than printing a number a reader must notice.
- **13 — round-5 arms, and scoring from `self`.** Both **fixed**, and the
- **13 — round-5 arms, and scoring from `self`.** *(Round 1's reviewer
reported the arm figure as **29 of 363 at 2p, 8%**, under its
`StrictReactive`. That number was omitted when this file was written and
later cited to it — see CB-REV-0002 #6. Recorded here now.)* Both **fixed**, and the
second was a real defect rather than a reporting one: `score` reads
Stress for the GR-E03/GR-E04 tiebreaks, so the final round's pressure
was invisible to the two modes CB-EV-0030 reports on. Inert arms are now

120
reviews/CB-REV-0002-h1.md Normal file
View file

@ -0,0 +1,120 @@
# CB-REV-0002 — re-review of the H1 corrections
Round 2 of the tier-L review. A **fresh** agent, not the round-1 reviewer,
because a re-review by the same reviewer inherits its own sampling. Run
2026-08-08 against the corrections made after
[`CB-REV-0001`](CB-REV-0001-h1.md).
> **Verdict: not approvable.** Three FATAL, five SERIOUS.
>
> **The substance of round 1's corrections held. The claims about them did
> not**, and two of the defects were introduced *by* the corrections.
The reviewer was briefed on the specific risk — **the author of the
corrections is the author of the errors** — and re-derived on five
independent seed windows.
---
## What it could not break, after trying
Stated first, because it is what round 1's corrections were for.
- **`Reactive` is genuinely one arm different.** The reviewer built a
`PureDelegate` with the same loop shape and *no* override, hashed every
command of every game, and got **identical output to `GreedyPolicy` in
8/8 cells over 500 seeds**, both variants. Round 1's worst finding is
properly closed. *Residual: nothing asserts it; it is true by
construction, not by test.*
- **All five replacement controls are non-inert**, re-verified by
mutation, and the SOLVE control is red on nine alternate seeds.
- **`h1b_soothes_the_darvo_stage_attack_too` really covers that path** —
disabling H1-B *only* for the DARVO extra Attack reddens that test alone.
- **The inert-arm metric is right**, agreeing exactly with an independent
definition in every cell of every window.
- **Every number in CB-EV-0031 §1 reproduces** and is stable across five
windows.
- **`score()` from `work` is correct per the delta and changes no baseline
path.**
## The three fatal ones
### 1. The fix for round 1's #11 did not fix it
The assertion was `games + setup_fails == 200`. **A refused setup
increments `setup_fails` and skips `games`, so the sum is invariant under
exactly the failure it claimed to catch.** Injecting setup failures gave
exit 0 and a full table over 196-game columns, under a banner reading
"200 games per cell".
**Conceded.** Now `assert_eq!(c.games, GAMES)`, verified to exit 101 under
injected failures, with `GAMES` named once so the banner and the assertion
cannot disagree.
### 2. The correction to the selective-column FATAL was itself selective
CB-EV-0030's correction quoted *"811000 per cell … baseline **and** H1"*
and, twelve lines away, *"311000"*. **Both came from the baseline
rank-75 row.** Under H1, rank-75 arms are `2p 59, 3p 0, 4p 0, 6p 0`.
**Conceded, and it is the same error twice.** The remedy for choosing a
favourable column chose a favourable column. **Every cell is now printed**
rather than summarised, because two attempts at a range produced three
wrong numbers — and the corrected verdict is the opposite of the one it
replaced: under rank-75, **H1 reduces DARVO arms to zero** at 3p and above.
### 3. "DARVO arms 2 per seat per game" is off by a factor of two
It is **1 per seat per game**, exactly, at every band: 600/(200×3),
800/(200×4), 1200/(200×6). Deterministic across five windows.
**Conceded.** A headline number, wrong, in a corrected section.
## The serious ones
| # | challenge | outcome |
|---|---|---|
| 4 | the tiebreak test's oracle was `assert_ne!` — "the set changed", not "changed correctly". Reversing GR-E03's tiebreak left it green | **conceded, fixed.** The oracle now names the expected winners before and after; the reviewer's M7 goes red |
| 5 | the #13 "real defect" was claimed to affect the reported modes and never measured | **conceded.** ~72,000 games across three modes: **zero divergences**, `attack-value` byte-identical. Real in principle, and its only witness is a constructed board. Now stated |
| 6 | "matches the reviewer's independent 29-of-363" cites a file that does not contain it | **conceded, with a correction to the correction.** Round 1's reviewer *did* report 29 of 363 — in its message, never transcribed into the review file. So the figure is real and the **record** was not. Recorded in CB-REV-0001 now |
| 7 | `regulation.rs` and `attack-value.rs` are run by **no gate** | **conceded, and it is the sharpest finding.** Every number in both evidence files came from a manual run of an ungated binary — including the assertion added for #1, unreachable from `make`. Now `make panels`, registered in `gates.toml` |
| 8 | `edition-check`'s sibling handling was self-certifying: it read the file list *out of PROVENANCE*, crashed instead of failing on an absent file, and "coverage" counted Markdown lines | **conceded — a defect introduced by the correction.** Siblings are now discovered on disk; an absent recorded file FAILs; undocumented siblings FAIL. **It found a real gap on its first run**: `Rules_Text.csv` was vendored with no digest |
## The minor ones
- **9 — the CSV falsifier ran over YAML.** Fixed; it now filters to `.csv`.
- **10 — "peak Stress held" was dead code.** `held` was built, never read,
and `let _ = held;` kept clippy quiet; the metric was still max over
payloads plus a floor. **The published numbers were right by
coincidence.** Now actually read from `held`, and the figures are
unchanged — which is how it stayed hidden.
- **11 — `Reactive`'s doc comment still argued for the copy** that round 1
ruled fatal, giving cover to restore it. Rewritten.
- **12 — §3's withdrawal ground was wrong.** *"There is no constant to
explain"* — there is: `darvo == seats × games`, exactly. The constant
moved from 400 to `n·games` rather than dissolving, and **what forces
exactly one arm per seat per game is still unexplained.**
- **13 — assorted**, including that the delta's DARVO clause is
conditional (*"if they share the same path"*) where the test's comment
says "outright".
## What the reviewer could not check
Digest freshness against upstream (`edition-check` reports
`upstream not checked out` when the sibling repo is absent — it did not
have it); several untouched modules; ground-game's design note; felt-play.
---
## The pattern, now visible across two rounds
Round 1 found five fatal errors of the form *correct computation, wrong
subject*. **Round 2 found three more, in the corrections to them** — an
assertion invariant under its own target, a selective fix for a selective
report, and a rate wrong by a factor of two.
> **A correction is new work and deserves the scrutiny of new work.** It
> arrives feeling like a conclusion, and that is exactly when it is least
> examined.
**Round 3 is owed.** Same reason as round 2.

View file

@ -59,10 +59,27 @@ def vendored_files():
# control unverified — correctly. Recorded relative to `editions/`.
SIBLINGS = "../"
# Discovered ON DISK, not read out of PROVENANCE (CB-REV-0002 #8). The
# first version listed siblings by reading the digest block, which made
# three controls vacuous at once: a recorded-but-absent file could never
# be reported missing (it was in `present` by construction), an absent one
# raised FileNotFoundError from `digest` instead of failing with the
# designed message, and "the sibling packages are covered" counted lines
# in a Markdown file -- it passed with all three files deleted.
SIBLING_GLOBS = ("catalog.yaml", "experiments/*/rules_delta.yaml", "experiments/*/*.csv")
def sibling_files():
"""Recorded paths that live beside the edition rather than in it."""
return sorted(f for f in recorded() if f.startswith(SIBLINGS))
"""Sibling packages that are on disk, as `../`-relative paths."""
import glob as _glob
root = os.path.join(ROOT, EDITION, SIBLINGS)
out = []
for pattern in SIBLING_GLOBS:
for hit in _glob.glob(os.path.join(root, pattern)):
rel = os.path.relpath(hit, os.path.join(ROOT, EDITION))
out.append(rel.replace(os.sep, "/"))
return sorted(out)
def check():
@ -72,7 +89,7 @@ def check():
siblings = sibling_files()
present = vendored_files() + siblings
undocumented = [f for f in vendored_files() if f not in want]
undocumented = [f for f in vendored_files() + siblings if f not in want]
if undocumented:
print(f" [FAIL] vendored with no recorded digest: {', '.join(undocumented)}")
rc = 1
@ -81,10 +98,15 @@ def check():
print(f" [FAIL] a digest is recorded for a file that is not here: {', '.join(missing)}")
rc = 1
for name in present:
for name in [f for f in present if f.endswith(".csv")]:
if name not in want:
continue
have = digest(os.path.join(ROOT, EDITION, name))
path = os.path.join(ROOT, EDITION, name)
if not os.path.exists(path):
print(f" [FAIL] {name} is recorded but not on disk")
rc = 1
continue
have = digest(path)
# `../x` resolves out of the edition dir, which is the point.
if have != want[name]:
print(f" [FAIL] {name} does not match its recorded digest")
@ -96,7 +118,7 @@ def check():
# ADR-0015 D3's falsifier, checked rather than asserted: the hand
# reader handles commas inside quotes and NOTHING ELSE. A doubled
# quote or an embedded newline means `csv` is the answer after all.
for name in present:
for name in [f for f in present if f.endswith(".csv")]:
raw = open(os.path.join(ROOT, EDITION, name), encoding="utf-8-sig").read()
if '""' in raw:
print(f" [FAIL] {name} contains a doubled quote — ADR-0011's revisit")
@ -116,7 +138,7 @@ def check():
print(" [----] upstream not checked out — freshness UNVERIFIED")
print(f" expected {UPSTREAM_DIR}")
return rc
for name in present:
for name in [f for f in present if f.endswith(".csv")]:
up = os.path.join(UPSTREAM_DIR, name)
if not os.path.exists(up):
print(f" [FAIL] {name} is not in upstream — where did it come from?")
@ -151,9 +173,9 @@ def self_test():
chk("digests match the real files",
all(digest(os.path.join(ROOT, EDITION, f)) == want[f] for f in everything))
chk("the sibling packages are covered",
len(sibling_files()) >= 3,
"catalog.yaml and rules_delta.yaml decide WHAT WE MEASURED; "
"CB-WP-0038 claimed digests for them and recorded none")
all(f in want for f in sibling_files()) and len(sibling_files()) >= 3,
"catalog.yaml and rules_delta.yaml decide WHAT WE MEASURED; the first "
"version counted lines in PROVENANCE and passed with the files deleted")
# The control that matters: a changed byte must be detected.
import tempfile