From 52bfb81d7f5006dbf2bbdae04fae266d6328c2fd Mon Sep 17 00:00:00 2001 From: galuis116 Date: Tue, 14 Jul 2026 10:22:15 -0400 Subject: [PATCH] fix(api): audit registered-vs-installed dashboard/digest copy (#5026) Repos that opt out of the gittensor plugin now legitimately show 0 registered repos, so wording that implied something was broken or missing at that count was misleading. The digest headline now leads with installed repos and only mentions the registered count when it's nonzero; the operator dashboard's "registry missing" delta is replaced with an accurate "gittensor plugin not enabled" when no registry snapshot has ever been persisted. No functional/logic change to how isRegistered or isInstalled are computed. --- src/api/routes.ts | 11 ++++- src/services/operator-dashboard.ts | 8 +++- test/integration/api.test.ts | 68 +++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/api/routes.ts b/src/api/routes.ts index 5b7e930a5c..d6bd5f4ce0 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -4748,11 +4748,18 @@ function buildDigestItems(args: { rateLimits: Awaited>; }) { const items: Array<{ kind: "summary" | "review-now" | "queue" | "drift" | "install"; title: string; detail: string; meta?: string }> = []; + // Lead with `installed` (repos this instance actually operates on) rather than `registered` (gittensor-subnet + // membership, an opt-in plugin -- see gittensor-wire.ts). "0 registered" is the normal, expected headline for + // any operator who hasn't opted into the gittensor plugin and must not read as broken (#5026). + const installed = args.repositories.filter((repo) => repo.isInstalled).length; const registered = args.repositories.filter((repo) => repo.isRegistered).length; items.push({ kind: "summary", - title: `${registered} registered repositories tracked`, - detail: `${args.repositories.length} repositories are present in the local LoopOver data cache.`, + title: `${installed} installed repositories tracked`, + detail: + registered > 0 + ? `${args.repositories.length} repositories are present in the local LoopOver data cache; ${registered} registered with the gittensor plugin.` + : `${args.repositories.length} repositories are present in the local LoopOver data cache.`, meta: "registry", }); const unhealthy = args.health.filter((record) => record.status !== "healthy"); diff --git a/src/services/operator-dashboard.ts b/src/services/operator-dashboard.ts index 6907979add..50c688f145 100644 --- a/src/services/operator-dashboard.ts +++ b/src/services/operator-dashboard.ts @@ -184,7 +184,13 @@ export async function buildOperatorDashboardPayload( metrics: [ { label: "Active sessions", value: String(activeSessions), delta: "browser + CLI/MCP" }, { label: "Installations", value: String(installations.length), delta: `${installedRepos} installed repos` }, - { label: "Registered repos", value: String(registeredRepos), delta: registry ? `${registry.repoCount} in latest registry` : "registry missing" }, + { + label: "Registered repos", + value: String(registeredRepos), + // A null registry is the normal, expected state for any operator who hasn't opted into the + // gittensor plugin (see gittensor-wire.ts) -- "missing" reads as broken when it isn't (#5026). + delta: registry ? `${registry.repoCount} in latest registry` : "gittensor plugin not enabled", + }, { label: "Digest subscriptions", value: String(digestSubscriptions), delta: "store-only" }, { label: "Product events", value: String(usageSummary.totalEvents), delta: `last ${windowDays} days` }, { label: "Active users", value: String(usageSummary.activeActors), delta: `hashed, last ${windowDays} days` }, diff --git a/test/integration/api.test.ts b/test/integration/api.test.ts index b33cd2ce27..01689c96c1 100644 --- a/test/integration/api.test.ts +++ b/test/integration/api.test.ts @@ -509,6 +509,72 @@ describe("api routes", () => { expect(legacyPerRepoDrift.status).toBe(404); }); + it("reads registered-repo copy as an honest, non-alarming state whether or not the gittensor plugin is opted into (#5026)", async () => { + const app = createApp(); + + // No registry snapshot ever persisted: the normal, expected state for an operator who hasn't opted + // into the gittensor plugin. "registered: 0" must not read as broken. + const unregisteredEnv = createTestEnv(); + await upsertRepositoryFromGitHub( + unregisteredEnv, + { name: "installed-only", full_name: "acme/installed-only", private: false, owner: { login: "acme" }, default_branch: "main" }, + 321, + ); + const unregisteredOperator = await app.request("/v1/app/operator-dashboard", { headers: apiHeaders(unregisteredEnv) }, unregisteredEnv); + expect(unregisteredOperator.status).toBe(200); + await expect(unregisteredOperator.json()).resolves.toMatchObject({ + metrics: expect.arrayContaining([ + expect.objectContaining({ label: "Registered repos", value: "0", delta: "gittensor plugin not enabled" }), + ]), + }); + const unregisteredDigest = await app.request("/v1/app/digest", { headers: apiHeaders(unregisteredEnv) }, unregisteredEnv); + expect(unregisteredDigest.status).toBe(200); + await expect(unregisteredDigest.json()).resolves.toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ + kind: "summary", + title: "1 installed repositories tracked", + detail: "1 repositories are present in the local LoopOver data cache.", + }), + ]), + }); + + // A registry snapshot IS persisted (gittensor plugin opted in somewhere): both copy surfaces should + // report the registered count honestly instead of the unregistered-branch wording. + const registeredEnv = createTestEnv(); + await upsertRepositoryFromGitHub( + registeredEnv, + { name: "installed-and-registered", full_name: "acme/installed-and-registered", private: false, owner: { login: "acme" }, default_branch: "main" }, + 654, + ); + await persistRegistrySnapshot( + registeredEnv, + normalizeRegistryPayload( + { "acme/installed-and-registered": { emission_share: 0.01, issue_discovery_share: 0, label_multipliers: {}, trusted_label_pipeline: false } }, + { kind: "raw-github", url: "fixture://registered-registry" }, + "2026-06-01T00:00:00.000Z", + ), + ); + const registeredOperator = await app.request("/v1/app/operator-dashboard", { headers: apiHeaders(registeredEnv) }, registeredEnv); + expect(registeredOperator.status).toBe(200); + await expect(registeredOperator.json()).resolves.toMatchObject({ + metrics: expect.arrayContaining([ + expect.objectContaining({ label: "Registered repos", value: "1", delta: "1 in latest registry" }), + ]), + }); + const registeredDigest = await app.request("/v1/app/digest", { headers: apiHeaders(registeredEnv) }, registeredEnv); + expect(registeredDigest.status).toBe(200); + await expect(registeredDigest.json()).resolves.toMatchObject({ + items: expect.arrayContaining([ + expect.objectContaining({ + kind: "summary", + title: "1 installed repositories tracked", + detail: "1 repositories are present in the local LoopOver data cache; 1 registered with the gittensor plugin.", + }), + ]), + }); + }); + it("serves upstream ruleset status, ruleset snapshots, and drift reports through private APIs", async () => { const app = createApp(); const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "public-token" }); @@ -2351,7 +2417,7 @@ describe("api routes", () => { const emptyOperator = await app.request("/v1/app/operator-dashboard", { headers: apiHeaders(emptyEnv) }, emptyEnv); expect(emptyOperator.status).toBe(200); await expect(emptyOperator.json()).resolves.toMatchObject({ - metrics: expect.arrayContaining([expect.objectContaining({ label: "Registered repos", delta: "registry missing" })]), + metrics: expect.arrayContaining([expect.objectContaining({ label: "Registered repos", delta: "gittensor plugin not enabled" })]), recommendationQuality: expect.objectContaining({ empty: true, sparse: false,