capabilities: send the denied routes too

the server half of the previous commit, which belonged with it. the frontend
guard needs both lists: absence from `routes` cannot tell a route this
account lacks from a route no capability claims, so without this the guard
permits everything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-07 01:01:01 +00:00
co-authored by Claude Opus 5
parent 537a77d320
commit f3a3ae3b64
@@ -34,11 +34,17 @@ selfCapabilitiesRouter.get('/capabilities', async (ctx) => {
? CAPABILITIES.map((c) => ({ key: c.key, level: 'write' as const }))
: [...grants].map(([key, level]) => ({ key, level }));
const heldKeys = new Set(held.map((h) => h.key));
return ctx.json({
isOwner,
capabilities: held,
// Flattened for the dock and the route guard, which care about paths rather than capability keys.
routes: held.flatMap(({ key }) => CAPABILITY_BY_KEY.get(key)?.routes ?? []),
// The complement, and the frontend genuinely needs both. "Not in `routes`" cannot distinguish a route
// this account lacks from a route no capability claims at all — `/`, the settings shell, the sign-in
// screens — and a guard that cannot tell those apart either blanks the app or guards nothing.
deniedRoutes: CAPABILITIES.filter((c) => !heldKeys.has(c.key)).flatMap((c) => c.routes ?? []),
});
});