From the 2026-07 pre-launch authorization architecture assessment (context: #2561, PR #2562).
Problem
permission-evaluator.ts → resolvePermissionSets resolves permission-set names through three sources: metadata service → bootstrap defaults → dbLoader (user-defined sets persisted in sys_permission_set). The dbLoader call swallows all failures:
try {
const dbRows = await dbLoader(unresolved);
...
} catch {
// Swallow — the request shouldn't fail just because the DB
// lookup is unavailable.
}
The metadata-service list() call at the top of the function has the same shape (catch { allPermSets = []; }).
Failure direction is correct (fail-closed: unresolvable sets grant nothing, the user gets 403 rather than an open door). The problem is observability: a transient DB error makes every custom permission set silently disappear for the affected requests — users are denied, admins see nothing in the logs, and the symptom ("permissions randomly stop working") points nowhere near the cause.
Fix
Surface the swallow: emit a warn with the unresolved names and the error when dbLoader (or the metadata list()) throws. PermissionEvaluator has no logger today — either accept an optional logger (constructor or method param), or move the try/catch responsibility to the caller (security-plugin.ts, which has ctx.logger). Keep the fail-closed behavior unchanged; this is logging only.
Acceptance
- dbLoader throwing produces exactly one
warn per request naming the unresolved permission sets; enforcement behavior is byte-identical.
- Same for the metadata
list() failure path.
- Unit test: stub a throwing dbLoader, assert the warn fires and resolution still returns the metadata/bootstrap results.
From the 2026-07 pre-launch authorization architecture assessment (context: #2561, PR #2562).
Problem
permission-evaluator.ts→resolvePermissionSetsresolves permission-set names through three sources: metadata service → bootstrap defaults →dbLoader(user-defined sets persisted insys_permission_set). ThedbLoadercall swallows all failures:The metadata-service
list()call at the top of the function has the same shape (catch { allPermSets = []; }).Failure direction is correct (fail-closed: unresolvable sets grant nothing, the user gets 403 rather than an open door). The problem is observability: a transient DB error makes every custom permission set silently disappear for the affected requests — users are denied, admins see nothing in the logs, and the symptom ("permissions randomly stop working") points nowhere near the cause.
Fix
Surface the swallow: emit a
warnwith the unresolved names and the error whendbLoader(or the metadatalist()) throws.PermissionEvaluatorhas no logger today — either accept an optional logger (constructor or method param), or move the try/catch responsibility to the caller (security-plugin.ts, which hasctx.logger). Keep the fail-closed behavior unchanged; this is logging only.Acceptance
warnper request naming the unresolved permission sets; enforcement behavior is byte-identical.list()failure path.