P3 internal#718: serve GET /templates selectable provider/model list FROM the registry (PR-A backend; NOT merged) #1977

Merged
hongming merged 1 commits from feat/internal-718-p3a-templates-from-registry into main 2026-05-28 03:02:48 +00:00
Owner

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.go List) 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-authored config.yaml providers: / runtime_config.models:

  • registry_backed: true when the runtime is in the registry runtimes: block.
  • registry_providers: the runtime's NATIVE provider set, each with display_name + auth_env (NAMES only) + billing_mode (platform_managed if the registry IsPlatform predicate holds, else byok). The SSOT the canvas Provider dropdown consumes instead of VENDOR_LABELS.
  • registry_models: the runtime's NATIVE model ids, each annotated with its DERIVED provider (DeriveProvider) + the billing_mode that 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

  • Additive + federation-ready + fail-OPEN. The existing template-served Models/Providers/ProviderRegistry fields are UNCHANGED, so non-registry runtimes (external/mock/kimi/future third-party) and older canvases keep working — a runtime absent from the registry yields registry_backed=false, no synthesized block.
  • NO hard-reject. Templates whose model isn't registry-derivable are still served (WARN-level only). Legacy-vocab reconcile remains P4.
  • Proxy ResolveUpstream / billing DeriveProvider untouched (P1/P2). Templates' own config.yaml providers: codegen untouched (P4).
  • Uses a process-cached registryManifest() accessor; named distinctly from #1972's providerRegistry() to compile against current main (both wrap the same SSOT; unify in a trivial follow-up once #1972 lands).

Tests / build (Phase 3)

  • TDD: 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-existing TestTemplatesList_* stay green.
  • go test ./internal/handlers ./internal/providers green; go build ./... OK; go vet clean; gofmt -l clean; golangci-lint run ./internal/handlers 0 issues.

Five-Axis (Phase 4)

Walked all five axes — Correctness (fail-open on every registry-error path; nil authEnv 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; platform closed-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-ack to satisfy each)

  • Comprehensive testing performed: 3 new Go tests in 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-existing TestTemplatesList_* stay green. Edge cases: empty-runtime template (fails open, no panic), un-derivable model (served un-annotated, not dropped), registry-load failure (fails open).
  • Local-postgres E2E run: N/A — GET /templates List walks the host configsDir + reads the embedded registry; it touches no DB (the handler has no Postgres dependency). go test ./internal/handlers ./internal/providers green locally.
  • Staging-smoke verified or pending: scheduled post-merge — after deploy, probe GET /templates on the synthetic tenant and confirm a claude-code template returns registry_backed:true + registry_providers/registry_models, and a non-registry runtime returns registry_backed:false.
  • Root-cause not symptom: the selectable provider/model list was sourced from each template's hand-authored config.yaml rather than the provider-registry SSOT, so an unregistered option could be surfaced and provider knowledge was duplicated; this serves the list from the registry (ProvidersForRuntime/ModelsForRuntime/DeriveProvider/IsPlatform) at the API boundary.
  • Five-Axis review walked: Correctness (fail-open on every registry-error path; nil authEnv safe — claude-code kimi split is by exact id; un-derivable model served un-annotated), Readability (no finding), Architecture (reuses P2-B providerRegistry() + LLMBillingMode* — one accessor/constant set), Security (NAMES-only auth_env, no secret values; platform closed-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.
  • No backwards-compat shim / dead code added: No — additive only. The existing template-served Models/Providers/ProviderRegistry fields 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.
  • Memory/saved-feedback consulted: 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).
