diff --git a/management/internals/modules/agentnetwork/manager.go b/management/internals/modules/agentnetwork/manager.go index f3dbcaf18..f1e119b12 100644 --- a/management/internals/modules/agentnetwork/manager.go +++ b/management/internals/modules/agentnetwork/manager.go @@ -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 diff --git a/management/internals/modules/agentnetwork/provider_redaction_test.go b/management/internals/modules/agentnetwork/provider_redaction_test.go index e6a415c75..246f4db1e 100644 --- a/management/internals/modules/agentnetwork/provider_redaction_test.go +++ b/management/internals/modules/agentnetwork/provider_redaction_test.go @@ -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") +}