DocsRecipes

Recipes

Permissions, roles, and entitlement analytics

4 min readReviewed July 2026

Authorization systems decide what a person or service may do. Analytics can show whether permissions are discoverable, overbroad, unused, frequently denied, or blocking legitimate work.

GraphJSON must not become the authorization authority. Evaluate access in the application or policy service, commit changes there, and send compact, privacy-conscious facts for analysis.

Use Security audit event analytics when the primary goal is an investigation-grade actor-action-resource trail. This recipe focuses on product adoption and access-policy operations.

Separate four concepts#

Concept Example
Role workspace_admin
Permission report.export
Entitlement Paid plan allows scheduled exports
Decision This actor may export this report now

A role can grant permissions. A subscription can create an entitlement. A policy decision evaluates those facts in context. Do not use one overloaded access field for all four.

Model grants and revocations#

{
  "event": "role_assignment_changed",
  "event_id": "evt_role_01J...",
  "account_id": "acct_42",
  "subject_type": "user",
  "subject_id": "usr_91",
  "role": "report_admin",
  "change": "granted",
  "actor_type": "workspace_admin",
  "actor_id": "usr_7",
  "reason_code": "team_transfer",
  "policy_version": "rbac-v12",
  "assignment_id": "assign_204",
  "schema_version": 1
}

Use the same stable assignment_id for the grant and its later revocation. Never include invitation tokens, session credentials, policy source code, or free-form HR notes.

Record access decisions selectively#

Logging every allowed read can create extreme volume and unnecessary sensitivity. Record decisions that support a defined use case:

  • denied sensitive actions
  • high-impact administrative actions
  • permission checks at a new policy boundary
  • sampled allowed decisions with explicit sampling metadata
  • terminal product actions that already imply successful authorization
{
  "event": "access_decision_evaluated",
  "event_id": "evt_decision_01J...",
  "account_id": "acct_42",
  "actor_type": "user",
  "actor_id": "usr_91",
  "capability": "report.export",
  "resource_type": "report",
  "resource_scope": "account",
  "decision": "denied",
  "reason_code": "missing_permission",
  "policy_version": "rbac-v12",
  "schema_version": 1
}

Avoid raw resource names and paths when a bounded resource type and opaque identifier are sufficient.

Keep entitlements event-time correct#

Plan and entitlement changes are state transitions:

{
  "event": "entitlement_changed",
  "event_id": "evt_entitlement_01J...",
  "account_id": "acct_42",
  "entitlement": "scheduled_exports",
  "change": "enabled",
  "source": "subscription",
  "plan_version": "pro-2026-07",
  "effective_at": "2026-07-26T00:00:00Z",
  "schema_version": 1
}

Use the entitlement service or billing system as authority. When analyzing a later feature action, join to the entitlement effective at that action rather than today's plan.

Measure denial rates with compatible opportunities#

SELECT
  JSONExtractString(json, 'capability') AS capability,
  count() AS decisions,
  countIf(
    JSONExtractString(json, 'decision') = 'denied'
  ) AS denied,
  round(100 * denied / nullIf(decisions, 0), 2) AS denied_pct,
  uniqExactIf(
    JSONExtractString(json, 'actor_id'),
    JSONExtractString(json, 'decision') = 'denied'
  ) AS affected_actors
FROM security_events
WHERE
  JSONExtractString(json, 'event') = 'access_decision_evaluated'
  AND timestamp >= now() - INTERVAL 30 DAY
GROUP BY capability
ORDER BY denied DESC

If allowed decisions are sampled but denied decisions are complete, the raw denial rate is invalid. Keep both populations exact or apply a documented, compatible weighting method.

Diagnose legitimate friction#

For product usability, distinguish:

  • user lacks a required role
  • account lacks a paid entitlement
  • policy blocks the resource scope
  • account configuration is incomplete
  • authentication is stale
  • request is suspicious or malformed

