ApiKey rows are soft-deleted by setting a timestamp, but the
prisma-soft-delete-middleware (configured in packages/db/src/client.ts with
ApiKey: true) filters ApiKey reads on the boolean deleted column — NOT on
deletedAt. Several delete paths set only deletedAt: new Date() and never set
deleted = true, so those soft-deleted rows stayed visible to any read that
relied on the middleware filter.
This PR makes every ApiKey soft-delete set BOTH deleted: true and deletedAt,
and backfills the pre-existing inconsistent rows.
Code changes (all ApiKey-model soft-delete paths now set deleted: true):
apps/apollo/src/lib/apiKeys/dbUtils/index.ts — deleteAPIKey and
deleteAllApiKeysForProject.apps/apollo/src/pages/api/internal-dashboard/team-members/remove/[id].ts —
the team-member-removal ApiKey sweep (added during review; it set only
deletedAt).apps/apollo/src/lib/consumer/provision.ts — the consumer-key re-provision
soft-delete (added during review; it set only deletedAt).apps/apollo/src/pages/api/v3.1/api_key_revocation.ts already set both fields
and is unchanged. The userApiKey/orgApiKeys paths are not affected: those
models are not registered in the soft-delete middleware, so their deletedAt-only
deletes are correct.
Liquibase migration v105_backfill_api_key_deleted_flag (registered in
changelog-root.json after v104):
deleted = true for every public.project_api_keys row where
deletedAt IS NOT NULL AND deleted = false.v103 backfill.down.ts resets deleted = false for the backfilled rows (best-effort; it
also touches rows set by the fixed code paths — documented in the file).The five manual deletedAt: null read guards in dbUtils/index.ts are
INTENTIONALLY KEPT for now as defense-in-depth. Removing them is a deliberate
follow-up that MUST come AFTER this migration has run in staging/production and
the deleted column is confirmed consistent with deletedAt.
Tests: apps/apollo/src/lib/apiKeys/dbUtils/deleteApiKey.unit.test.ts — 6 cases
covering both dbUtils delete paths, asserting the data payload sets
deleted: true + deletedAt, the where scoping, and the Err path.
node_modules is not installed in this worktree, so local typecheck/tests
could not be run. Relying on CI check-types and run-vitest-tests.ApiKey is registered in the soft-delete middleware
(packages/db/src/client.ts) and that the library filters on the boolean
deleted field, confirming the root cause.prisma.apiKey/tx.apiKey update/updateMany/delete call
site in apps/apollo/src; confirmed all ApiKey-model soft-delete paths now
set deleted: true, and that userApiKey/orgApiKeys are not
middleware-managed.deleted field is a valid ApiKeyUpdateManyMutationInput
member (the schema declares deleted Boolean, already written by the dbUtils
paths).v105 against the sibling v103/v104 migrations: changelog shape,
root-changelog ordering/format, and up/down batching all match.Before merge: v105 must be run against staging (and reviewed) per
packages/db/docs/DB_MIGRATIONS_GUIDE.md. The guard-removal cleanup is a
separate follow-up PR after the backfill is confirmed.
🤖 Generated with Claude Code