Explorar o código

fix(skills): fleetflow monitor — treat empty-lanes payload as a fetch miss

ff-status rewrites status.json via a status.json.tmp -> mv swap, so a reader
can catch a transient {"lanes":[]} (or an unparseable shard) mid-replace. The
old code only counted a network/parse failure as a miss and happily rendered
an empty grid on a torn write. Now an empty/missing lanes array is treated
exactly like a fetch miss: keep the previous render and count it toward the
3-miss warning. misses resets only on a real, non-empty payload.

Co-Authored-By: Claude <noreply@anthropic.com>
0xDarkMatter hai 1 semana
pai
achega
86c2c77b6d
Modificáronse 2 ficheiros con 16 adicións e 5 borrados
  1. 11 5
      skills/fleetflow/assets/ff-monitor.html
  2. 5 0
      skills/fleetflow/tests/run.sh

+ 11 - 5
skills/fleetflow/assets/ff-monitor.html

@@ -70,14 +70,20 @@ const esc = t => (t||"").replace(/[&<>"]/g, c => ({"&":"&amp;","<":"&lt;",">":"&
 const open = new Set();
 let misses = 0;
 async function tick() {
-  let d;
-  try { d = await (await fetch("status.json?t="+Date.now())).json(); misses = 0; }
-  catch (e) {
+  let d = null;
+  try { d = await (await fetch("status.json?t="+Date.now())).json(); }
+  catch (e) { d = null; }
+  // torn-write guard: ff-status rewrites status.json via status.json.tmp -> mv,
+  // so a reader can catch a transient {"lanes":[]} (or an unparseable shard)
+  // mid-replace. Treat an empty/missing-lanes payload like a fetch miss - keep
+  // the previous render and count it toward the 3-miss warning.
+  if (!d || !Array.isArray(d.lanes) || d.lanes.length === 0) {
     if (++misses >= 3) document.getElementById("foot").textContent =
-      "status.json unreachable — is ff-status --watch running?";
+      "status.json empty or unreachable — torn write, or ff-status --watch not running?";
     return;
   }
-  const lanes = d.lanes || [];
+  misses = 0;
+  const lanes = d.lanes;
   const running = lanes.filter(l => l.state === "running").length;
   document.getElementById("run").textContent = d.run;
   const oldest = Math.min(...lanes.map(l => l.started).filter(Boolean));

+ 5 - 0
skills/fleetflow/tests/run.sh

@@ -94,6 +94,11 @@ bash "$S/ff-status.sh" --run r1 --repo "$REPO" 2>/dev/null | jq -e '.lanes[] | s
   && ok "status: dry-run lane state done" || bad "status: lane state wrong"
 [ -f "$HERE/../assets/ff-monitor.html" ] && grep -q "status.json" "$HERE/../assets/ff-monitor.html" \
   && ok "monitor asset present + polls status.json" || bad "monitor asset missing"
+# torn-write guard: an empty/missing-lanes payload is treated as a fetch miss
+grep -q "torn-write guard" "$HERE/../assets/ff-monitor.html" \
+  && ok "monitor: empty-lanes torn-write guard present" || bad "monitor: torn-write guard missing"
+grep -q "d.lanes.length === 0" "$HERE/../assets/ff-monitor.html" \
+  && ok "monitor: guards on empty lanes array" || bad "monitor: empty-lanes guard logic missing"
 
 # --- escape guard ------------------------------------------------------------------
 check "escape guard: clean main" 0 bash "$S/ff-collect.sh" --check-main-clean --run r1 --repo "$REPO"