Track whether the user:

  • requested access
  • contacted an administrator
  • upgraded
  • abandoned
  • completed the task through an approved path

A high denial rate can indicate successful protection, a confusing interface, incorrect defaults, or hostile traffic. Segment by reason and product context before acting.

Measure entitlement utilization#

Start with eligible accounts:

WITH entitled AS (
  SELECT DISTINCT
    JSONExtractString(json, 'account_id') AS account_id
  FROM entitlement_snapshots
  WHERE
    JSONExtractString(json, 'entitlement') = 'scheduled_exports'
    AND JSONExtractBool(json, 'enabled') = true
),
used AS (
  SELECT DISTINCT
    JSONExtractString(json, 'account_id') AS account_id
  FROM product_events
  WHERE
    JSONExtractString(json, 'event') = 'scheduled_export_delivered'
    AND JSONExtractString(json, 'outcome') = 'succeeded'
    AND timestamp >= now() - INTERVAL 30 DAY
)
SELECT
  count() AS entitled_accounts,
  countIf(used.account_id != '') AS using_accounts,
  round(
    100 * using_accounts / nullIf(entitled_accounts, 0),
    2
  ) AS utilization_pct
FROM entitled
LEFT JOIN used USING account_id

An unused entitlement can mean poor discovery, no current need, an overly broad package, or a customer reserving future capacity. Do not automatically remove contractual access because analytics shows no recent use.

Measure grant to first legitimate use#

For newly granted permissions, calculate:

  • percentage used within 7, 30, and 90 days
  • time to first successful action
  • denial before grant
  • grant followed quickly by revocation
  • role or permission requested but never used

Use maturity windows. A permission granted yesterday has not had 30 days to be used.

Find stale access carefully#

A review candidate might satisfy:

assignment older than 180 days
AND no successful use of any role capability in 90 days
AND subject is still active

This is a candidate, not an automatic revocation command. Service accounts, break-glass roles, infrequent finance workflows, and seasonal responsibilities need explicit policy.

Store access-review decisions in the identity-governance or authorization system, then publish a compact result if trend analysis is useful.

Monitor policy changes#

Attach policy_version to decisions. Before and after a rollout, compare:

  • denial rate by capability
  • affected actors and accounts
  • false-denial reports
  • access-request volume
  • successful task completion
  • support contacts
  • security outcomes

Use a feature flag or controlled rollout for significant policy changes. A drop in allowed actions may be intended, but a drop in legitimate task success is still important.

Protect the dataset#

Permission data can reveal organizational structure and sensitive resources.

  • use opaque user and resource IDs
  • keep reason codes bounded
  • separate high-sensitivity audit collections
  • restrict dashboards and exports
  • avoid employee performance rankings
  • choose retention deliberately
  • reconcile critical audit requirements outside GraphJSON

GraphJSON does not provide a general-purpose identity governance or certification workflow.

Build the dashboard#

Recommended sections:

  1. role and entitlement changes
  2. denied actions by capability and reason
  3. affected actors and accounts
  4. access-request and recovery funnel
  5. entitlement utilization
  6. grant-to-first-use and unused assignments
  7. policy-version impact
  8. event completeness and source reconciliation

Launch checklist#

  • Roles, permissions, entitlements, and decisions are separate concepts.
  • Authorization remains synchronous and authoritative outside GraphJSON.
  • Grant and revocation facts use stable assignment identity.
  • Decision logging has a purpose and controlled volume.
  • Secrets, tokens, policy source, and sensitive notes are excluded.
  • Denial rates use compatible sampled or exact populations.
  • Event-time entitlement state is used for historical analysis.
  • Utilization begins with the entitled population.
  • Stale-access results are review candidates, not automatic commands.
  • Policy changes are versioned and evaluated against legitimate outcomes.
  • Access data has restricted sharing and intentional retention.
  • Critical counts reconcile with the authorization authority.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support