Found while implementing #7555 (PR #7605); deliberately not folded into that PR, whose scope is baseline composition rather than baseline application.
The defect
Both permission resolutions in packages/plugins/plugin-hono-server/src/current-user-endpoints.ts apply the baseline set(s) only when the caller's own names resolved to nothing:
:740 — the /auth/me/permissions object/field surface
:954 — the /me/apps capability + tab surface
Both spelled if (resolved.length === 0 && ...).
That is exactly the fallback cliff ADR-0090 D5 abolishes, verbatim:
The fallback cliff is abolished. Today's semantics ("fallback applies only while the user has zero explicit grants") mean the first real grant silently removes the user's baseline. everyone is additive like any other position: baseline ∪ explicit, always.
SecurityPlugin.resolvePermissionSetsForContext was fixed for this years-in-ADR-terms ago and applies the baseline additively on every authenticated request. These two endpoints were not, so the two planes disagree the moment a user holds any explicit grant at all.
Consequence
A member who receives their first position or permission-set grant keeps the baseline on the data plane (the engine middleware resolves it additively) but loses it on the UI plane:
/auth/me/permissions reports object/field access computed without the baseline — narrower than what a read will actually return;
/me/apps computes systemPermissions and tabPermissions without the baseline, so a baseline-granted tab or capability can disappear from the served app list for a user who demonstrably still has it.
Fail-direction is closed (the UI under-reports), so it presents as "the console hides things the API allows" rather than as an exposure.
Not fixed by #7605
PR #7605 changes which names those two call sites resolve (the composed baseline instead of a single displaced name) and leaves the resolved.length === 0 guard exactly as it found it — widening the guard is a distinct behaviour change for every user who holds a grant, and it deserves its own measurement rather than riding along on a composition fix. Both fallbackNames.length > 0 guards in that PR are the same cliff with a new variable name.
Suggested shape
Mirror resolvePermissionSetsForContext: push the baseline names into requested before the single resolvePermissionSets call, rather than resolving them in a second call gated on emptiness. Worth checking at the same time whether the endpoints should stop re-implementing this resolution at all and call the plugin's own service instead — the drift is a symptom of two copies of one rule.
Please measure the before/after on a real member with one explicit grant; the interesting number is how many tabs/capabilities appear that did not before.
Found while implementing #7555 (PR #7605); deliberately not folded into that PR, whose scope is baseline composition rather than baseline application.
The defect
Both permission resolutions in
packages/plugins/plugin-hono-server/src/current-user-endpoints.tsapply the baseline set(s) only when the caller's own names resolved to nothing::740— the/auth/me/permissionsobject/field surface:954— the/me/appscapability + tab surfaceBoth spelled
if (resolved.length === 0 && ...).That is exactly the fallback cliff ADR-0090 D5 abolishes, verbatim:
SecurityPlugin.resolvePermissionSetsForContextwas fixed for this years-in-ADR-terms ago and applies the baseline additively on every authenticated request. These two endpoints were not, so the two planes disagree the moment a user holds any explicit grant at all.Consequence
A member who receives their first position or permission-set grant keeps the baseline on the data plane (the engine middleware resolves it additively) but loses it on the UI plane:
/auth/me/permissionsreports object/field access computed without the baseline — narrower than what a read will actually return;/me/appscomputessystemPermissionsandtabPermissionswithout the baseline, so a baseline-granted tab or capability can disappear from the served app list for a user who demonstrably still has it.Fail-direction is closed (the UI under-reports), so it presents as "the console hides things the API allows" rather than as an exposure.
Not fixed by #7605
PR #7605 changes which names those two call sites resolve (the composed baseline instead of a single displaced name) and leaves the
resolved.length === 0guard exactly as it found it — widening the guard is a distinct behaviour change for every user who holds a grant, and it deserves its own measurement rather than riding along on a composition fix. BothfallbackNames.length > 0guards in that PR are the same cliff with a new variable name.Suggested shape
Mirror
resolvePermissionSetsForContext: push the baseline names intorequestedbefore the singleresolvePermissionSetscall, rather than resolving them in a second call gated on emptiness. Worth checking at the same time whether the endpoints should stop re-implementing this resolution at all and call the plugin's own service instead — the drift is a symptom of two copies of one rule.Please measure the before/after on a real member with one explicit grant; the interesting number is how many tabs/capabilities appear that did not before.