mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-25 09:34:21 +08:00
backlot: fix all five dogfood findings (F-01..F-05)
- F-01: cost bar crit (red) state past 90% of budget - F-02: normalize() hardens fetched board state against sparse payloads - F-03: /thumb 404s for videos with no extractable poster frame instead of serving raw video bytes - F-04: checkpoint artifact path refs only resolve inside the project dir - F-05 (board half): stall detection — in_progress stage with no disk activity >10min renders red 'stalled?' + header badge flips to STALLED?; verified against the real wedged why-cities-glow project - eval harness from dogfood session committed (visual regression + interaction smoke, capture watcher, server/gate test suites) + regression tests for each finding; 46 backlot tests green, visual eval green (restage-before-capture note logged)
This commit is contained in:
@@ -31,6 +31,11 @@ FALLBACK_STAGES = [
|
||||
# How long (seconds) without filesystem activity before a board reads "idle".
|
||||
LIVE_WINDOW_SECONDS = 5 * 60
|
||||
|
||||
# An in_progress stage with no filesystem activity for this long is flagged
|
||||
# as possibly stalled (F-05: a wedged agent must be visible, not silent —
|
||||
# heartbeat checkpoints and tool events both reset the clock).
|
||||
STALL_WINDOW_SECONDS = 10 * 60
|
||||
|
||||
|
||||
def _read_json(path: Path) -> Optional[dict]:
|
||||
"""Read a JSON file, returning None on any failure."""
|
||||
@@ -84,13 +89,22 @@ def _load_pipeline_meta(pipeline_type: Optional[str]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _resolve_artifact(project_dir: Path, value: Any) -> Optional[dict]:
|
||||
"""Checkpoint artifacts may be inline dicts or path strings — resolve both."""
|
||||
"""Checkpoint artifacts may be inline dicts or path strings — resolve both.
|
||||
|
||||
Path references are only followed INSIDE the project directory: a
|
||||
checkpoint must not be able to pull arbitrary JSON from elsewhere on
|
||||
disk onto the board (F-04).
|
||||
"""
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
if isinstance(value, str) and value:
|
||||
p = Path(value)
|
||||
if not p.is_absolute():
|
||||
p = project_dir / value
|
||||
try:
|
||||
p.resolve().relative_to(Path(project_dir).resolve())
|
||||
except (ValueError, OSError):
|
||||
return None
|
||||
return _read_json(p)
|
||||
return None
|
||||
|
||||
@@ -553,6 +567,16 @@ def load_board_state(project_dir: Path) -> dict[str, Any]:
|
||||
last_activity = _last_activity(project_dir)
|
||||
now = time.time()
|
||||
|
||||
# Stall detection: an in_progress stage that stopped writing anything.
|
||||
for stage_entry in stages:
|
||||
if (
|
||||
stage_entry["status"] == "in_progress"
|
||||
and last_activity
|
||||
and (now - last_activity) > STALL_WINDOW_SECONDS
|
||||
):
|
||||
stage_entry["stalled"] = True
|
||||
stage_entry["stalled_minutes"] = int((now - last_activity) / 60)
|
||||
|
||||
state: dict[str, Any] = {
|
||||
"project_id": project_id,
|
||||
"title": marker.get("title") or meta_json.get("name") or project_id.replace("-", " ").title(),
|
||||
|
||||
Reference in New Issue
Block a user