Paliadin: visibility-gate returns 0 rows — Supabase-MCP runs as supabase_admin without JWT #72
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
Paliadin (in-app AI assistant) sees 0 projects, 0 deadlines, 0 appointments for every visibility-gated query, even when m (
global_role = 'global_admin') is the session user. Concrete incident from 2026-05-22:fb838b5d-d91a-4b30-bbdc-9b20cd25e2a6).This is not "empty week" — this is a structural visibility outage.
Root cause
paliad.can_see_project(_project_id)evaluatesauth.uid()— the user fromrequest.jwt.claims.sub. The Supabase-MCP that Paliadin uses to reach the DB connects assupabase_adminwith no JWT context (request.jwt.claims = NULL). Thereforeauth.uid() = NULL, all three EXISTS branches incan_see_projectfail, and the function returnsfalsefor every project.Reproducer (any paliadin tmux pane):
The empty-result protocol in the Paliadin skill catches the typical filter bugs but cannot catch this one — the broader sanity-check query still goes through
can_see_project(), which is exactly the function that's silently returning false for everything.Workaround (used live in the 14:54 turn)
m's UUID is
1afbfbe9-45f1-42b2-a9d7-c785d8d7785f. Because m isglobal_admin, the first EXISTS branch incan_see_projectsucceeds and all data becomes visible. This is a hot patch — not a real fix — and it only works for users whose UUID Paliadin has somehow learned out-of-band.Proposed fixes (ascending order of cleanness)
[ctx …]block from the frontend already carriessession_id. Extend it withpaliad_user_id=<uuid>(server-side enriched from the authenticated session). The Paliadin skill prefixes every visibility-gated query withSET LOCAL request.jwt.claim.sub = '<that uuid>'. The frontend never trusts the value — it comes from the Go server. Pros: zero infra work. Cons: auth is now in the prompt context; non-admin users still have to traverse the real visibility logic via this syntheticauth.uid(), which works as long ascan_see_projectonly ever readsauth.uid()(it does today).paliadin.fetch_deadlines(_user_id uuid, …)SECURITY DEFINER function per recipe. It internally checks visibility for_user_idand writes to apaliadin_audit_logrow per call. The MCP loses arbitrary SQL access; recipes become callable RPCs. Pros: every Paliadin read is auditable; no JWT plumbing. Cons: locks Paliadin into a finite set of recipes — concept-Q&A and ad-hoc analyses get harder.Authorization: Bearer <jwt>, the JWT carriessub = <user uuid>, andauth.uid()resolves correctly. Pros: the visibility gate works the way it was designed, no per-query plumbing. Cons: non-trivial pane-spawn change inm/mAi#207(aichat backend on mRiver — same backend that runsPALIADIN_BACKEND=aichatper t-paliad-194); needs a JWT-issuer route in paliad to mint a short-lived per-session token.Recommend (1) now as a fast unblock, plus (3) as the proper fix once aichat owns the pane spawn end-to-end.
Impact on past data answers
Every datenbezogene Paliadin-Antwort in the current production deploy that used
can_see_project()is unreliable — until a fix lands, m must assume Paliadin's "keine Daten" answers are false negatives. Concept answers (Kostenrechner tables, UPC fee schedules, glossary, navigation) are unaffected.Test plan
requester=paliadin,user_id, turn_id, classifier_tag.