P3 internal#718: serve GET /templates selectable provider/model list FROM the registry (PR-A backend; NOT merged) #1977
Reference in New Issue
Block a user
Delete Branch "feat/internal-718-p3a-templates-from-registry"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
internal#718 P3 — PR-A (backend, no-merge): serve GET /templates selectable provider/model list FROM the registry. Base
main.First of three stacked P3 PRs. P3 = the "only-registered-selectable" + "no hardcoded provider knowledge" half. P3 is UI/API-affecting → CTO merge-go after the Five-Axis gate per the task brief. NOT merged.
What this does (retire-list surface #1)
GET /templates(workspace-server/internal/handlers/templates.goList) now ANNOTATES every registry-known runtime's template with an authoritative registry-served selectable list, sourced from the provider registry (workspace-server/internal/providers, the P2-A synced SSOT —ProvidersForRuntime/ModelsForRuntime/DeriveProvider/IsPlatform) instead of the template's hand-authoredconfig.yaml providers:/runtime_config.models:registry_backed: true when the runtime is in the registryruntimes:block.registry_providers: the runtime's NATIVE provider set, each withdisplay_name+auth_env(NAMES only) +billing_mode(platform_managedif the registryIsPlatformpredicate holds, elsebyok). The SSOT the canvas Provider dropdown consumes instead ofVENDOR_LABELS.registry_models: the runtime's NATIVE model ids, each annotated with its DERIVED provider (DeriveProvider) + thebilling_modethat provider implies. The canvas can render no model the registry doesn't list for the runtime ("only registered selectable"); shows the DERIVED provider's billing source (folds in the closed #1931's canvas intent).Design / constraints
Models/Providers/ProviderRegistryfields are UNCHANGED, so non-registry runtimes (external/mock/kimi/future third-party) and older canvases keep working — a runtime absent from the registry yieldsregistry_backed=false, no synthesized block.ResolveUpstream/ billingDeriveProvideruntouched (P1/P2). Templates' ownconfig.yaml providers:codegen untouched (P4).registryManifest()accessor; named distinctly from #1972'sproviderRegistry()to compile against current main (both wrap the same SSOT; unify in a trivial follow-up once #1972 lands).Tests / build (Phase 3)
TestTemplatesList_RegistryServesSelectableModels(a template's bogus model id never leaks; native ids present),TestTemplatesList_RegistryAnnotatesDerivedProviderAndBilling(derived provider + platform_managed/byok per model; provider display_name/auth_env/billing from registry),TestTemplatesList_NonRegistryRuntimeFallsOpenToTemplate(mock runtime:registry_backed=false, template fields untouched). All pre-existingTestTemplatesList_*stay green.go test ./internal/handlers ./internal/providersgreen;go build ./...OK;go vetclean;gofmt -lclean;golangci-lint run ./internal/handlers0 issues.Five-Axis (Phase 4)
Walked all five axes — Correctness (fail-open on every registry-error path;
nilauthEnv safe since claude-code kimi split is by exact id; un-derivable model served un-annotated not dropped), Readability (no finding), Architecture (FYI: trivial accessor unification with #1972 follow-up), Security (NAMES-only auth_env, no secrets;platformclosed-set can't be forged), Performance (sync.Once-memoized parse; per-model derive is O(small native set) on the list path only). Approve.cross-ref: internal#718 P3. Stacked PRs: PR-B canvas (consume + retire #4/#5), PR-C controlplane provisioner (#2).
SOP checklist (RFC#351 — peer
/sop-ackto satisfy each)templates_test.go— registry serves the selectable model set (a template's bogus model id never leaks; native ids present), derived-provider + platform_managed/byok annotation per model + provider display_name/auth_env/billing from the registry, and non-registry runtime (mock) fall-open (registry_backed=false, template fields untouched). All pre-existingTestTemplatesList_*stay green. Edge cases: empty-runtime template (fails open, no panic), un-derivable model (served un-annotated, not dropped), registry-load failure (fails open).GET /templatesListwalks the host configsDir + reads the embedded registry; it touches no DB (the handler has no Postgres dependency).go test ./internal/handlers ./internal/providersgreen locally.GET /templateson the synthetic tenant and confirm a claude-code template returnsregistry_backed:true+registry_providers/registry_models, and a non-registry runtime returnsregistry_backed:false.ProvidersForRuntime/ModelsForRuntime/DeriveProvider/IsPlatform) at the API boundary.providerRegistry()+LLMBillingMode*— one accessor/constant set), Security (NAMES-only auth_env, no secret values;platformclosed-set can't be forged by a template), Performance (sync.Once-memoized parse; per-model derive is O(small native set) on the list path only). Approve.Models/Providers/ProviderRegistryfields are kept ON PURPOSE (federation/non-registry-runtime + older-canvas back-compat, not a shim); the new registry fields ride alongside. No dead code introduced.feedback_build_integration_tag_before_push(CP PR-C built with -tags=integration),feedback_watch_latest_main_head_not_merge_commit(rebased onto post-P2-B main),feedback_pr_merge_conventions(merge commits, not WIP, not merged here),feedback_ci_status_check_combined_state(used combined/status),reference_providers_runtime_matrix_ssot(registry is the SSOT this consumes).c4b63c7cb3to2d0d070040APPROVED — independent dev-SOP Five-Axis review (agent-reviewer; author hongming != reviewer). internal#718 P3 PR-A: GET /templates serves the selectable provider/model list FROM the registry.
Axis 1 — Additive / no break: PASS. New struct fields are all
omitempty;registry_models/registry_providers/registry_backedare NEW JSON keys — template-servedmodels/providers/provider_registryare untouched.modelSpec.BillingModeisyaml:"-", so it is never populated on the template path and the existingmodelsarray serializes byte-identically (templates.go:329 builds Models from raw YAML only). 12 pre-existingTestTemplatesList_*are unmodified (diff appends only at templates_test.go:1329+). Non-registry runtime (mock, in knownRuntimes runtime_registry.go:86 but absent from the registryruntimes:block) ->ProvidersForRuntimeerrors -> enrichFromRegistry fails open, RegistryBacked=false, template fields kept (templates_registry.go:186-192).Axis 2 — Correctness of annotation: PASS. Verified against the registry SSOT (providers.yaml@main):
claude-opus-4-7is exact-listed only under anthropic-api -> DeriveProvider step-3 resolves it to anthropic-api -> byok;anthropic/claude-opus-4-7exact-listed only under platform -> platform -> platform_managed (matches tests). anthropic-oauth display_name "Claude Code subscription" + auth_env [CLAUDE_CODE_OAUTH_TOKEN] match. ThenilavailableAuthEnv is correct: every claude-code native id (incl. sonnet/opus/haiku) resolves via the exact-id path (len(exact)==1), never needing the auth-env tie-break. No hardcoded provider vocabulary reintroduced.Axis 3 — Reuse not dup: PASS. Confirmed
providerRegistry()+LLMBillingModePlatformManaged/LLMBillingModeBYOKalready exist on main (P2-B / #1972, llm_billing_mode.go). enrichFromRegistry reuses them; no re-implementation of the loader, DeriveProvider, or IsPlatform. (Nit: the PR-description "Design/constraints" bullet still says a distinctly-namedregistryManifest()accessor — that text is stale; the actual code correctly callsproviderRegistry(), as the file-header NOTE states. Cosmetic, non-blocking.)Axis 4 — No scope creep: PASS. Touches only 3 files under internal/handlers/. Does NOT hard-reject (un-derivable models served un-annotated, WARN-only — P4 deferred), does NOT touch proxy ResolveUpstream / billing DeriveProvider / templates config.yaml codegen.
Axis 5 — Tests / build / security: PASS. 3 new tests genuinely discriminate: bogus template model id never leaks into registry_models; derived provider+billing asserted by exact equality (anthropic-api/byok, platform/platform_managed, oauth display_name/auth_env); non-registry fail-open with template Models/Providers asserted unchanged. No secret-shaped strings; auth_env carries env-var NAMES only.
Merge remains gated on CI green (combined state currently pending) + the CTO merge-go per the P3 task brief. Not merging.
approve on current head (provider-SSOT P3, CTO keep-going)