import { accountDb } from "./scoped-db"; import { forgetLocator, forgetScope } from "./scoped-db/locators"; import { dropAccountState } from "./account-scope"; /** * Removes the repository-scoped runners and pending runner registrations that `ownerId` holds for `repoId`. Run on * deletion and on transfer: a runner registered under the old owner must not keep claiming the repository's jobs * (and receiving the new owner's secrets) where the new owner can neither see nor revoke it. */ export async function dropRepoRunners(ownerId: string, repoId: string): Promise { const owner = accountDb(ownerId); const registrations = await owner.all<{ token_hash: string }>( "DELETE FROM runner_registration_tokens WHERE repo_id = ? RETURNING token_hash", repoId, ); const runners = await owner.all<{ id: string; token_hash: string; previous_token_hash: string | null }>( "DELETE FROM runners WHERE repo_id = ? RETURNING id, token_hash, previous_token_hash", repoId, ); await Promise.all([ ...registrations.map((row) => forgetLocator("runner_registration", row.token_hash)), ...runners.flatMap((runner) => [ forgetLocator("runner", runner.id), forgetLocator("runner_token", runner.token_hash), ...(runner.previous_token_hash ? [forgetLocator("runner_token", runner.previous_token_hash)] : []), ]), ]); } /** * Account-side leftovers of a deleted repository: the owner's runners and runner registrations for it, the * repository's AI key (kept in the AccountState named by the repository id), and every locator pointing into it. * Other users' notifications and digest items about it are not chased through every account: reads filter rows * whose repository is gone and prune them (notification-store `filterReadable`), and token grants naming a * deleted repository id match nothing since ids are never reused. */ export async function purgeDeletedRepoFromAccounts(repo: { id: string; owner_id: string }): Promise { await dropRepoRunners(repo.owner_id, repo.id); await dropAccountState(repo.id); await forgetScope(repo.id); }