Gateway P2 Task 7 (Job API + manager) — verify + fix report
NOTE:
.superpowers/sdd/task-7-report.mdalready existed and holds an UNRELATED
report ("Task 7: Queue events + opt-in early SSE + keepalives", dated Jul 4).
To avoid clobbering it this report was written totask-7-jobs-report.md.
Branch: feat/gateway-p2-jobs-ocr
Commit: efa29d153328167750f5da58c9769768e55d43f4
Subject: feat(p2): Job API endpoints + manager single-job path + server wiring
Status: GREEN — build, vet, race (jobs+server), and full suite all pass. Committed.
Results
| Step | Command | Result |
|---|---|---|
| Build | go build ./... |
clean (BUILD_OK) |
| Vet | go vet ./... |
clean (VET_OK) |
| Race — targeted | go test -race ./internal/jobs/ ./internal/server/ |
ok jobs 3.655s, ok server 1.479s |
| Full suite | go test ./... |
all ok |
Full-suite package results:
? cmd/gateway [no test files]
ok internal/audit 2.195s
ok internal/config 0.018s
ok internal/idem 0.013s
ok internal/jobs 1.686s
ok internal/pool 1.634s
ok internal/proxy 2.568s
ok internal/registry 0.049s
ok internal/server 0.258s
Fixes made
1 fix — test fixture in internal/jobs/manager_test.go (mgrFixture, ~lines 48-54)
Symptom: TestManagerRunsJobToCompletion (and every other manager test) hung; go test
hit the 5-minute wall. The stack dump showed a worker parked in Manager.process at
manager.go:265 — the slot-admission select waiting on tk.Ready() — while the test's
deferred m.Stop(context.Background()) deadlocked in wg.Wait() because that worker never
returned.
Root cause: The fixture built pools directly from config.Slots{Total: 1}, leaving
BatchMax = 0. Every fixture job is submitted with config.ClassBatch. In
pool.localPool.pump (pool.go:124) a batch ticket is only admitted when
activeBatch < slots.BatchMax; with BatchMax = 0 that is never true, so batch jobs are
NEVER admitted and the worker blocks for the full 30-minute SlotWaitTimeout.
This is a fixture-only defect, not a manager/pool bug: production config never has
BatchMax = 0. config.go:132-133 normalizes BatchMax to Total whenever it is <= 0,
and main.go builds pools from that validated config. The fixture bypasses
config.Load/validation, so it must set BatchMax itself to mirror the normalized
production value.
Change: set BatchMax: 1 alongside Total: 1 on both fixture upstreams (t7m-ocr,
t7m-ext) — i.e. config.Slots{Total: 1, BatchMax: 1}, the exact value validation would
have produced. No test assertion was weakened; the single-slot admission discipline the
tests rely on (release-on-every-terminal-state, sequential 2-job runs) is preserved.
- Slots: config.Slots{Total: 1}},
+ Slots: config.Slots{Total: 1, BatchMax: 1}}, (x2)
No changes were needed in manager.go, api.go, server.go, main.go, or api_test.go.
Note for review (latent, not triggered — no change made)
Manager.Stop(ctx) waits on wg.Wait() before calling m.cancel(), and
Manager.process's slot-admission select (manager.go:265) watches only tk.Ready(),
time.After(SlotWaitTimeout), and m.ctx.Done() — not stopClaim. If a worker is ever
blocked waiting for a slot at drain time, Stop will not cancel until SlotWaitTimeout
(default 30m) elapses, since cancel() runs only after wg.Wait() returns (or the Stop
ctx deadline fires — context.Background() has none). This was the amplifier that turned
the fixture bug into a full hang. It does not fire on the happy path (slot granted → worker
returns → Stop completes) and all tests are green, so per the "verify + fix defects, do not
redesign" scope it was left as-is. Worth a follow-up: have the slot-wait select also observe
stopClaim, or cancel before wg.Wait() in Stop.