Feature 04 task 5: Async job runner and GET /api/v1/jobs/{id} #29

Member

Implements Feature 04 task 5 (f7b9a2c8). Done-condition: internal/jobs persists jobs (kind, status queued|running|succeeded|failed, payload, result JSON, error, timestamps), runs them on a bounded worker pool with per-kind handlers registered at startup, supports progressive partial results, marks running jobs failed ("interrupted by restart") at boot, and serves GET /api/v1/jobs/{id} -> {id,kind,status,result,error}; results/errors contain no secrets; tests cover success, failure, progressive mid-run, restart recovery and unknown id 404; quality floor passes.

Schema reconciliation (orchestrator-approved, task note): migrations/0003_jobs_status.sql rebuilds jobs with the queued/running/succeeded/failed enum (no dependents; legacy pending/done mapped); schema_test and migrate_test updated.

Evidence (developer-run): gofmt empty; go vet clean; TZ=UTC go test -race all 12 packages, 501 tests/subtests pass; CGO_ENABLED=0 build ok; make sqlc-check no drift; real boot recovered a running job to failed/interrupted by restart and returned 401 for /api/v1/jobs/x without a session.

Base: feature/49e2f2e2-... (3ba78d4).

Implements Feature 04 task 5 (f7b9a2c8). Done-condition: internal/jobs persists jobs (kind, status queued|running|succeeded|failed, payload, result JSON, error, timestamps), runs them on a bounded worker pool with per-kind handlers registered at startup, supports progressive partial results, marks running jobs failed ("interrupted by restart") at boot, and serves GET /api/v1/jobs/{id} -> {id,kind,status,result,error}; results/errors contain no secrets; tests cover success, failure, progressive mid-run, restart recovery and unknown id 404; quality floor passes. Schema reconciliation (orchestrator-approved, task note): migrations/0003_jobs_status.sql rebuilds jobs with the queued/running/succeeded/failed enum (no dependents; legacy pending/done mapped); schema_test and migrate_test updated. Evidence (developer-run): gofmt empty; go vet clean; TZ=UTC go test -race all 12 packages, 501 tests/subtests pass; CGO_ENABLED=0 build ok; make sqlc-check no drift; real boot recovered a running job to failed/interrupted by restart and returned 401 for /api/v1/jobs/x without a session. Base: feature/49e2f2e2-... (3ba78d4).
dev_agent added 1 commit 2026-10-03 15:07:18 -04:00
Add async job runner and GET /api/v1/jobs/{id}
CI / ci (pull_request) Successful in 1m29s
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (2 high)
CI / ci (push) Successful in 1m27s
f37f240b26
internal/jobs persists jobs in the jobs table, which is also the queue: a
bounded pool of workers claims queued jobs oldest first. Handlers are
registered per kind before Start with typed payloads and results, and can
publish partial results to the row while they run. Only typed results and
*jobs.Failure messages are stored; any other handler error is logged and
stored as "internal error", so dependency error text cannot surface a
secret. Start fails jobs a previous process left running with
"interrupted by restart" before any worker runs.

GET /api/v1/jobs/{id} returns {id, kind, status, result, error} inside the
protected /api/v1 group; an unknown id is a 404 not_found.

Migration 0003_jobs_status rebuilds jobs with status queued, running,
succeeded, failed, mapping legacy pending and done rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent review — Codex gpt-6-sol, reasoning high

Verdict: CHANGES_REQUIRED
Reviewed SHA: f37f240b26c533890eb80fab6005b5fccecadd89

{
  "verdict": "CHANGES_REQUIRED",
  "reviewed_sha": "f37f240b26c533890eb80fab6005b5fccecadd89",
  "summary": "The lifecycle, progressive result, route, and migration code match the requested behavior on inspection. The diff leaves migrations 0000–0002 and dependencies unchanged. gofmt and go vet passed. The race tests could not start because the read-only filesystem blocks Go's temporary build directory; sqlc-check was not run because it regenerates files. Two unmet done-conditions block approval.",
  "findings": [
    {
      "id": "F1",
      "severity": "high",
      "location": "cmd/server/main.go:70",
      "failure_case": "The production server cannot execute any job kind. A queued job is claimed and then failed because no handler exists.",
      "evidence": "Startup creates and starts the runner without calling jobs.Register. The only registrations in the repository are in tests; internal/jobs/jobs.go:271–275 fails jobs with unregistered kinds.",
      "suggested_correction": "Register the required production handlers before Start, and test startup with an enqueued job of a registered kind."
    },
    {
      "id": "F2",
      "severity": "high",
      "location": "internal/jobs/jobs.go:145",
      "failure_case": "A handler can publish a typed result with an exported credential string, or return a Failure whose Message contains a credential; either value reaches the jobs row and GET response. An ordinary handler error containing a credential is also logged verbatim.",
      "evidence": "The generic result type is unrestricted and is passed directly to json.Marshal at lines 146 and 155. Failure.Message is stored unchanged at lines 299–306, and other handler errors are logged at line 304. TestSecretsNeverStored covers only fields deliberately hidden from JSON and does not exercise either leak.",
      "suggested_correction": "Use explicitly vetted result fields and controlled public failure codes or messages, avoid logging raw handler errors, and test secret values in exported result fields and Failure messages."
    }
  ]
}

Posted unaltered by review_agent. This is transport, not a second review.

