<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Job status endpoint. The current run status of every scheduled job, keyed by job id, so
//   the scheduler page can light up running indicators on a poll without the user opening
//   anything.
//
// OPERATIONAL MODEL
//   Status is not stored centrally — each job writes its own .json stat file beside its log,
//   and this endpoint reads the schedule to know which ids exist, then collects one file per
//   id. A job with no stat file has never run and is omitted entirely, which is how the page
//   tells "never run" apart from "ran and finished".
//
// DESIGN PRINCIPLES
//   The recorded status is cross-checked against the process table.
//       A stat file saying "running" is only believed while /proc/<pid> still exists. A
//       runner killed mid-job — OOM, reboot, kill -9 — never gets to write its own failure,
//       so trusting the file alone would leave the page showing a spinner forever.
//
//   Reports status only.
//       No timestamps, no output, no exit codes. This is polled frequently and exists to
//       drive indicator state; the detail views fetch what they need from log.php.
//
// OPERATIONAL SAFEGUARDS
//   Read-only. Nothing here starts, stops, or reaps a job.
//
//   Every file read degrades to an empty object.
//       @file_get_contents with a ?: '{}' fallback and a ?: [] after json_decode, so a stat
//       file that is unreadable or caught mid-write yields status 'unknown' rather than a
//       fatal that would blank every indicator on the page.
//
//   A dead runner is reported as error, never as still running.
//       This is the safe direction to be wrong in: a job wrongly shown as finished prompts
//       someone to look, whereas one wrongly shown as running is silently ignored forever.
//
//   Known limit: PID reuse.
//       /proc/<pid> existing does not prove it is still *this* job's process. On a host that
//       has churned through the pid space a recycled pid could hold a dead job in 'running'.
//       Accepted rather than fixed — the alternative is a start-time comparison against
//       /proc/<pid>/stat, which is more machinery than an indicator light justifies.
//
// REQUEST
//   GET, no parameters
//
// RESPONSE
//   {"<job id>":"running"|"success"|"error"|"unknown", …}
//   Jobs that have never run are absent from the object entirely.
//
// DEPENDS ON
//   include/scheduler.php   vv_schedule_load(), vv_job_stat_path()
//   LOG_DIR                 <job>.json stat files written by run_job.sh
// ═══════════════════════════════════════════════════════════════════════════════════════════════
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/scheduler.php';

$result = [];
foreach (array_keys(vv_schedule_load()) as $id) {
    $statFile = vv_job_stat_path($id);
    if (!file_exists($statFile)) continue;
    $stat   = json_decode(@file_get_contents($statFile) ?: '{}', true) ?: [];
    $status = $stat['status'] ?? 'unknown';
    if ($status === 'running' && !empty($stat['pid']) && !file_exists("/proc/{$stat['pid']}")) {
        $status = 'error';
    }
    $result[$id] = $status;
}
echo json_encode($result);
