diff --git a/tools/ci/canary-check.sh b/tools/ci/canary-check.sh index b5c9589b8..a6e1ee13c 100755 --- a/tools/ci/canary-check.sh +++ b/tools/ci/canary-check.sh @@ -24,6 +24,8 @@ # network's tip; with a feed line, tip_height must be within ten blocks of height + age_s * blocks_per_min / 60 # mining {minutes >= 5, refusals: 0, accepted_blocks >= 1, read_back} # shard {claimed: true, proved: true, paid_or_queued: "paid" | "queued", read_back}; for kind mac only +# or {unavailable: , mechanics: , +# allocation: , read_back} (main's ruling 9 October 2026 13:0x UK: a fact, not a failure) # {not_applicable: "no prover on Apple silicon", read_back} passes (Apple silicon has no prover) # quit {bounded: true, seconds, read_back}: the process ends on its own quit inside the bound # lines_read_back a list of the eight line names, each with an evidence path on a box @@ -121,6 +123,16 @@ for r_ in blocks: kind = str(r.get('kind', '')).lower() if kind == 'mac' and h.get('not_applicable'): # Apple silicon has no prover (9 October 2026, 01:1x UK): the mac block's shard line is n/a, read back like any line if not box_path(h.get('read_back')): red("shard.read_back is not a box path (the n/a line is read back too)") + elif h.get('unavailable'): + # main's ruling (9 October 2026, 13:0x UK, the 2.0.3 hive canary): a prover whose mechanics ran (a segment claimed, exported and + # proved, or the worklist read every pass) but found no segment inside the margin while the fleet's provers hold the worklist is a + # FACT, not a failure of the entry; the line names the mechanics proved, carries the proving lane's allocation sentence, and is read + # back like any line. It never replaces a shard that was claimed and refused for the entry's own fault (a dangling binary, a wrong + # program id, a verifier refusal): those stay claimed/proved/paid_or_queued, or the block fails. + if not str(h.get('unavailable')).strip(): red("shard.unavailable must name why no segment was available (one sentence)") + if not str(h.get('mechanics', '')).strip(): red("shard.mechanics must name what the prover proved on this box (the claim, export and proof steps that ran)") + if not str(h.get('allocation', '')).strip(): red("shard.allocation must carry the proving lane's allocation sentence") + if not box_path(h.get('read_back')): red("shard.read_back is not a box path (the unavailable line is read back too)") else: if h.get('not_applicable'): red(f"shard.not_applicable is accepted for kind mac only (this block is '{kind or 'artefact'}')") if h.get('claimed') is not True or h.get('proved') is not True: red("shard.claimed and shard.proved must both be true") @@ -192,6 +204,11 @@ b=copy.deepcopy(blk); b['kind']='hive'; b['sync']={'genesis_height':0,'tip_heigh json.dump({'sha':r['sha'],'artefacts':[m,f,t,b],'verdict':'PASS','recorded_at':'t','recorded_by':'x'}, open(p,'w'))" bash "$ME" "$SHA" --artefact mac >/dev/null 2>&1 || { echo "self-test failed: a mac block with the shard n/a was refused: $(bash "$ME" "$SHA" --artefact mac 2>&1)"; fails=1; } out=$(bash "$ME" "$SHA" --artefact fleet 2>&1) && { echo "self-test failed: a fleet block with the shard n/a passed"; fails=1; }; case "$out" in *"kind mac only"*) ;; *) echo "self-test failed: the n/a refusal was not named: $out"; fails=1 ;; esac + python3 -c "import json; p='$d/$SHA.json'; r=json.load(open(p)); b=r['artefacts'][3]; b['shard']={'unavailable':'no whole segment inside the margin while the fleet provers hold the worklist','mechanics':'segment 22344..22351 claimed, exported and proved; the records refused as outside the carrier window','allocation':'the proving lane: incumbents hold every whole segment until the lease rule lands','read_back':'build-1:/srv/canary/x/04-shard.txt'}; b['sync']['tip_height']=14189; json.dump(r, open(p,'w'))" + bash "$ME" "$SHA" --artefact hive >/dev/null 2>&1 || { echo "self-test failed: the hive shard.unavailable form with its three sentences was refused: $(bash "$ME" "$SHA" --artefact hive 2>&1)"; fails=1; } + python3 -c "import json; p='$d/$SHA.json'; r=json.load(open(p)); del r['artefacts'][3]['shard']['allocation']; json.dump(r, open(p,'w'))" + out=$(bash "$ME" "$SHA" --artefact hive 2>&1) && { echo "self-test failed: shard.unavailable without the allocation sentence passed"; fails=1; }; case "$out" in *"shard.allocation"*) ;; *) echo "self-test failed: the missing allocation sentence was not named: $out"; fails=1;; esac + python3 -c "import json; p='$d/$SHA.json'; r=json.load(open(p)); r['artefacts'][3]['shard']={'claimed':True,'proved':True,'paid_or_queued':'paid','read_back':'build-1:/srv/canary/x/04-shard.txt'}; r['artefacts'][3]['sync']['tip_height']=14100; json.dump(r, open(p,'w'))" bash "$ME" "$SHA" --artefact windows >/dev/null 2>&1 || { echo "self-test failed: a tip within ten blocks of the age-adjusted feed was refused: $(bash "$ME" "$SHA" --artefact windows 2>&1)"; fails=1; } out=$(bash "$ME" "$SHA" --artefact hive 2>&1) && { echo "self-test failed: a tip 85 blocks under the adjusted feed passed"; fails=1; }; case "$out" in *"not within ten blocks"*) ;; *) echo "self-test failed: the tip refusal was not named: $out"; fails=1 ;; esac echo "not json" > "$d/$SHA.json"; bash "$ME" "$SHA" >/dev/null 2>&1 && { echo "self-test failed: a non-JSON record passed"; fails=1; }