Description
resolveRemoteAccessLauncherForDevice (apps/api/src/routes/devices/core.ts:1073-1090) reads partners.settings to find the tenant's configured remote-access providers. For an organization-scoped caller that read returns zero rows, so partnerSettings falls back to {}, no providers are found, and both the device-detail availability flag and POST /devices/:id/remote-access-launch degrade to "no provider configured" — for a tenant that has one configured.
This is the same partner-axis RLS gap already fixed once in getEnrollmentDefaultsForOrg (#2776 task 3.4), and the integration test written for that fix documents the chain exactly.
The chain
Each link verified against origin/main (f2d7fda83):
- The route admits org scope.
POST /:id/remote-access-launch is requireScope('organization', 'partner', 'system') (core.ts:1102). The device-detail read path that computes the availability flag is reachable the same way (core.ts:988-1013).
partners has exactly one SELECT policy, breeze_partner_isolation_select … USING (breeze_has_partner_access(id)) (migrations/2026-04-11-partners-rls.sql:81). I grepped every migration: there is no second permissive policy, and no own-partner branch via breeze_current_partner_id (that helper is used in the catalog and CIS-baseline policies, not here).
breeze_has_partner_access is system → TRUE, NULL → FALSE, else id = ANY(breeze_accessible_partner_ids()) (migrations/2026-04-11-a-rls-function-bootstrap.sql).
computeAccessiblePartnerIds returns [] for scope === 'organization' (middleware/auth.ts:382-389) — [partnerId] only for partner scope, null only for system.
So an org-scoped session cannot see the partner row, and the join yields nothing.
Why the withSystemDbAccessContext wrapper does not save it
The read is wrapped in withSystemDbAccessContext (core.ts:1077), which looks like it escalates. It does not, inside a request: withDbAccessContext early-returns when a context store already exists (db/index.ts:440), and authMiddleware has already opened one for the request. The nested call therefore retains the caller's ambient scope rather than switching to system.
That is the same behaviour confirmed on #3391, where the identical wrapper in readPreferredProviderId was inert ceremony and was removed. Here the consequence is worse than cosmetic, because this call actually depends on the escalation it appears to perform. A genuine escalation needs runOutsideDbContext first, as the billing bulk handlers do.
Why no test catches it
Quoting the precedent test, which says it better than I would:
A mocked-DB unit test cannot catch this: the mock always returns whatever row the test stages, with no RLS evaluation. Only a real Postgres connection running through the actual breeze_app role, under a real withDbAccessContext org-scoped RLS session, can prove the join is visible.
core.remoteAccessLaunch.test.ts mocks the db entirely, so it stages the partner settings it then asserts on.
What I have and have not established
Verified: every link above, by reading the code and grepping all migrations.
Not verified: a runtime reproduction. I have not stood up a real-Postgres org-scoped session against this resolver, so I am reporting a structural defect rather than an observed failure. It is possible that in practice the technicians who use remote access are partner-scoped, in which case the reachable path is simply untrodden — but the route explicitly admits organization scope, and the enrollment-cap precedent shows this class does reach real callers.
enrollmentDefaultsPartnerCap.integration.test.ts is close to a drop-in template — its orgContext() helper already mirrors computeAccessiblePartnerIds for org scope. Happy to write the equivalent repro and the fix together if you want it; I did not want to push a fix for a path I had only proven on paper.
Suggested fix
Wrap the partner read in runOutsideDbContext(() => withSystemDbAccessContext(...)) so the escalation is real. Worth a sweep for the same shape elsewhere: a bare withSystemDbAccessContext inside a request path is inert everywhere, so any other site that depends on it for cross-axis visibility has the same defect.
Found while building the profile-UI provider dropdown for #3391/#3404, where the same read has to work for exactly this org-scoped population.
Description
resolveRemoteAccessLauncherForDevice(apps/api/src/routes/devices/core.ts:1073-1090) readspartners.settingsto find the tenant's configured remote-access providers. For an organization-scoped caller that read returns zero rows, sopartnerSettingsfalls back to{}, no providers are found, and both the device-detail availability flag andPOST /devices/:id/remote-access-launchdegrade to "no provider configured" — for a tenant that has one configured.This is the same partner-axis RLS gap already fixed once in
getEnrollmentDefaultsForOrg(#2776 task 3.4), and the integration test written for that fix documents the chain exactly.The chain
Each link verified against
origin/main(f2d7fda83):POST /:id/remote-access-launchisrequireScope('organization', 'partner', 'system')(core.ts:1102). The device-detail read path that computes the availability flag is reachable the same way (core.ts:988-1013).partnershas exactly one SELECT policy,breeze_partner_isolation_select … USING (breeze_has_partner_access(id))(migrations/2026-04-11-partners-rls.sql:81). I grepped every migration: there is no second permissive policy, and no own-partner branch viabreeze_current_partner_id(that helper is used in the catalog and CIS-baseline policies, not here).breeze_has_partner_accessissystem → TRUE,NULL → FALSE, elseid = ANY(breeze_accessible_partner_ids())(migrations/2026-04-11-a-rls-function-bootstrap.sql).computeAccessiblePartnerIdsreturns[]forscope === 'organization'(middleware/auth.ts:382-389) —[partnerId]only for partner scope,nullonly for system.So an org-scoped session cannot see the partner row, and the join yields nothing.
Why the
withSystemDbAccessContextwrapper does not save itThe read is wrapped in
withSystemDbAccessContext(core.ts:1077), which looks like it escalates. It does not, inside a request:withDbAccessContextearly-returns when a context store already exists (db/index.ts:440), andauthMiddlewarehas already opened one for the request. The nested call therefore retains the caller's ambient scope rather than switching to system.That is the same behaviour confirmed on #3391, where the identical wrapper in
readPreferredProviderIdwas inert ceremony and was removed. Here the consequence is worse than cosmetic, because this call actually depends on the escalation it appears to perform. A genuine escalation needsrunOutsideDbContextfirst, as the billing bulk handlers do.Why no test catches it
Quoting the precedent test, which says it better than I would:
core.remoteAccessLaunch.test.tsmocks the db entirely, so it stages the partner settings it then asserts on.What I have and have not established
Verified: every link above, by reading the code and grepping all migrations.
Not verified: a runtime reproduction. I have not stood up a real-Postgres org-scoped session against this resolver, so I am reporting a structural defect rather than an observed failure. It is possible that in practice the technicians who use remote access are partner-scoped, in which case the reachable path is simply untrodden — but the route explicitly admits organization scope, and the enrollment-cap precedent shows this class does reach real callers.
enrollmentDefaultsPartnerCap.integration.test.tsis close to a drop-in template — itsorgContext()helper already mirrorscomputeAccessiblePartnerIdsfor org scope. Happy to write the equivalent repro and the fix together if you want it; I did not want to push a fix for a path I had only proven on paper.Suggested fix
Wrap the partner read in
runOutsideDbContext(() => withSystemDbAccessContext(...))so the escalation is real. Worth a sweep for the same shape elsewhere: a barewithSystemDbAccessContextinside a request path is inert everywhere, so any other site that depends on it for cross-axis visibility has the same defect.Found while building the profile-UI provider dropdown for #3391/#3404, where the same read has to work for exactly this org-scoped population.