Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
3.3 KiB
3.3 KiB
Step 6 — Jobs and stats
Read 00-shared.md, the plan (§2 host facts, §3, §4.2, §9.1 sparklines, §12,
§14 in full, §15 jobs_enabled/journal_lines, §16, §17 "Stats refactor" and
"Jobs"), src/main.rs (lock_holder, cmd_* helpers, GenerateOptions),
src/curate/telemetry.rs (stats), systemd/, and the handoffs for steps
1–5. This is plan §19 item 6.
Deliverables
src/jobs.rs(§14.1): theJobenum,parse(regex^[a-z0-9-]+$and the listed forms only),name,unit,description,takes_lock,dangerous. Register inlib.rs.daily-epub job run <name>(§14.2) inmain.rs: parse (unknown → exit 2 with the catalogue), take the lock vialock_holderwhentakes_lock(), find-or-insert therequestedjobs row →running, run the mapped command in-process with the same functionsmainuses, setok/failed,finished_at,run_id(generate, fromGenerateOutcome.run_id), a one-linemessage; non-zero exit on failure. The job lifecycle helpers live insrc/jobs.rsas db functions or onDb.systemd/daily-epub-job@.service(copy of the generate unit with the%idescription andjob run %i),systemd/50-daily-epub.rulesexactly as §14.3,SupplementaryGroups=systemd-journalondaily-epub.service.JobRunner(§14.4): finalize the trait from step 1's stub;SystemdRunnerwithtokio::process::Command, 10 s timeout, stderr in the error;MockRunnerwith recorded calls and scripted statuses;DisabledRunnerwhenjobs_enabled = false.servepicks the runner from config.src/web/dashboard/jobs.rs+dashboard/jobs.html+dashboard/job.html: the catalogue cards (Start withdata-confirmfordangerous(), a date input on thegenerate-<date>card), the jobs table,POST /dashboard/jobs/{name}(409 + flash for a duplicaterequested/runningunit; insert;runner.start; failed start marks the row),GET /dashboard/jobs/{id}(row, liveUnitStatus, journal tail ofserver.journal_linesin<pre>, the 30-second "unit exited before the job started" rule).reload_if_changedon every job start (§4.2).app.jsrefreshes the job page every 5 s whilerequested/running.- Stats (§12): refactor
telemetry::statsintostats_data -> StatsData+render_stats_text(&StatsData) -> Stringwith byte-identical CLI output (a test pins it against a seeded DB before and after);GET /dashboard/statswith?days=14|30|90, the tables, three server-rendered SVG sparklines via_sparkline.html, the retriever yield table. - Overview sparklines (§9.1): cost per run, selected per run, generation seconds over the last 30 non-dry runs, using the same partial.
- Tests (§17 "Stats refactor", "Jobs"):
Job::parseaccepts the catalogue and the dated form, rejects../x, uppercase and unknown names;POST /dashboard/jobs/{name}inserts a row and callsMockRunner::startwith the right unit; a running duplicate is refused; a failed start marks the row;job runin-process (a--skip-llm-style config with mocked providers, e.g.features-pruneorprofile-rebuildagainst a temp DB) flipsrequested → running → ok; the polkit rule file is present and its regex matches the unit name format (string test); stats text unchanged.