Appearance
Handoff: SVC-015-queue-worker-batching-semantics ai-service queue worker batching semantics
Scope
- Service/area:
ai-serviceviaservices/ai-classifier-service - Task pack:
docs/agents/service-tasks/ai-service.md - Goal: accept the bounded native AI classification queue worker and batching slice using existing question-context and job contracts.
- Non-goals: public/default
/api/questions/ai-classify/*route promotion, usage/quota checks, wallet credit movement, provider secret storage, full legacy BullMQ parity, live browser proof, or changes to legacy source under/Users/velikho/Desktop/WORKING/HOCTAPAZ/.
Docs Read
go-platform/docs/agents/service-agent-execution-queue.jsongo-platform/docs/agents/service-agent-closeout-ledger.jsongo-platform/docs/agents/service-tasks/ai-service.mdgo-platform/docs/agents/agent-handoff-format.mdgo-platform/docs/api/ai-classifier-service.mdgo-platform/services/ai-classifier-service/README.md
Legacy Evidence Read
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.queue.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.worker.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.service.ts- Legacy source was read-only evidence only; no legacy files were changed.
Files Changed
go-platform/services/ai-classifier-service/internal/domain/classifier.gogo-platform/services/ai-classifier-service/internal/usecase/classifier.gogo-platform/services/ai-classifier-service/internal/usecase/classifier_test.gogo-platform/services/ai-classifier-service/internal/repository/memory.gogo-platform/services/ai-classifier-service/internal/repository/postgres.gogo-platform/services/ai-classifier-service/internal/repository/postgres_test.gogo-platform/services/ai-classifier-service/internal/http/server.gogo-platform/services/ai-classifier-service/migrations/000003_queue_worker_claims.sqlgo-platform/services/ai-classifier-service/api/openapi.yamlgo-platform/contracts/openapi/services/ai-classifier-service.yamlgo-platform/services/ai-classifier-service/README.mdgo-platform/docs/api/ai-classifier-service.mdgo-platform/docs/agents/service-tasks/ai-service.mdgo-platform/docs/agents/service-agent-closeout-ledger.jsongo-platform/docs/agents/service-agent-closeout-ledger.mdgo-platform/docs/agents/handoffs/SVC-015-queue-worker-batching-semantics.mdgo-platform/scripts/test/service-task-pack-coverage.sh
Behavior Implemented
- Added native worker claim fields to classification jobs:
attempts,maxAttempts,nextRunAt,lockedUntil, andworkerId. - Added memory and Postgres pending-job claim semantics. Postgres uses row locking with
FOR UPDATE SKIP LOCKED; both stores skip jobs with futurenextRunAt. - Added
ProcessNextPendingJobfor worker-owned execution. Worker failures requeue asPENDINGwith exponential backoff untilmaxAttempts, then end asFAILED. - Added opt-in HTTP runtime workers controlled by
AI_CLASSIFIER_WORKER_ENABLED, concurrency, poll interval, lease, max attempts, and retry-base-delay env values. - Reworked classification processing to call the provider in fixed batches of 5 question IDs, persist batch progress, and record one usage event per provider batch.
RetryJobandCancelJobnow clear worker claim state before reprocessing or cancelling.
Tests And Verification
- Command:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/... -count=1 - Result: passed.
- Evidence: all ai-classifier-service domain, provider, repository, usecase, HTTP, and cmd packages passed.
- Command:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/usecase -run 'TestClassifierServiceProcessJobBatchesProviderCalls|TestClassifierServiceProcessNextPendingJobRetriesWithBackoff' -count=1 -v - Result: passed.
- Evidence: targeted worker tests covered 12-question batching into 3 provider calls, batch usage metadata, retry backoff, delayed claim skip, and terminal max-attempt failure.
- Command:
make test-ai-classification-job-route-guard - Result: passed.
- Evidence: gateway route tables still keep public AI classification job/suggestion/SSE/error/cancel/generation/solution routes off the native AI classifier foundation.
- Command:
AI_CLASSIFICATION_JOB_SELF_TEST=1 make test-ai-classification-job-live - Result: passed.
- Evidence: self-test mode passed for the AI classification job live-smoke harness; this is not fresh real browser proof.
- Command:
make test-question-classification-apply-routes - Result: passed.
- Evidence: the exact classification apply route remains the allowed question-bank-service route; native AI classifier job routes were not promoted.
- Command:
ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path); puts "ok #{path}" }' services/ai-classifier-service/api/openapi.yaml contracts/openapi/services/ai-classifier-service.yaml - Result: passed.
- Evidence: service and contract OpenAPI YAML parsed.
- Command:
cmp -s services/ai-classifier-service/api/openapi.yaml contracts/openapi/services/ai-classifier-service.yaml && echo 'openapi mirror ok' - Result: passed.
- Evidence: service OpenAPI and mirrored contract are identical.
- Command:
make test-service-agent-closeout-ledger - Result: passed.
- Evidence: closeout ledger accepts the handoff path and keeps the broad rollout not-complete.
- Command:
make test-service-agent-execution-queue - Result: passed.
- Evidence: execution queue still points SVC-015 at
docs/agents/handoffs/SVC-015-queue-worker-batching-semantics.md. - Command:
make test-service-task-packs - Result: passed.
- Evidence: service task pack coverage accepts SVC-015 as a non-cutover accepted handoff while still requiring cutover-gated IAM/Profile rows to remain open.
- Command:
pnpm docs:build - Result: passed with the existing Rollup chunk-size warning.
- Evidence: VitePress built and rendered docs successfully.
Route State
- Legacy route: public
/api/questions/ai-classify/*,/api/questions/ai-generation/*,/api/questions/ai-solutions/*, and/api/attempts/:attemptId/submitstay legacy-proxied. - Native route: internal
/v1/ai-classifier/jobs*,/v1/ai/*/preview, and/v1/admin/ai-classifier/queue-summaryremain service/native routes for gateway adapters and tests. - Gateway state: no default route table was changed or promoted by this handoff.
- Rollback: keep
deploy/gateway/routes.jsonas the active default route table. Disable native background draining by leaving or settingAI_CLASSIFIER_WORKER_ENABLED=0; keep public AI job routes pointed at legacy until real runtime/browser proof and rollback evidence are attached.
Data Notes
- Tables touched:
classification_jobs. - Migration run: not run against a live database in this handoff.
- Migration added:
services/ai-classifier-service/migrations/000003_queue_worker_claims.sqladds claim/retry fields and pending/locked indexes. - Validation report: not applicable because no live data was migrated or backfilled.
Risks / Blockers
- This is a bounded native worker foundation, not full BullMQ parity. Legacy supports configurable job batch concurrency up to 5; this native slice processes batches sequentially with service-worker concurrency controlling parallel jobs.
- No fresh real gateway/browser proof was run; only static route guard and self-test live-smoke harness passed.
- Public route promotion remains blocked on usage/wallet boundary proof, provider settings ownership, real runtime worker proof with seeded fixtures, browser evidence, and rollback proof.
Next Step
- Run the native worker against disposable Postgres/question-bank fixtures with
AI_CLASSIFIER_WORKER_ENABLED=1, capture queue drain logs plus browser/SSE evidence, then decide whether a non-default gateway rehearsal can expose selected read-only job routes.