think
16px
820px

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: classifyclassify, 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; explicit ocr/classify preserved), TestLoadRejectsMissingOrBadAdapter (missing adapter, unknown adapter, invalid operation all rejected).
  • registry: TestResolveJobUpstreamsAndIsJobUpstream (resolve t1-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.go
  • internal/registry/registry.go, internal/registry/registry_test.go
  • internal/audit/event.go