v0.5.14
First batch from the 2026-07-12 improvement audit. Headline: job projects set / nudge were returning HTTP 500 in production — a documented feature was dead.
Fixed
job projects set/job projects nudgereturned HTTP 500 (config was mounted read-only)._persist_projectsrewroteconfig/projects.yamlin place, but that file is git-owned and bind-mounted:rointo the broker container, so every priority mutation raisedOSError: Read-only file system. Verified against the live broker. Config ownership is now split:config/projects.yamlstays the git-owned, read-only baseline (projects, their baseline priority, and theirdefaults:), while runtime priority changes persist to a writable overlay at$JOBD_STATE_DIR/project-priorities.yaml(defaulting to the SQLite DB's directory, so it is covered by the DB backup and never conflicts with agit mergeon redeploy). The overlay stores only deltas from the baseline, so a later config-as-code priority change still lands for any project nobody nudged. Because the endpoints only ever touchpriority,defaults:blocks are now git-only — making the old "one nudge silently erases everydefaults:block" round-trip hazard structurally impossible rather than merely tested against. See the new "Config vs state" section indocs/runbook.md.- Fleet-wide hang-guard
_defaultdefaults are now committed.idle_timeout_s: 3600/max_wall_s: 172800existed only as an uncommitted edit on the broker host — onegit cloneaway from being silently lost.
Added
GET /jobspagination. The endpoint used to return every row ever — on a broker with retention off, the entire history. It now acceptslimit(1..1000) andoffset, and always reports the full filtered count in theX-Total-Countheader.limitis deliberately opt-in (absent = all), becausegraphand--arraybuild over the complete set and a silent default cap at the API layer would quietly corrupt them.job list --limit/-n(default 50) and--all.job listused to dump the broker's entire job history. It is now bounded by default and prints… showing 50 of Nrather than truncating silently.--arraystill shows every member of an array (a truncated array misrepresents its shape). The MCP surface had already capped its own output; the human CLI had not.job --version/-V, and a barejobnow prints the full help (with the command list) instead of a terse usage error.JOBD_URLandJOBD_API_TOKENare documented injob --help, and the six commands that rendered as blank rows (cancel,wait,classify,projects list/set/nudge) now have help text.
Changed
- SQLAlchemy connection pool sized above the threadpools that feed it. The engine used QueuePool's defaults (
pool_size=5+max_overflow=10= 15 connections) while two threadpools open sessions against it — anyio's (40 tokens; every sync endpoint) andasyncio.to_thread's (the/next-jobdispatch scan). Past 15 concurrent DB-touching requests, the surplus threads blocked on pool checkout for up topool_timeout(30s) — a cliff reached exactly when the dispatch fan-out wakes every parked worker at once. Now 20/60, tunable viaJOBD_DB_POOL_SIZE/JOBD_DB_MAX_OVERFLOW.
Removed
test_full_suite_green, a meta-test that shelled out to re-run the entire suite as a subprocess. It contributed zero coverage, ignored the outer-m/-k/-xfilters, doubled the cost of every new test, and its own docstring scoped it to a migration that finished whenmcp-v1was tagged. CI already runs the full suite. Suite runtime: ~136s → ~66s.