Minor Changes
-
d1a2251: Let the application provide the permission judgement (#2225).
security.grant: { isGranted(attribute, subject, user), install?(ctx) }is consulted before the role matrix, for roles and permissions alike.true/falseis the verdict; anything else falls through torole_hierarchy/role_permissions, which stay the default. A judge that throws denies. AgrantwithoutisGrantedfails at boot instead of being skipped.install(ctx)— for bothsecurity.grantand arolesProvider— now receivespermissionRegistry, so the app can pre-warm every registered key in one request. ReadgetKeys()at fetch time: modules register their entities afterinstallruns.- New signal
security:changed(SIGNALS.SECURITY_CHANGED): emit it when your answers change and the app remounts, as onauth:login. Emit only on an actual change — a remount discards unsaved form state. - The debug bar's auth panel shows the delegated verdicts for every registered permission.
See docs/security.md, "Delegating the judgement to your backend".
Patch Changes
-
65a0c01: Two things the docs never said, both found by a consumer hitting them
auth:loginis emitted byLoginPage, and by nothing else. It describes a
screen, not a change of authentication state — and several things wait on it:
theauth:readydeferred, the expired-session guard re-arming, role loading in
aPersistableRolesProvider, and the live-entity stream. An app that replaced
the login screen gets none of them, and none of them fails loudly.security.md
now says so, with the one line that fixes it.live-entities.mdnever said when the stream connects. It does now, as a
table of the three ways into a session — a valid session at boot connects
directly, aLoginPagelogin connects via the signal, and your own login
screen connects nothing unless it emitsauth:loginitself. The third row is
the one that bites: pushed updates simply do not arrive for that session, with
no error anywhere.And
isAuthenticated()is synchronous, whichsecurity.mdnow spells out
along with what follows from it: an app whose session lives in a refreshable
token must revalidate before mounting, or the route guard runs against a
session not yet restored and drops the user on/login?session_lost=1holding a
perfectly valid credential.No code changed. Every claim in both pages was checked against the source rather
than remembered — a document cannot go red on its own, so it has to be held
against something that can.