Module: CurrentScope::GrantDiagnosis
- Defined in:
- lib/current_scope/grant_diagnosis.rb
Overview
Judges a scoped grant. Two methods, two certainties:
verdict_for PROVEN — the grant cannot match anything, for any type.
type_untargeted? ADVISORY — suggestive only; the host's runtime hooks
decide what a controller resolves to, so this can be
wrong and says so where the operator reads it.
Neither is #90's "inert" (the RECORD is gone; different fix). Design rationale: docs/plans/2026-07-28-032-feat-unresolvable-grant-guardrail-plan.md
Class Method Summary collapse
-
.type_untargeted?(grant, verdict: :__unset) ⇒ Boolean
verdict:lets a caller that already has it skip the recompute (three role_permissions plucks per grant otherwise). -
.untargeted_caveat ⇒ Object
The advisory's caveat, centralised for the same reason the proven wording is: the console and the CLI had drifted into two versions of it.
- .verdict_fix(verdict) ⇒ Object
-
.verdict_for(grant) ⇒ Object
Proven, or nil.
-
.verdict_label(verdict) ⇒ Object
One wording, so the task and the view cannot drift.
Class Method Details
.type_untargeted?(grant, verdict: :__unset) ⇒ Boolean
verdict: lets a caller that already has it skip the recompute (three
role_permissions plucks per grant otherwise).
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/current_scope/grant_diagnosis.rb', line 41 def type_untargeted?(grant, verdict: :__unset) verdict = verdict_for(grant) if verdict == :__unset return false unless verdict.nil? return false if orphaned?(grant) role = grant.role return false if role.nil? || role.full_access? return false unless ensure_models_loaded! klass = resource_class(grant) return false if klass.nil? # unresolvable class is #90's inert, not ours keys = persisted_keys(role) return false if keys.any? { |key| routed?(key) && targets_any_route_key?(key, klass) } # #108: a grant on a PARENT legitimately reaches its children, so stay # silent when any declared chain reaches this class. !reachable_through_declared_chain?(klass, keys) rescue NameError, ActiveRecord::ActiveRecordError => e raise if e.instance_of?(NoMethodError) log_degrade(e) false end |
.untargeted_caveat ⇒ Object
The advisory's caveat, centralised for the same reason the proven wording is: the console and the CLI had drifted into two versions of it.
69 70 71 72 73 74 |
# File 'lib/current_scope/grant_diagnosis.rb', line 69 def untargeted_caveat "This is NOT a verdict. Only your current_scope_record hooks decide which " \ "records a controller resolves to, and that is not knowable statically. A " \ "controller serving this type under a different name is a false alarm here. " \ "Check the hook before removing anything." end |
.verdict_fix(verdict) ⇒ Object
84 85 86 87 88 89 90 91 92 |
# File 'lib/current_scope/grant_diagnosis.rb', line 84 def verdict_fix(verdict) case verdict when :no_permissions "Tick at least one permission on this role, or remove the grant." when :unrouted_permissions "Every key on this role is absent from the route-derived catalog, so " \ "nothing can ever gate them. Re-tick the role against current routes." end end |
.verdict_for(grant) ⇒ Object
Proven, or nil. Never guesses.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/current_scope/grant_diagnosis.rb', line 14 def verdict_for(grant) return nil if orphaned?(grant) role = grant.role return nil if role.nil? || role.full_access? keys = persisted_keys(role) # Checked BEFORE the catalog guard: a role with nothing ticked can never # match whatever the catalog says, so deferring to "no verdict" here # would downgrade a proven finding to an advisory. return :no_permissions if keys.empty? # An empty catalog means routes are not derived yet, not that every key # is dead. Only the unrouted claim depends on it. return nil if CurrentScope.catalog.keys.empty? return :unrouted_permissions if keys.none? { |key| live?(key, resource_class(grant)) } nil rescue NameError, ActiveRecord::ActiveRecordError => e raise if e.instance_of?(NoMethodError) log_degrade(e) nil end |
.verdict_label(verdict) ⇒ Object
One wording, so the task and the view cannot drift.
77 78 79 80 81 82 |
# File 'lib/current_scope/grant_diagnosis.rb', line 77 def verdict_label(verdict) case verdict when :no_permissions then "role ticks no permissions" when :unrouted_permissions then "role ticks only unrouted keys" end end |