**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.go` `List`) 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-authored `config.yaml providers:` / `runtime_config.models`: - `registry_backed`: true when the runtime is in the registry `runtimes:` block. - `registry_providers`: the runtime's NATIVE provider set, each with `display_name` + `auth_env` (NAMES only) + `billing_mode` (`platform_managed` if the registry `IsPlatform` predicate holds, else `byok`). The SSOT the canvas Provider dropdown consumes instead of `VENDOR_LABELS`. - `registry_models`: the runtime's NATIVE model ids, each annotated with its DERIVED provider (`DeriveProvider`) + the `billing_mode` that 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 - **Additive + federation-ready + fail-OPEN.** The existing template-served `Models`/`Providers`/`ProviderRegistry` fields are UNCHANGED, so non-registry runtimes (external/mock/kimi/future third-party) and older canvases keep working — a runtime absent from the registry yields `registry_backed=false`, no synthesized block. - **NO hard-reject.** Templates whose model isn't registry-derivable are still served (WARN-level only). Legacy-vocab reconcile remains P4. - Proxy `ResolveUpstream` / billing `DeriveProvider` untouched (P1/P2). Templates' own `config.yaml providers:` codegen untouched (P4). - Uses a process-cached `registryManifest()` accessor; named distinctly from #1972's `providerRegistry()` to compile against current main (both wrap the same SSOT; unify in a trivial follow-up once #1972 lands). ### Tests / build (Phase 3) - TDD: `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-existing `TestTemplatesList_*` stay green. - `go test ./internal/handlers ./internal/providers` green; `go build ./...` OK; `go vet` clean; `gofmt -l` clean; `golangci-lint run ./internal/handlers` 0 issues. ### Five-Axis (Phase 4) Walked all five axes — Correctness (fail-open on every registry-error path; `nil` authEnv 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; `platform` closed-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-ack` to satisfy each) - **Comprehensive testing performed:** 3 new Go tests in `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-existing `TestTemplatesList_*` stay green. Edge cases: empty-runtime template (fails open, no panic), un-derivable model (served un-annotated, not dropped), registry-load failure (fails open). - **Local-postgres E2E run:** N/A — `GET /templates` `List` walks the host configsDir + reads the embedded registry; it touches no DB (the handler has no Postgres dependency). `go test ./internal/handlers ./internal/providers` green locally. - **Staging-smoke verified or pending:** scheduled post-merge — after deploy, probe `GET /templates` on the synthetic tenant and confirm a claude-code template returns `registry_backed:true` + `registry_providers`/`registry_models`, and a non-registry runtime returns `registry_backed:false`. - **Root-cause not symptom:** the selectable provider/model list was sourced from each template's hand-authored config.yaml rather than the provider-registry SSOT, so an unregistered option could be surfaced and provider knowledge was duplicated; this serves the list from the registry (`ProvidersForRuntime`/`ModelsForRuntime`/`DeriveProvider`/`IsPlatform`) at the API boundary. - **Five-Axis review walked:** Correctness (fail-open on every registry-error path; nil authEnv safe — claude-code kimi split is by exact id; un-derivable model served un-annotated), Readability (no finding), Architecture (reuses P2-B `providerRegistry()` + `LLMBillingMode*` — one accessor/constant set), Security (NAMES-only auth_env, no secret values; `platform` closed-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. - **No backwards-compat shim / dead code added:** No — additive only. The existing template-served `Models`/`Providers`/`ProviderRegistry` fields 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. - **Memory/saved-feedback consulted:** `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).
hongming added 1 commit 2026-05-28 02:15:40 +00:00
feat(workspace-server): P3 internal#718 — serve GET /templates selectable provider/model list from the registry
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
E2E API Smoke Test / E2E API Smoke Test (pull_request) Blocked by required conditions
E2E Chat / E2E Chat (pull_request) Blocked by required conditions
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Blocked by required conditions
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Blocked by required conditions
Harness Replays / Harness Replays (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 12s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 7s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Successful in 49s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 39s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
Harness Replays / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 3s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 3s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m12s
gate-check-v3 / gate-check (pull_request) Successful in 16s
verify-providers-gen / Regenerate providers artifact and fail on drift (pull_request) Successful in 55s
qa-review / approved (pull_request) Failing after 4s
security-review / approved (pull_request) Failing after 4s
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 4s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Successful in 8m29s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Failing after 15m34s
CI / Canvas (Next.js) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / Platform (Go) (pull_request) Successful in 5m46s
CI / all-required (pull_request) Successful in 28m11s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
c4b63c7cb3
P3 item 1 (retire-list #1 surface). GET /templates (templates.go List) now
ANNOTATES each 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) instead of the
template's hand-authored config.yaml providers:/runtime_config.models block:

- registry_backed: true when the runtime is in the registry runtimes: block.
- registry_providers: the runtime's NATIVE provider set (ProvidersForRuntime),
  each with display_name + auth_env + billing_mode (platform_managed if the
  registry IsPlatform predicate holds, else byok) — the SSOT the canvas
  Provider dropdown consumes instead of its hardcoded VENDOR_LABELS map.
- registry_models: the runtime's NATIVE model ids (ModelsForRuntime), each
  annotated with its DERIVED provider (DeriveProvider) + the billing_mode that
  provider implies — so the canvas shows the billing source of the DERIVED
  provider (folds in #1931 intent) and can render no model the registry did
  not list for the runtime ("only registered selectable").

Additive + federation-ready + fail-OPEN: the existing template-served
Models/Providers/ProviderRegistry fields are UNCHANGED, so non-registry
runtimes (external/mock/kimi/future third-party) and older canvases keep
working — a runtime absent from the registry yields registry_backed=false and
no synthesized block. NO hard-reject: templates whose model isn't
registry-derivable are still served (WARN-level only; legacy-vocab reconcile
is P4).

Uses a process-lifetime-cached registryManifest() accessor (LoadManifest is a
pure parse of a go:embed'd YAML). Named distinctly from #1972's
providerRegistry() to compile against current main; both wrap the same SSOT.

Proxy ResolveUpstream / billing DeriveProvider untouched (P1/P2). Templates'
own config.yaml providers: codegen untouched (P4).

TDD: TestTemplatesList_RegistryServesSelectableModels (a template's bogus model
id never leaks into the registry-served list; native ids present),
TestTemplatesList_RegistryAnnotatesDerivedProviderAndBilling (derived
provider + platform_managed/byok per model; provider display_name/auth_env/
billing from the registry), TestTemplatesList_NonRegistryRuntimeFallsOpenToTemplate
(mock runtime: registry_backed=false, template fields untouched). All existing
TestTemplatesList_* stay green (template-served fields unchanged).

internal#718 P3 — not merged; CTO merge-go after Five-Axis (UI/API-affecting).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hongming added the tier:medium label 2026-05-28 02:15:57 +00:00
hongming force-pushed feat/internal-718-p3a-templates-from-registry from c4b63c7cb3 to 2d0d070040 2026-05-28 02:21:08 +00:00 Compare
agent-reviewer approved these changes 2026-05-28 02:31:35 +00:00
agent-reviewer left a comment
Member

APPROVED — 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_backed are NEW JSON keys — template-served models/providers/provider_registry are untouched. modelSpec.BillingMode is yaml:"-", so it is never populated on the template path and the existing models array serializes byte-identically (templates.go:329 builds Models from raw YAML only). 12 pre-existing TestTemplatesList_* are unmodified (diff appends only at templates_test.go:1329+). Non-registry runtime (mock, in knownRuntimes runtime_registry.go:86 but absent from the registry runtimes: block) -> ProvidersForRuntime errors -> 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-7 is exact-listed only under anthropic-api -> DeriveProvider step-3 resolves it to anthropic-api -> byok; anthropic/claude-opus-4-7 exact-listed only under platform -> platform -> platform_managed (matches tests). anthropic-oauth display_name "Claude Code subscription" + auth_env [CLAUDE_CODE_OAUTH_TOKEN] match. The nil availableAuthEnv 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/LLMBillingModeBYOK already 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-named registryManifest() accessor — that text is stale; the actual code correctly calls providerRegistry(), 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.

**APPROVED — 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_backed` are NEW JSON keys — template-served `models`/`providers`/`provider_registry` are untouched. `modelSpec.BillingMode` is `yaml:"-"`, so it is never populated on the template path and the existing `models` array serializes byte-identically (templates.go:329 builds Models from raw YAML only). 12 pre-existing `TestTemplatesList_*` are unmodified (diff appends only at templates_test.go:1329+). Non-registry runtime (`mock`, in knownRuntimes runtime_registry.go:86 but absent from the registry `runtimes:` block) -> `ProvidersForRuntime` errors -> 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-7` is exact-listed only under anthropic-api -> DeriveProvider step-3 resolves it to anthropic-api -> byok; `anthropic/claude-opus-4-7` exact-listed only under platform -> platform -> platform_managed (matches tests). anthropic-oauth display_name "Claude Code subscription" + auth_env [CLAUDE_CODE_OAUTH_TOKEN] match. The `nil` availableAuthEnv 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`/`LLMBillingModeBYOK` already 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-named `registryManifest()` accessor — that text is stale; the actual code correctly calls `providerRegistry()`, 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.
claude-ceo-assistant approved these changes 2026-05-28 03:02:46 +00:00
claude-ceo-assistant left a comment
Owner

approve on current head (provider-SSOT P3, CTO keep-going)

approve on current head (provider-SSOT P3, CTO keep-going)
hongming merged commit 753e0f569d into main 2026-05-28 03:02:48 +00:00
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1977