## Independent review — Codex `gpt-6-sol`, reasoning high Verdict: **CHANGES_REQUIRED** Reviewed SHA: `f37f240b26c533890eb80fab6005b5fccecadd89` ```json { "verdict": "CHANGES_REQUIRED", "reviewed_sha": "f37f240b26c533890eb80fab6005b5fccecadd89", "summary": "The lifecycle, progressive result, route, and migration code match the requested behavior on inspection. The diff leaves migrations 0000–0002 and dependencies unchanged. gofmt and go vet passed. The race tests could not start because the read-only filesystem blocks Go's temporary build directory; sqlc-check was not run because it regenerates files. Two unmet done-conditions block approval.", "findings": [ { "id": "F1", "severity": "high", "location": "cmd/server/main.go:70", "failure_case": "The production server cannot execute any job kind. A queued job is claimed and then failed because no handler exists.", "evidence": "Startup creates and starts the runner without calling jobs.Register. The only registrations in the repository are in tests; internal/jobs/jobs.go:271–275 fails jobs with unregistered kinds.", "suggested_correction": "Register the required production handlers before Start, and test startup with an enqueued job of a registered kind." }, { "id": "F2", "severity": "high", "location": "internal/jobs/jobs.go:145", "failure_case": "A handler can publish a typed result with an exported credential string, or return a Failure whose Message contains a credential; either value reaches the jobs row and GET response. An ordinary handler error containing a credential is also logged verbatim.", "evidence": "The generic result type is unrestricted and is passed directly to json.Marshal at lines 146 and 155. Failure.Message is stored unchanged at lines 299–306, and other handler errors are logged at line 304. TestSecretsNeverStored covers only fields deliberately hidden from JSON and does not exercise either leak.", "suggested_correction": "Use explicitly vetted result fields and controlled public failure codes or messages, avoid logging raw handler errors, and test secret values in exported result fields and Failure messages." } ] } ``` --- Posted unaltered by `review_agent`. This is transport, not a second review.
dev_agent added 1 commit 2026-10-03 15:13:12 -04:00
Register a built-in noop job kind and store only failure codes
CI / ci (push) Successful in 1m27s
CI / ci (pull_request) Successful in 1m27s
f160c9f3a9
The server now registers the framework's built-in noop kind through
registerJobKinds before the runner starts, so production can run a job; a
noop job succeeds with {"ok":true}. Each feature's kinds are registered in
the same function.

Handlers report failure only with a *jobs.Failure code their kind declared
at registration. The runner stores and returns only that code, or
internal_error, interrupted or unknown_kind, and logs only the job id, kind,
code and a fixed reason: handler error text is never stored, returned or
logged. Results implement jobs.Public, whose PublicJSON names exactly the
data that is stored and returned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev_agent added 1 commit 2026-10-03 15:16:37 -04:00
Store "interrupted by restart" for jobs recovered at boot
CI / ci (push) Successful in 1m27s
CI / ci (pull_request) Successful in 1m28s
985fea6639
Boot recovery now writes the required fixed text to the job error column,
and the recovery test asserts that literal text instead of the constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev_agent added 1 commit 2026-10-03 15:20:32 -04:00
Retry job claims after a database error instead of waiting for a wake
CI / ci (push) Successful in 1m27s
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (pull_request) Successful in 1m29s
899f917fa4
A claim that failed with anything but sql.ErrNoRows parked the worker
on the wake channel, so a job queued across a restart (which has no
wake token) stayed queued until another enqueue or restart. The worker
now retries such a claim with backoff from 50ms doubling to 5s, reset
on any successful or empty claim, and stops promptly on shutdown. The
claim error is logged by category only.

The claim query is a Runner field so a test can inject one failing
claim; TestClaimErrorIsRetried drops the wake token and asserts the
queued job still runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_agent left a comment
Member

Independent review — Codex gpt-6-sol, reasoning high

Verdict: APPROVED
Reviewed SHA: 899f917fa48c9cc0541e006480e7bb865c3cfd90

{
  "verdict": "APPROVED",
  "reviewed_sha": "899f917fa48c9cc0541e006480e7bb865c3cfd90",
  "summary": "The prior fixes are present. The runner persists job states and progressive results, retries transient claim errors, recovers interrupted jobs, and serves the session-protected GET route. The migration applied in memory to fresh, 0001, and 0002 schemas. gofmt and go vet passed. The Go race test could not start because the read-only sandbox blocks Go’s temporary build directory; sqlc-check was unavailable because sqlc is not installed.",
  "findings": []
}

Posted unaltered by review_agent. This is transport, not a second review.

## Independent review — Codex `gpt-6-sol`, reasoning high Verdict: **APPROVED** Reviewed SHA: `899f917fa48c9cc0541e006480e7bb865c3cfd90` ```json { "verdict": "APPROVED", "reviewed_sha": "899f917fa48c9cc0541e006480e7bb865c3cfd90", "summary": "The prior fixes are present. The runner persists job states and progressive results, retries transient claim errors, recovers interrupted jobs, and serves the session-protected GET route. The migration applied in memory to fresh, 0001, and 0002 schemas. gofmt and go vet passed. The Go race test could not start because the read-only sandbox blocks Go’s temporary build directory; sqlc-check was unavailable because sqlc is not installed.", "findings": [] } ``` --- Posted unaltered by `review_agent`. This is transport, not a second review.
approver_agent merged commit e050736178 into feature/49e2f2e2-c9c1-4c1b-8d66-753ceeadbf75-platform-services 2026-10-03 15:24:03 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#29