The seam a consumer overrides to answer "may this principal use this scope?" its own way.
The stock implementation (SearchScopePermissionResolver) reads
__mj.SearchScopePermission rows keyed by UserID or by one of the user's MJ Roles. That
covers MJ's own model, but it is not the only shape a permission model can take: a consumer
whose entitlements are neither a user nor an MJ Role — a per-tenant capability grant, say —
has no row that can express them, and its grants are invisible to the check that actually
runs on every search.
Rather than have such a consumer project its model into SearchScopePermission as derived
per-user rows — which works, but creates permission state that can drift from its source —
it subclasses SearchScopePermissionResolver and registers against this base:
@RegisterClass(SearchScopePermissionResolverBase, SEARCH_SCOPE_PERMISSION_RESOLVER_KEY) exportclassMyResolverextendsSearchScopePermissionResolver { publicoverrideasyncResolveEffectivePermission(input: ResolvePermissionInput) { conststock = awaitsuper.ResolveEffectivePermission(input); if (stock.Allowed) returnstock; // never narrow what MJ already granted returnthis.myOwnGrantCheck(input); // only ever widen } }
One caution on that pattern: a stock denial whose Source is 'PrincipalNotActivatable' means
the CALLER may not wield the named principal. An override that widens past it re-opens the
wieldability gate — widen from the user's own entitlements, never on the strength of a
principal the stock resolver just refused.
Do not pass a priority. Subclassing the stock resolver is what orders the registration, and
it does so more reliably than a number can. ClassFactory.Register treats an omitted priority as
"one higher than the highest already registered for this (base, key)" — and a subclass cannot be
defined without its parent module having loaded first, so MJ's own registration always runs
before the consumer's and the consumer always lands above it. Extending the concrete resolver
therefore guarantees the ordering as a side effect of the language.
A hardcoded priority forfeits that guarantee. Two independent consumers that both pick the same
number collide, Register warns, and resolution silently degrades to whichever happened to be
registered last — a load-order bug wearing the costume of a configuration value. The priority
argument exists for cases where subclassing is genuinely impossible; this is not one of them.
Failure posture.SearchEngine treats a resolver throw as DENIED, never as allowed. An
override that cannot reach its own store must not accidentally open a scope.
The seam a consumer overrides to answer "may this principal use this scope?" its own way.
The stock implementation (SearchScopePermissionResolver) reads
__mj.SearchScopePermissionrows keyed byUserIDor by one of the user's MJ Roles. That covers MJ's own model, but it is not the only shape a permission model can take: a consumer whose entitlements are neither a user nor an MJ Role — a per-tenant capability grant, say — has no row that can express them, and its grants are invisible to the check that actually runs on every search.Rather than have such a consumer project its model into
SearchScopePermissionas derived per-user rows — which works, but creates permission state that can drift from its source — it subclasses SearchScopePermissionResolver and registers against this base:One caution on that pattern: a stock denial whose Source is 'PrincipalNotActivatable' means the CALLER may not wield the named principal. An override that widens past it re-opens the wieldability gate — widen from the user's own entitlements, never on the strength of a principal the stock resolver just refused.
Do not pass a priority. Subclassing the stock resolver is what orders the registration, and it does so more reliably than a number can.
ClassFactory.Registertreats an omitted priority as "one higher than the highest already registered for this (base, key)" — and a subclass cannot be defined without its parent module having loaded first, so MJ's own registration always runs before the consumer's and the consumer always lands above it. Extending the concrete resolver therefore guarantees the ordering as a side effect of the language.A hardcoded priority forfeits that guarantee. Two independent consumers that both pick the same number collide,
Registerwarns, and resolution silently degrades to whichever happened to be registered last — a load-order bug wearing the costume of a configuration value. The priority argument exists for cases where subclassing is genuinely impossible; this is not one of them.Failure posture.
SearchEnginetreats a resolver throw as DENIED, never as allowed. An override that cannot reach its own store must not accidentally open a scope.