فهرست منبع

fix(skills): fleetflow ff-status — derive lane state from last journal record

A respawn appends a fresh "started" AFTER an old "result", which means the
lane is running again. The old last-result-wins derivation took tail -1 of
the result records and showed done/failed for a re-spawned lane. Derive
last_type via `select(.id==$id)|.type | tail -1`; "started" -> running,
else fall through to the result rc/artifact as before.

Test: a started,result,started journal -> state running; the same lane
result-last -> still done (no regression on the common path).

Co-Authored-By: Claude <noreply@anthropic.com>
0xDarkMatter 2 هفته پیش
والد
کامیت
f7fc8c9d42
2فایلهای تغییر یافته به همراه28 افزوده شده و 1 حذف شده
  1. 6 1
      skills/fleetflow/scripts/ff-status.sh
  2. 22 0
      skills/fleetflow/tests/run.sh

+ 6 - 1
skills/fleetflow/scripts/ff-status.sh

@@ -57,9 +57,14 @@ emit() {
   for id in $(jq -r 'select(.type=="started") | .id' "$RUNDIR/journal.jsonl" | awk '!seen[$0]++'); do
   for id in $(jq -r 'select(.type=="started") | .id' "$RUNDIR/journal.jsonl" | awk '!seen[$0]++'); do
     brain="$(jq -r --arg id "$id" 'select(.id==$id) | .brain' "$RUNDIR/journal.jsonl" | head -1)"
     brain="$(jq -r --arg id "$id" 'select(.id==$id) | .brain' "$RUNDIR/journal.jsonl" | head -1)"
     phase="$(jq -r --arg id "$id" 'select(.type=="started" and .id==$id) | .phase // "build"' "$RUNDIR/journal.jsonl" | head -1)"
     phase="$(jq -r --arg id "$id" 'select(.type=="started" and .id==$id) | .phase // "build"' "$RUNDIR/journal.jsonl" | head -1)"
+    # state derives from the LAST journal record for this id: a respawn appends a
+    # fresh "started" AFTER an old "result", which means the lane is running again.
+    # last-result-wins (tail -1 of result records) would wrongly show done/failed.
+    last_type="$(jq -r --arg id "$id" 'select(.id==$id) | .type' "$RUNDIR/journal.jsonl" | tail -1)"
     rc="$(jq -r --arg id "$id" 'select(.type=="result" and .id==$id) | .rc' "$RUNDIR/journal.jsonl" | tail -1)"
     rc="$(jq -r --arg id "$id" 'select(.type=="result" and .id==$id) | .rc' "$RUNDIR/journal.jsonl" | tail -1)"
     art="$(jq -r --arg id "$id" 'select(.type=="result" and .id==$id) | .artifact' "$RUNDIR/journal.jsonl" | tail -1)"
     art="$(jq -r --arg id "$id" 'select(.type=="result" and .id==$id) | .artifact' "$RUNDIR/journal.jsonl" | tail -1)"
-    if [ -z "$rc" ]; then state="running"; finished=0
+    if [ "$last_type" = "started" ]; then state="running"; finished=0
+    elif [ -z "$rc" ]; then state="running"; finished=0
     elif [ "$rc" = "0" ]; then state="done"; finished=$(mtime "$art")
     elif [ "$rc" = "0" ]; then state="done"; finished=$(mtime "$art")
     else state="failed"; finished=$(mtime "$art"); fi
     else state="failed"; finished=$(mtime "$art"); fi
     started=$(mtime "$RUNDIR/$id.prompt.txt")
     started=$(mtime "$RUNDIR/$id.prompt.txt")

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

@@ -217,6 +217,28 @@ printf '%s' "$CL2" | awk -F'\t' '$1=="keeplane"&&$2=="kept"{f=1} END{exit !f}' &
 [ -d "$CLEANROOT/rc-cleanlane" ] || [ -d "$CLEANROOT/rc-dirtlane" ] || [ -d "$CLEANROOT/rc-keeplane" ] \
 [ -d "$CLEANROOT/rc-cleanlane" ] || [ -d "$CLEANROOT/rc-dirtlane" ] || [ -d "$CLEANROOT/rc-keeplane" ] \
   && bad "ff-clean: cache dir remains" || ok "ff-clean: cache dirs removed"
   && bad "ff-clean: cache dir remains" || ok "ff-clean: cache dirs removed"
 
 
+# --- state derivation (feature C): last journal record wins -----------------------
+# a respawn appends "started" AFTER an old "result" -> the lane is running again,
+# NOT done/failed (the last-result-wins bug this fixes).
+RD5="$REPO/.fleetflow/r5"; mkdir -p "$RD5"
+: > "$RD5/z.prompt.txt"   # mtime source for elapsed
+printf '%s\n' \
+  '{"type":"started","key":"v2:z","id":"z","brain":"sonnet","phase":"build","v":"1.1.0"}' \
+  '{"type":"result","key":"v2:z","id":"z","brain":"sonnet","rc":0,"artifact":"x"}' \
+  '{"type":"started","key":"v2:z","id":"z","brain":"sonnet","phase":"build","v":"1.1.0"}' \
+  > "$RD5/journal.jsonl"
+bash "$S/ff-status.sh" --run r5 --repo "$REPO" 2>/dev/null \
+  | jq -e '.lanes[]|select(.id=="z")|.state=="running"' >/dev/null \
+  && ok "status: respawned lane (started,result,started) is running" || bad "status: respawned lane state wrong"
+# regression guard: same lane with result-last is still done (common path unchanged)
+printf '%s\n' \
+  '{"type":"started","key":"v2:z","id":"z","brain":"sonnet","phase":"build","v":"1.1.0"}' \
+  '{"type":"result","key":"v2:z","id":"z","brain":"sonnet","rc":0,"artifact":"x"}' \
+  > "$RD5/journal.jsonl"
+bash "$S/ff-status.sh" --run r5 --repo "$REPO" 2>/dev/null \
+  | jq -e '.lanes[]|select(.id=="z")|.state=="done"' >/dev/null \
+  && ok "status: result-last lane is still done (no regression)" || bad "status: result-last state wrong"
+
 echo "=== $PASS passed, $FAILN failed ==="
 echo "=== $PASS passed, $FAILN failed ==="
 [ "$FAILN" = 0 ] || exit 1
 [ "$FAILN" = 0 ] || exit 1
 exit 0
 exit 0