From 1a500c760fef70fd8ede5760383804c276d5fa71 Mon Sep 17 00:00:00 2001 From: Rhea Rafferty Date: Sun, 9 Aug 2026 11:08:48 +0000 Subject: [PATCH 1/2] Give a skipped readback criterion its own state, and count criteria instead of declaring 12 The criteria set was the last instrument in this job still running on two states. When build_assets_legacy is absent the value-for-value comparison and the row-survival check fall into an `else` that prints "(no build_assets_legacy; nothing to compare the copy against)" and the run goes green. That is a criterion silently not executing - exactly the shape `exit 2` was introduced elsewhere to prevent, in the one place it had not been applied. It is latent rather than active: the table still exists, so the branch is not taken today. It becomes permanent the moment task #114 drops the table, at which point the deploy log keeps printing a reassuring sentence forever while checking nothing. check() now increments `evaluated` skip() new; prints `SKIP : ` and increments `skipped` else reports both criteria as SKIP rather than one parenthetical end prints `evaluated N criteria, M skipped, ... failed`, and warns on M>0 Skips are loud, not red. A skip can be legitimate, and failing the deploy on one would hold production hostage to a comparison source we intend to delete. What it must not be is quiet: a permanently skipped criterion is itself the event worth seeing, and it announces itself on every deploy rather than waiting for someone to remember that a deletion elsewhere retired a check here. Counted, not declared, for the same reason the workflow lint reports "N of M YAML files" with both figures counted: this gate has been described in writing as "12 criteria", and a number kept in prose stays at 12 while the code drifts. Today the declared number happens to be right, and "happens to be right" is how it goes wrong later. Deliberately NOT bundled into #114's deletion, which was the earlier plan. That deletion is irreversible, needs its own explicit authorisation, and may well be executed under time pressure - a fix riding along with it is a fix riding the least careful moment. Done separately, while the table still exists, the change is verifiable now: both branches were exercised (legacy present -> 2 evaluated, 0 skipped; legacy absent -> 0 evaluated, 2 skipped; a real mismatch -> still fails), which is not possible once the else branch is the only reachable one. Design is Sentinel's (give the skipped state a name); counting rather than asserting the total is Gogo's. Co-Authored-By: Claude Opus 5 --- .github/workflows/deploy-hands-server.yml | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-hands-server.yml b/.github/workflows/deploy-hands-server.yml index e3ee4cb..9081fcf 100644 --- a/.github/workflows/deploy-hands-server.yml +++ b/.github/workflows/deploy-hands-server.yml @@ -300,7 +300,20 @@ jobs: } fail=0 + evaluated=0 + skipped=0 + + # Three states, matching every other instrument here. The criteria set was the + # last thing in this job still running on two: a criterion whose comparison + # source was absent fell into an `else`, printed a parenthetical, and the run + # went green - which is a criterion silently not executing, the same shape + # `exit 2` exists to prevent everywhere else. + # + # `skip` is not a softer `check`. A permanently skipped criterion is itself the + # event worth seeing: it says so on every deploy instead of waiting for someone + # to remember that a comparison stopped happening. check() { # check + evaluated=$((evaluated + 1)) if [[ "$2" == "$3" ]]; then echo " ok $1: $2" else @@ -309,6 +322,11 @@ jobs: fi } + skip() { # skip + skipped=$((skipped + 1)) + echo " SKIP $1: $2" + } + echo "apply outcome: ${APPLY_OUTCOME}" # These hold after every apply, whatever the migration did, so they stay @@ -465,7 +483,14 @@ jobs: lost=$(q "SELECT COUNT(*) AS n FROM build_assets_legacy l WHERE NOT EXISTS (SELECT 1 FROM build_assets n WHERE n.id = l.id)" | jq -r '.[0].n') check "every row in the retained copy reached the rebuilt table" "$lost" "0" else - echo " (no build_assets_legacy; nothing to compare the copy against)" + # Not a note in parentheses. These two criteria are not being evaluated, + # and once build_assets_legacy is dropped they never will be again - at + # which point this branch would have printed a reassuring sentence on + # every deploy forever while checking nothing. Reported as SKIP so the + # count below stops matching, and nobody has to remember that a deletion + # elsewhere retired a criterion here. + skip "every carried row matches the retained copy value for value" "no build_assets_legacy to compare against" + skip "every row in the retained copy reached the rebuilt table" "no build_assets_legacy to compare against" fi fi @@ -486,6 +511,20 @@ jobs: got=$(pnpm exec wrangler --version 2>/dev/null | tail -1 | tr -d 'v ') check "wrangler matches the version the failure behaviour was measured on" "$got" "$MEASURED_WRANGLER" + # Counted, not declared. We have described this gate as "12 criteria" in + # writing, and a number kept in prose stays at 12 while the code drifts - the + # same reason the workflow lint reports "N of M YAML files" with both figures + # counted. A criterion falling into a skip shows up here as a smaller number on + # its own, with nobody remembering to edit anything. + echo "evaluated ${evaluated} criteria, ${skipped} skipped, $([[ "$fail" == "0" ]] && echo 0 || echo "1 or more") failed" + if [[ "$skipped" -gt 0 ]]; then + # Not a failure: a skip can be legitimate, and turning it red would make the + # deploy hostage to a comparison source we intend to delete. It is loud + # instead, and it stays loud, which is the property that "remember to fix it + # during the deletion" does not have. + echo "note: ${skipped} criterion/criteria did not execute this run - see SKIP lines above." >&2 + fi + exit "$fail" # Deploy the Worker BEFORE putting secrets. `wrangler secret put` refuses From f79a4da1a1524b634e62d44f88804e73ac105804 Mon Sep 17 00:00:00 2001 From: Rhea Rafferty Date: Sun, 9 Aug 2026 16:45:10 +0000 Subject: [PATCH 2/2] Add a fourth outcome: could-not-run, which blocks without claiming a measurement Sentinel caught that this PR fixed the confusion in one direction and left it standing in the other. `PRAGMA foreign_key_check` being refused by D1 was reported as `FAIL foreign_key_check did not run`, which sends someone looking for a foreign-key violation that was never measured. The blocking is deliberate and stays: the comment above that branch reasons that a refusal means D1 changed what it permits, and that is worth stopping for. What changes is what it says it is. ok ran, correct FAIL ran, wrong answer -> blocks, counted as evaluated CANNOT RUN did not run, and that is itself the anomaly -> blocks, NOT counted as evaluated SKIP did not run, acceptably -> does not block `cannot_run` sits between `skip` and a failing `check` on purpose. Reporting it as a skip would let a substrate change through; reporting it as a failure asserts a violation nobody observed. It is the same distinction the rest of this job now makes - could-not-run is not a result - applied to the one place that had it backwards. The summary line reports the count separately: "evaluated N criteria, M skipped, K could not run, ... failed". A criterion that stops being measurable shows up as K rising rather than as a fabricated verdict. Exercised: pragma clean -> evaluated=1 fail=0; pragma finds orphans -> evaluated=1 fail=1; pragma refused -> evaluated=0 unrunnable=1 fail=1, and the three print as distinguishable lines. Co-Authored-By: Claude Opus 5 --- .github/workflows/deploy-hands-server.yml | 24 +++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-hands-server.yml b/.github/workflows/deploy-hands-server.yml index 9081fcf..3d1dc90 100644 --- a/.github/workflows/deploy-hands-server.yml +++ b/.github/workflows/deploy-hands-server.yml @@ -302,6 +302,7 @@ jobs: fail=0 evaluated=0 skipped=0 + unrunnable=0 # Three states, matching every other instrument here. The criteria set was the # last thing in this job still running on two: a criterion whose comparison @@ -327,6 +328,19 @@ jobs: echo " SKIP $1: $2" } + # A criterion that could not execute, where not executing is itself the + # anomaly. Distinct from `skip` (did not run, and that is acceptable) and from + # `check` failing (ran, and the answer was wrong). It blocks like a failure + # because the policy for this one is that a refusal means the substrate + # changed - but it must not *say* "ran and failed", because sending someone to + # investigate a violation that was never measured is the same confusion this + # job now avoids everywhere else, pointed the other way. + cannot_run() { # cannot_run + unrunnable=$((unrunnable + 1)) + echo " CANNOT RUN $1: $2" >&2 + fail=1 + } + echo "apply outcome: ${APPLY_OUTCOME}" # These hold after every apply, whatever the migration did, so they stay @@ -352,11 +366,13 @@ jobs: if orphans=$(q "PRAGMA foreign_key_check" 2>/tmp/fkc.err | jq -r 'length'); then check "foreign_key_check is empty" "$orphans" "0" else - echo " FAIL foreign_key_check did not run. It is known to work on this" >&2 - echo " database, so a refusal now means the substrate changed:" >&2 + # Not `check ... FAIL`: nothing was measured, so there is no violation to + # go looking for. It still blocks - a refusal means D1 changed what it + # permits, and that is worth stopping for - but it is reported as what it + # is. + cannot_run "foreign_key_check is empty" "PRAGMA refused; it is known to work on this database, so the substrate changed" head -c 300 /tmp/fkc.err >&2 || true echo >&2 - fail=1 fi if [[ "${APPLY_OUTCOME}" == "success" ]]; then @@ -516,7 +532,7 @@ jobs: # same reason the workflow lint reports "N of M YAML files" with both figures # counted. A criterion falling into a skip shows up here as a smaller number on # its own, with nobody remembering to edit anything. - echo "evaluated ${evaluated} criteria, ${skipped} skipped, $([[ "$fail" == "0" ]] && echo 0 || echo "1 or more") failed" + echo "evaluated ${evaluated} criteria, ${skipped} skipped, ${unrunnable} could not run, $([[ "$fail" == "0" ]] && echo 0 || echo "1 or more") failed" if [[ "$skipped" -gt 0 ]]; then # Not a failure: a skip can be legitimate, and turning it red would make the # deploy hostage to a comparison source we intend to delete. It is loud