diff --git a/src/review/gittensor-wire.ts b/src/review/gittensor-wire.ts index 197df86d1..ea9b2febe 100644 --- a/src/review/gittensor-wire.ts +++ b/src/review/gittensor-wire.ts @@ -35,11 +35,13 @@ export function shouldEnableGittensorForRepo( /** * The set of repos (lowercased full names) this self-host instance has opted into the gittensor plugin for -- - * every locally-known repo (listRepositories, deliberately NOT filtered by isRegistered: isRegistered is + * every locally installed repo (listRepositories, deliberately NOT filtered by isRegistered: isRegistered is * itself DOWNSTREAM of this decision on self-host, see registry/sync.ts's persistRegistrySnapshot, so filtering * on it here would be circular -- a repo could never earn its first isRegistered=true) whose manifest sets - * `experimental.gittensor: true` AND the global env kill-switch is on. A per-repo manifest-load error is - * skipped (treated as not-opted-in), never aborts the pass -- mirrors selftune-wire.ts's selfTuneRepos. + * `experimental.gittensor: true` AND the global env kill-switch is on. Registry-only rows from historical + * snapshots are ignored: an external repo's cached manifest must not activate outbound subnet work for this + * instance. A per-repo manifest-load error is skipped (treated as not-opted-in), never aborts the pass -- mirrors + * selftune-wire.ts's selfTuneRepos. * Flag-OFF (default) short-circuits before listing repos or loading a single manifest, so a plain self-host * instance makes zero local reads for this and zero outbound gittensor-registry requests (see index.ts). */ @@ -48,6 +50,7 @@ export async function gittensorEnabledRepoFullNames(env: Env & { GITTENSORY_EXPE const repos = await listRepositories(env); const enabled = new Set(); for (const repo of repos) { + if (!repo.isInstalled) continue; try { const manifest = await loadRepoFocusManifest(env, repo.fullName); if (shouldEnableGittensorForRepo(env, manifest.experimental.gittensor)) enabled.add(repo.fullName.toLowerCase()); diff --git a/test/unit/gittensor-wire.test.ts b/test/unit/gittensor-wire.test.ts index 7cf6ca6d6..515648b95 100644 --- a/test/unit/gittensor-wire.test.ts +++ b/test/unit/gittensor-wire.test.ts @@ -11,6 +11,14 @@ async function seedRegisteredRepo(env: Env, fullName: string): Promise { .run(); } +async function seedRegistryOnlyRepo(env: Env, fullName: string): Promise { + const [owner, name] = fullName.split("/"); + await (env.DB as unknown as { prepare: (s: string) => { bind: (...v: unknown[]) => { run: () => Promise } } }) + .prepare("INSERT INTO repositories (full_name, owner, name, is_installed, is_registered) VALUES (?, ?, ?, 0, 1)") + .bind(fullName, owner, name) + .run(); +} + // Wrap env.DB.prepare so any SQL matching `pattern` throws, exercising a fail-safe catch; every other query // delegates to the real test DB unchanged. Mirrors selftune-wiring.test.ts's poisonDbPrepare. function poisonDbPrepare(env: Env, pattern: RegExp): void { @@ -97,6 +105,13 @@ describe("gittensorEnabledRepoFullNames", () => { await expect(gittensorEnabledRepoFullNames(env)).resolves.toEqual(new Set(["owner/notyetregistered"])); }); + it("excludes registry-only repos whose cached manifests opt in (regression for the unscoped registry activation bypass)", async () => { + const env = createTestEnv({ GITTENSORY_EXPERIMENTAL_GITTENSOR: "true" }); + await seedRegistryOnlyRepo(env, "attacker/external"); + await upsertRepoFocusManifest(env, "attacker/external", { experimental: { gittensor: true } }); + await expect(gittensorEnabledRepoFullNames(env)).resolves.toEqual(new Set()); + }); + it("fails safe per-repo: a manifest-load error is swallowed and the pass still resolves", async () => { const env = createTestEnv({ GITTENSORY_EXPERIMENTAL_GITTENSOR: "true" }); await seedRegisteredRepo(env, "owner/blip");