Skip to content

feat: approval-gated legacy identity remediation tooling - #117

Open
felixgateru wants to merge 15 commits into
mainfrom
feat/legacy-identity-remediation
Open

felixgateru wants to merge 15 commits into
mainfrom
feat/legacy-identity-remediation

Conversation

@felixgateru

Copy link
Copy Markdown
Contributor

Problem

  • feat: dry-run legacy identity audit report #116 ships a read-only report surfacing legacy identity-state drift, but no way to act on it. The issue requires remediation to be individually approval-gated, evidenced, idempotent, and never a blanket backfill.

Depends on #116 (report) and #115 (email-change flow): stacked on both, so this diff includes their commits until they merge.

Fix

Three platform-admin-only GraphQL mutations, each targeting exactly one operator-identified row — no bulk "fix everything" entry point — requiring a non-empty evidence/reason string recorded verbatim in audit_logs, and idempotent (a repeated or resumed call is a safe no-op):

  • recordAdministratorAssistedEmailVerification(entityId, evidence) — sets entity_emails.verified_at on the strength of evidence gathered outside Atom (a support ticket, an identity check). Atom cannot verify the evidence itself; it only requires the caller to state it.
  • quarantineOauthLink(entityId, provider, subject, reason) — soft-disables a suspicious link via a new oauth_identities.quarantined_at column (migration 003), preserving the row for audit. upsert_oauth_identity's existing-link lookup now rejects a quarantined link outright — it can never re-authenticate or be silently refreshed by a fresh callback.
  • revokeOauthLink(entityId, provider, subject, reason) — hard, unrecoverable delete for links already confirmed unjustified.

Publishes the existing frozen entity.update event rather than a new event name (v1 event names are frozen).

runbooks/legacy-identity-recovery.md is the operator procedure: backup first, run the report, triage by pendingTokens (a self-service path may already be in flight), remediate one row at a time, verify by re-running the report, and documented rollback boundaries for what each mutation can't undo on its own.

Tests

  • 7 new integration tests + 1 new in-module unit test proving a quarantined link is rejected end-to-end through upsert_oauth_identity
  • evidence/reason requirement, idempotence, audit-trail content, non-admin rejection, and the remediated row disappearing from the report afterward — all covered
  • full unit + contract suite and 45 tests in adjacent admin/email-change/report suites, no regressions

Completes #110 (workstream B).

…ability so it matches the migration-seeded database contract

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…ady hit the undeclared api_endpoint applicability error

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
… verified email-change flow

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…confirm flow

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…contracts

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…ries

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…ontracts

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…port

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…hQL mutations

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…or runbook

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
…ion mutations

Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant