Skip to content

An admin cannot resolve an account id to a person #123

Description

@davidtaing

TL;DR

  • The problem — the admin surface asks a reviewer to compare a contact email against an opaque account uuid before handing over a profile, and renders anyone else's credential check as "another Bluehex admin". So the comparison happens in the Supabase dashboard in another tab, or it does not happen — and assigning the wrong owner clears every credential's verified and takes an unassign-then-reassign to repair.
  • The fix — one narrow security definer function returning an email for an account id, executable by bluehex_admin alone, in the idiom of my_profile(). It has to be a migration rather than a query: auth is not an exposed schema, so PostgREST cannot reach auth.users at all.
  • To decide — whether it takes one id or a set, since the queue would otherwise call it once per verified credential. Not to be built: an automated email match on assignment — the spec refuses it on purpose, because that override is exactly where social engineering comes back.
Full context — the reasoning, the constraints and what has already been ruled out. This is the part a coding agent should read.

Two places on the admin surface ask a reviewer to make a judgement about an account, and neither can show them who the account is. Both landed in #122.

Assigning an owner. docs/spec/profile-and-credentials.md:75 is deliberate that the override is "a human check rather than a mechanism" — an admin decides, and no automated email match is wanted. But the check itself is "does the claimer's verified account email equal practitioner_contacts.contact_email", and the panel can only show one side of it: the contact address is rendered, and the input takes an account uuid. The admin is being asked to compare an address against an opaque identifier, so in practice the comparison happens in the Supabase dashboard in another tab, or it does not happen. The consequence of getting it wrong is real — the profile is handed over, every credential's verified is cleared, and A → B is refused by the guard, so the repair is unassign-then-reassign.

Naming who performed a check. practitioner_credentials.verified_by is an account id. The queue currently renders your own check as Checked by <you>, anybody else's as another Bluehex admin, and a null one as with no admin recorded — which is reachable today, since every seeded verified row has it null. "Another Bluehex admin" is the honest thing to say when the id cannot be resolved, and it is a poor substitute for a name on the surface whose whole subject is who vouched for what.

Why it is a migration rather than a query

auth is not an exposed schema, so PostgREST cannot reach auth.users at all, and public.admins carries no grant to bluehex_admin. There is nothing to select.

The likely shape is one narrow security definer function returning an email for an account id, executable by bluehex_admin alone — the same idiom as my_profile() in 20260822050002_profile_own_reads.sql, and subject to the same rule that the predicate is the whole control. Both callers above are served by it. Worth deciding at the same time whether it takes one id or a set, since the queue would call it once per verified credential otherwise.

Not to be built

An automated email match on assignment. The spec refuses it on purpose — that override is exactly where social engineering comes back, and a mechanism there would launder a human decision into an automatic one.

Metadata

Metadata

Assignees

Labels

area: dbPostgres schema, migrations, RLS, queriesenhancementNew feature or requesthitlNeeds a human (decision/design/review)size: SAn evening for someone new to this codebase

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions