Summary
src/lib/maintainer/detect.ts has two places that query github_installation_users joined to github_installations!inner(...), then unwrap the result with a manual cast instead of the unwrapJoin helper that already exists in src/lib/supabase/inner-join.ts for exactly this:
isUserMaintainer (line 27): (row as unknown as { github_installations: {...} | null }).github_installations
listMaintainerInstalls (line 64): row as unknown as Row, same shape
This is the same join ambiguity that was already fixed once in mentor-assign.ts during the auto-assign mentor PR, Supabase can return an !inner join as either an object or a single element array depending on how the relationship is inferred, and unwrapJoin normalizes that safely. These two functions assume it's always an object.
Context
Both functions currently work in practice, but only because the join shape happens to come back as an object today. If that shape ever changes (schema tweak, Supabase client version bump), these casts won't catch it, unwrapJoin would.
Suggested fix
Replace both manual casts with unwrapJoin<{ uninstalled_at: string | null }>(row.github_installations) and the equivalent in listMaintainerInstalls, matching how process-pr-event.ts already does it for its own joins.
Summary
src/lib/maintainer/detect.tshas two places that querygithub_installation_usersjoined togithub_installations!inner(...), then unwrap the result with a manual cast instead of theunwrapJoinhelper that already exists insrc/lib/supabase/inner-join.tsfor exactly this:isUserMaintainer(line 27):(row as unknown as { github_installations: {...} | null }).github_installationslistMaintainerInstalls(line 64):row as unknown as Row, same shapeThis is the same join ambiguity that was already fixed once in
mentor-assign.tsduring the auto-assign mentor PR, Supabase can return an!innerjoin as either an object or a single element array depending on how the relationship is inferred, andunwrapJoinnormalizes that safely. These two functions assume it's always an object.Context
Both functions currently work in practice, but only because the join shape happens to come back as an object today. If that shape ever changes (schema tweak, Supabase client version bump), these casts won't catch it,
unwrapJoinwould.Suggested fix
Replace both manual casts with
unwrapJoin<{ uninstalled_at: string | null }>(row.github_installations)and the equivalent inlistMaintainerInstalls, matching howprocess-pr-event.tsalready does it for its own joins.