Skip to content

Commit a3cb353

Browse files
committed
engine(suite): checkpoint after every cell, and the breach remedy is a clock move
PER CELL, NOT PER EGRESS COLUMN. The checkpoint is also the only progress signal the outside world has: watch-busbar's deadline arithmetic - the thing that decides whether a run can finish before the box self-terminates - reads the pulled checkpoint's cell count. On the 2026-08-05 busbar-152 run that count was up to five cells stale at column granularity, and during a streamable cell's long search the projection declared DEADLINE BREACH and told the operator to relaunch a healthy run that was pacing fine: 17 of 36 served while the checkpoint still said 12. A checkpoint cadence coarser than the decision it feeds turns the watchdog into a false-alarm generator. A cell is 14-30 minutes of measurement; serializing the snapshot costs seconds; the write was already atomic. The promote-guard-tolerant handling moves with the flush, unchanged: a mid-run guard trip is logged and skipped, only the final write may make it fatal. AND THE WATCHER'S ADVICE NO LONGER FORFEITS THE RUN. "Raise BENCH_MAX_MIN and relaunch NOW" throws away every measured cell to change a number that moves on the live box in two seconds: the death clock is `shutdown -h +N`, a cost backstop and not part of the instrument. The breach message now prints the in-place extension command (`shutdown -c; shutdown -h +720`) and reserves relaunching for a box that is already gone - which is exactly how the 2026-08-05 breach was actually resolved, keeping all 17 cells. engine tests 572 pass. suite.rs rides the board-event train; watch-busbar.sh is operator tooling and binds nothing.
1 parent a67de11 commit a3cb353

2 files changed

Lines changed: 53 additions & 47 deletions

File tree

engine/src/suite.rs

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,64 +1043,33 @@ pub fn run_suite_with(
10431043

10441044
let mut upstreams: HashMap<String, Upstream> = HashMap::new();
10451045
let mut any_served = false;
1046-
let mut last_egress: Option<String> = None;
10471046
let mut written: Option<Paths> = None;
10481047

1049-
// WRITTEN INCREMENTALLY, after every egress column, not held in memory and written once at the
1050-
// end: these runs take hours on a box with a hard self-termination timer, so a run interrupted
1048+
// WRITTEN INCREMENTALLY, after EVERY CELL, not held in memory and written once at the end:
1049+
// these runs take hours on a box with a hard self-termination timer, so a run interrupted
10511050
// partway through must not lose every cell it already measured. Partial progress that survives
10521051
// is worth more than a complete result that might not arrive.
10531052
//
1054-
// A PROMOTE-GUARD TRIP ON ONE OF THESE CHECKPOINTS IS NOT THE SAME EVENT AS ON THE FINAL WRITE.
1055-
// Every checkpoint before the last is, by construction, thinner than the finished run it is
1056-
// partway through, so tripping the guard against a fuller prior snapshot already on disk is
1057-
// expected mid-run, not a sign the run went bad. Propagating it with `?` used to abort this whole
1058-
// function - discarding every cell already measured for the rest of the grid and never reaching
1059-
// the final flush that would have carried the complete run. So a checkpoint trip is logged and
1060-
// skipped; only the FINAL flush below, once every column has been measured, may make the guard's
1061-
// refusal fatal.
1062-
// STREAMED, so the checkpoint above is a checkpoint. This was `for result in run_grid_with(...)`,
1063-
// which returns a Vec: the whole grid had to finish before the loop began, so the per-column
1053+
// PER CELL, NOT PER EGRESS COLUMN, and the 2026-08-05 busbar-152 run is why. The checkpoint is
1054+
// also the only progress signal the outside world has: watch-busbar's deadline arithmetic - the
1055+
// thing that decides whether a run can finish before the box self-terminates - reads the pulled
1056+
// checkpoint's cell count. At column granularity that count was up to five cells stale, and
1057+
// during a streamable cell's long search the projection declared DEADLINE BREACH and told the
1058+
// operator to relaunch a healthy run that was in fact pacing fine (17/36 served while the
1059+
// checkpoint still said 12). A checkpoint cadence coarser than the decision it feeds turns the
1060+
// watchdog into a false-alarm generator. A cell is ~14-30 minutes of measurement; serializing
1061+
// the snapshot costs seconds. The write is atomic (tmp+rename in write_snapshot), so a reader
1062+
// never sees a torn file.
1063+
//
1064+
// STREAMED, so the checkpoint really is one. This was `for result in run_grid_with(...)`,
1065+
// which returns a Vec: the whole grid had to finish before the loop began, so the incremental
10641066
// flush - and the promise in the comment above it - could never fire on an interrupted run.
10651067
// Busbar measured 16 of 36 cells over four hours and not one of them reached disk.
10661068
run::run_grid_streaming(&rc, cfg.min_conc, cfg.max_conc, metrics, &mut |result| {
10671069
let id = &result.outcome.id;
10681070
let ing = id.ingress.clone();
10691071
let eg = id.egress.clone();
10701072

1071-
if last_egress.as_deref() != Some(eg.as_str()) {
1072-
if let Some(finished_egress) = &last_egress {
1073-
match flush(cfg, &upstreams, any_served, Some(box_qualify.clone())) {
1074-
Ok(paths) => written = Some(paths),
1075-
Err(SnapshotError::PromoteGuard {
1076-
existing_served,
1077-
incoming_served,
1078-
}) => {
1079-
eprintln!(
1080-
"suite: checkpoint after egress column {finished_egress} not written yet \
1081-
({incoming_served} served so far vs {existing_served} on disk) - \
1082-
continuing to measure the rest of the grid"
1083-
);
1084-
}
1085-
// A CHECKPOINT THAT FAILS TO WRITE MUST NOT DISCARD THE CELLS STILL TO COME.
1086-
// This used to `return Err`, abandoning the rest of the grid over a write that
1087-
// the FINAL flush is about to attempt again anyway - the same reasoning the
1088-
// promote-guard arm above already applies, arriving by a different error. The
1089-
// failure is remembered, not swallowed: if the final write also fails, that is
1090-
// the error the run reports, and if it succeeds the run really is fine and this
1091-
// was transient. Either way, hours of measurement are not thrown away over a
1092-
// mid-run disk hiccup.
1093-
Err(e) => {
1094-
eprintln!(
1095-
"suite: checkpoint after egress column {finished_egress} failed to \
1096-
write ({e}) - continuing to measure; the final write decides the run"
1097-
);
1098-
}
1099-
}
1100-
}
1101-
last_egress = Some(eg.clone());
1102-
}
1103-
11041073
// THE EVIDENCE FOR THE VERDICT, not just the verdict: `status` and `body_snippet` are
11051074
// recorded on every cell, so an artifact can say what the gateway actually answered instead
11061075
// of just "does not serve" 36 times over. Otherwise a whole field declining for one
@@ -1204,6 +1173,7 @@ pub fn run_suite_with(
12041173

12051174
// Read before `entry` takes ownership of the key.
12061175
let configurable = cfg.manifest.egress.iter().any(|e| e == &eg);
1176+
let cell_label = format!("{ing}>{eg}");
12071177
upstreams
12081178
.entry(eg)
12091179
// `configurable` is whether this gateway can be POINTED at this upstream at all, which
@@ -1220,6 +1190,31 @@ pub fn run_suite_with(
12201190
})
12211191
.cells
12221192
.insert(ing, cell);
1193+
1194+
// THE CHECKPOINT, per cell - see the module note above on why this cadence is load-bearing.
1195+
// A PROMOTE-GUARD TRIP HERE IS NOT THE SAME EVENT AS ON THE FINAL WRITE: every checkpoint
1196+
// before the last is thinner than the finished run it is partway through, so tripping the
1197+
// guard against a fuller prior snapshot on disk is expected mid-run. Logged and skipped;
1198+
// only the FINAL flush below may make the guard's refusal fatal. Likewise a checkpoint that
1199+
// fails to write must not discard the cells still to come - the final write decides the run.
1200+
match flush(cfg, &upstreams, any_served, Some(box_qualify.clone())) {
1201+
Ok(paths) => written = Some(paths),
1202+
Err(SnapshotError::PromoteGuard {
1203+
existing_served,
1204+
incoming_served,
1205+
}) => {
1206+
eprintln!(
1207+
"suite: checkpoint after {cell_label} not written yet ({incoming_served} served \
1208+
so far vs {existing_served} on disk) - continuing to measure the rest of the grid"
1209+
);
1210+
}
1211+
Err(e) => {
1212+
eprintln!(
1213+
"suite: checkpoint after {cell_label} failed to write ({e}) - continuing to \
1214+
measure; the final write decides the run"
1215+
);
1216+
}
1217+
}
12231218
});
12241219

