CheckTable and LookupTargetFacts resolve the target with to_regclass, whose behavior when the connected role lacks schema USAGE produces two misleading outcomes instead of an actionable refusal:
- Qualified name, no
USAGE on the schema: to_regclass raises insufficient_privilege (SQLSTATE 42501), which surfaces as a raw wrapped permission-denied error rather than a typed refusal naming the missing GRANT.
- Unqualified name: search_path resolution silently skips schemas the role cannot use, so
to_regclass returns NULL and the check reports ErrTableNotFound for a table that exists — the operator is told the table is missing when the real problem is a missing GRANT USAGE.
The engine-role access check already handles this correctly: gatherAccessFacts in pkg/preflight/privileges.go catches SQLSTATE 42501 and routes both it and the NULL-resolution case through unresolvedTargetCause, which separates "does not exist" from "not visible to this role". CheckTable and LookupTargetFacts in pkg/preflight/preflight.go lack the same handling.
Proposed fix: mirror the privileges.go handling in both functions — catch SQLSTATE 42501 and route through unresolvedTargetCause (or an equivalent typed cause), so a privilege-blocked target refuses with the missing grant instead of a raw error or a false not-found. Integration coverage per the test methodology: a role without schema USAGE, exercising both the qualified and unqualified paths.
This issue was drafted by Kiran's (@Kiran01bm) coding agent (Amp / Claude Opus 4.5) from a review finding on #60.
CheckTableandLookupTargetFactsresolve the target withto_regclass, whose behavior when the connected role lacks schemaUSAGEproduces two misleading outcomes instead of an actionable refusal:USAGEon the schema:to_regclassraisesinsufficient_privilege(SQLSTATE 42501), which surfaces as a raw wrapped permission-denied error rather than a typed refusal naming the missingGRANT.to_regclassreturnsNULLand the check reportsErrTableNotFoundfor a table that exists — the operator is told the table is missing when the real problem is a missingGRANT USAGE.The engine-role access check already handles this correctly:
gatherAccessFactsinpkg/preflight/privileges.gocatches SQLSTATE 42501 and routes both it and theNULL-resolution case throughunresolvedTargetCause, which separates "does not exist" from "not visible to this role".CheckTableandLookupTargetFactsinpkg/preflight/preflight.golack the same handling.Proposed fix: mirror the
privileges.gohandling in both functions — catch SQLSTATE 42501 and route throughunresolvedTargetCause(or an equivalent typed cause), so a privilege-blocked target refuses with the missing grant instead of a raw error or a false not-found. Integration coverage per the test methodology: a role without schemaUSAGE, exercising both the qualified and unqualified paths.This issue was drafted by Kiran's (@Kiran01bm) coding agent (Amp / Claude Opus 4.5) from a review finding on #60.