A host's authentication customizer runs on every request of a session. Policy resolution read the live permission universe once for every Allow term, and each reading recomputed the PolicyCatalog fingerprint, walked every catalog resource and called every host-name function. A role of nine named permissions therefore built nine universes per request; a full-access document built one. - PolicyRules.allowed, resolve and withheld read the universe they are given once per call (PolicyUniverse.once), so resultFromClaims, fromClaims, both claims mappers, the access editor and AuthentikAdmin cost one universe per resolution whatever the policy holds. PolicyParser.parseForSave reads it once per document. - PolicyCatalog.of compares the instance with the kept fingerprint piece by piece instead of building the fingerprint text on every call; the same content is compared, so every in-place metadata change still gives a new catalog on the next call. The catalog keeps its permission names and its part of the universe. - Nothing is cached across resolutions: no universe, no decision. The catalog cache is per JVM and derived only from that JVM's instance, as before. - PolicyResult.replacement(): the grant alone (Allow minus Deny), for a host whose sessions take their permissions only from the token. apply() is unchanged and remains a union with what the host passes. Measured on a 1,400-object instance (212 tables, 985 processes, 199 widgets), one resolution: 9 named permissions 12.1 ms and 7.9 MB to 0.33 ms and 0.12 MB; 30 named 28.7 ms and 25.3 MB to 0.32 ms and 0.14 MB; full access 1.19 ms and 1.37 MB to 0.77 ms and 0.20 MB. Tests: PolicyResolutionCostTest counts universe builds (1 for 1, 9 and 30 names, 0 for a void or absent claim), pins that one reading grants exactly what a reading per term grants, and holds an allocation budget; PolicyCatalogTest changes every fingerprinted aspect in place. The golden vectors are untouched and pass. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>