12251220
// The final write always happens, so a grid with a single egress column is not lost.

watch-busbar.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,20 @@ except Exception: print(0)' 2>/dev/null || echo 0)\"
103103
fi
104104
log "otb=$alive cells=$cells/$CELLS_TOTAL ($held)$verdict"
105105
# Loud, separate, and unmissable: the summary line above scrolls past in a log nobody reads closely.
106+
#
107+
# THE REMEDY IS AN IN-PLACE CLOCK EXTENSION, NOT A RELAUNCH. This used to say "Raise BENCH_MAX_MIN
108+
# and relaunch NOW", which throws away every cell already measured to fix a number that can be
109+
# changed on the live box in two seconds: the death clock is `shutdown -h +N`, a cost backstop and
110+
# not part of the instrument, and `shutdown -c` + a new `shutdown -h` moves it without touching the
111+
# measurement. The 2026-08-05 busbar-152 run is the case: the breach fired (on checkpoint-stale
112+
# arithmetic, as it happened - see the per-cell checkpoint note in engine/src/suite.rs - but the
113+
# remedy is the same when the breach is real), the clock was extended in place, and the run kept
114+
# every cell. Relaunching remains the advice ONLY when the box is already gone.
106115
[ -n "$verdict" ] && case "$verdict" in *"WILL NOT FINISH"*)
107116
log "DEADLINE BREACH - this grid cannot complete before the box self-terminates."
108-
log " Raise BENCH_MAX_MIN and relaunch NOW, or accept a partial grid. Waiting changes nothing." ;;
117+
log " EXTEND THE CLOCK IN PLACE - the measurement is untouched and no cell is lost:"
118+
log " ssh -i \$BENCH_STATE_DIR/gateway-bench-key.pem ubuntu@$IP 'sudo shutdown -c; sudo shutdown -h +720'"
119+
log " Relaunching forfeits every measured cell and is only right if the box is already dead." ;;
109120
esac
110121

111122
# NEVER READ "NOT STARTED YET" AS "FINISHED". At launch the box is still provisioning and `otb` is

0 commit comments

Comments
 (0)