Surface catalog models in the scoped provider list for unrestricted callers

An unrestricted policy on a provider without an operator declaration left
the caller-scoped model list empty while the setup answer advertises the
catalog models. The scoped list now always carries the effective set, so
the dashboard's model filter matches the setup page in every shape.
This commit is contained in:
mlsmaycon
2026-08-27 12:34:15 +00:00
parent fd1940dad8
commit b58d888d84
2 changed files with 38 additions and 8 deletions

View File

@@ -254,14 +254,16 @@ func (m *managerImpl) callerScopedProviders(ctx context.Context, accountID, user
for _, p := range authorized {
r := p.RedactedForViewer()
// The model list follows the same effective computation the setup
// answer and the proxy use: a caller whose policies carry a model
// allowlist sees only the models those guardrails permit, so the
// dashboard's model filter never offers a model the caller's own
// requests could not use. Grant holders keep the full declared
// lists — their usage view spans everyone's requests.
if allAllowed, effective := effectiveModelsForProvider(p, policiesForProvider(applicable, p.ID), guardrailsByID); !allAllowed {
r.Models = providerModelsByID(p, effective)
}
// answer and the proxy use: allowlist-restricted callers see only
// the models their guardrails permit, and an unrestricted policy
// on a provider without an operator declaration surfaces the
// catalog models, matching the setup response — so the dashboard's
// model filter never offers a model the caller's own requests
// could not use, and never comes up empty when the setup page
// lists models. Grant holders keep the full declared lists —
// their usage view spans everyone's requests.
_, effective := effectiveModelsForProvider(p, policiesForProvider(applicable, p.ID), guardrailsByID)
r.Models = providerModelsByID(p, effective)
out = append(out, r)
}
return out, nil

View File

@@ -259,3 +259,31 @@ func TestGetAllProviders_SelfScopedAllowlistWithoutDeclaredModels(t *testing.T)
require.Len(t, scoped[0].Models, 1)
assert.Equal(t, "gpt-5.4", scoped[0].Models[0].ID)
}
func TestGetAllProviders_SelfScopedUnrestrictedFallsBackToCatalogModels(t *testing.T) {
ctx := context.Background()
mgr, s := newSelfScopeStore(t)
// Unrestricted policy on a provider without an operator declaration:
// the setup answer advertises the catalog models, and the scoped
// provider list must match so the model filter is never emptier than
// the setup page.
granted := newSynthTestProvider()
granted.ID = "prov-catalog"
granted.Name = "Granted"
granted.Models = nil
require.NoError(t, s.SaveAgentNetworkProvider(ctx, granted))
policy := newSynthTestPolicy(granted.ID, "grp-eng", "")
policy.ID = "pol-catalog"
require.NoError(t, s.SaveAgentNetworkPolicy(ctx, policy))
scoped, err := mgr.GetAllProviders(ctx, testAccountID, "user-a")
require.NoError(t, err)
require.Len(t, scoped, 1)
require.NotEmpty(t, scoped[0].Models, "catalog models back the filter when the operator declared none")
ids := make([]string, 0, len(scoped[0].Models))
for _, m := range scoped[0].Models {
ids = append(ids, m.ID)
}
assert.Equal(t, declaredModelIDs(granted), ids, "the scoped list mirrors the setup answer's declared/catalog set")
}