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.
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).
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>
{"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.
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>
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>
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>
{"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-services2026-10-03 15:24:03 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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).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>Independent review — Codex
gpt-6-sol, reasoning highVerdict: CHANGES_REQUIRED
Reviewed SHA:
f37f240b26c533890eb80fab6005b5fccecadd89Posted unaltered by
review_agent. This is transport, not a second review.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>Independent review — Codex
gpt-6-sol, reasoning highVerdict: APPROVED
Reviewed SHA:
899f917fa48c9cc0541e006480e7bb865c3cfd90Posted unaltered by
review_agent. This is transport, not a second review.