Task 1 report (Gateway P2) — config + registry + audit wiring for ocr-http/azure-di/classify upstreams
Branch: feat/gateway-p2-jobs-ocr · Scope: foundation only, additive, no behavior change to existing LLM upstreams.
Note: this file previously held the P0 "module scaffold" report; it is superseded here by the P2 Task 1 report.
What changed
internal/config/config.go
Extended Upstream with job-adapter fields (all ignored for LLM upstreams):
| Field | yaml | Purpose |
|---|---|---|
Adapter string |
adapter |
sync-http | azure-di | classify |
SubmitPath string |
submit_path |
adapter-specific submit path |
PollPath string |
poll_path |
async poll path |
ResultPath string |
result_path |
async result path |
OperationValue string |
operation |
audit operation this upstream emits (ocr | classify) |
Updated the Type doc comment to include classify.
Validation added in Load (after the ProbePath default, before the slots check — inherits the pre-existing "≥1 endpoint + ≥1 model", duplicate-id/model, and class checks that already run for every upstream):
- When Type ∈ {ocr-http, classify}: Adapter MUST be one of sync-http|azure-di|classify; empty or unknown → error.
- OperationValue defaults from Type when unset: classify → classify, otherwise ocr.
- OperationValue, if set, MUST be ocr or classify; anything else (e.g. chat) → error.
- The pre-existing "≥1 endpoint + ≥1 model" and slots.total > 0 requirements already apply to all upstreams, so ocr-http/classify inherit them unchanged.
internal/registry/registry.go
Added package function IsJobUpstream(u *config.Upstream) bool → true for Type ∈ {ocr-http, classify}, so a later task routes sync (LLM proxy) vs job (async Job API). Resolve(model) was already type-agnostic (maps every upstream's Models), so ocr-http/classify models resolve with no change. On-prem enforcement untouched (see below).
internal/audit/event.go
Added exported operation constants for the schema-v1 enum (none existed before; handler used string literals):
OperationChat, OperationEmbed, OperationOCR, OperationClassify, OperationJob, OperationJobChild.
Schema version and field set unchanged — pure additive constant block. proxy/handler.go intentionally left untouched (still uses literals) to keep the diff additive and existing behavior byte-identical.
How on-prem enforcement extends to the new types
Enforcement is not in the registry or in config.Load; it is the runtime, class-based, type-agnostic predicate in proxy/handler.go:188:
up.Class == "external_dev" && !cfg.AllowExternalUpstreams → 403.
Because it keys off Class (validated to on_prem|external_dev for every upstream) and never on Type, it already covers ocr-http/classify the moment the Job API routes a resolved upstream through the same predicate (a later task). I deliberately did not add an allow_external_upstreams gate to config.Load — the existing code loads external_dev LLM upstreams fine and refuses them at request time, so a load-time gate would change existing behavior. The registry test asserts an external_dev ocr-http upstream resolves with Class preserved so the existing enforcement path refuses it.
Tests (namespace-safe: unique t1-* ids/models, filtered map lookups by id, no bare counts)
config:TestLoadOCRClassifyUpstreams(fields parse; operation default =ocr; explicitocr/classifypreserved),TestLoadRejectsMissingOrBadAdapter(missing adapter, unknown adapter, invalid operation all rejected).registry:TestResolveJobUpstreamsAndIsJobUpstream(resolvet1-paddleocr→ocr upstream & IsJobUpstream true; classify true; llm false; unknown→ErrUnknownModel),TestResolveExternalDevOCRStillClassGated.
RED (before implementation)
config_test.go: paddle.Adapter undefined / SubmitPath / OperationValue / PollPath ...
registry_test.go: unknown field Adapter in struct literal; undefined: IsJobUpstream
FAIL (build failed) both packages
GREEN (after)
--- PASS: TestLoadOCRClassifyUpstreams
--- PASS: TestLoadRejectsMissingOrBadAdapter
--- PASS: TestResolveJobUpstreamsAndIsJobUpstream
--- PASS: TestResolveExternalDevOCRStillClassGated
go build ./... OK · go vet ./... OK · go test ./... all packages ok (audit, config, idem, pool, proxy, registry, server).
Files touched
internal/config/config.go,internal/config/config_test.gointernal/registry/registry.go,internal/registry/registry_test.gointernal/audit/event.go