Skip to content

Resolution Engine ​

The SDK's authorization engine resolves a partner's effective abilities for a given app at a given timestamp — Authz = f(App, Actor, Timestamp). It uses one SQL query — no N+1, no in-PHP filtering, no cached partial state.

The set of abilities an actor holds is an AbilitySet, reached at runtime through SdkContext::abilities(). ("Ability set" is the capability axis — what an actor may do. It is distinct from row-level data scope, which is the Partners #[Scoped*] family; the two never share a name. See Scoped Entity Sets.)

Snapshot mode ​

Class:

Shell\Context\SnapshotAbilitiesContributor

Returns the client-relevant bucket:

php
[
  'act' => [ ability => bool ],   // operation guards (UX hint; server-enforced)
]

see is not delivered here. Per-element visibility is resolved server-side from the same See grants and applied by injecting sap.ui.viewModifications into the app manifest when it is served (View Visibility); only act reaches the client. The underlying AbilityGrantQuery still resolves See grants — they just travel through the manifest, not context.json.

Algorithm ​

  1. Load all abilities for the app (from sdk_abilities filtered by artifact_id).
  2. Initialize each entry as false.
  3. Run AbilityGrantQuery.
  4. Mark granted abilities as true.

That's it. Three steps, one query, no surprises.

AbilityGrantQuery ​

A single UNION ALL query that resolves four grant paths in parallel:

  1. Direct ability assignments — sdk_ability_assignments joined to sdk_abilities.
  2. Role assignments — sdk_role_assignments joined through sdk_ability_role to sdk_abilities.
  3. Group → role grants — sdk_group_assignments joined through sdk_group_role and sdk_ability_role to sdk_abilities.
  4. Group → ability grants — sdk_group_assignments joined through sdk_ability_group to sdk_abilities.

All four are filtered by:

  • partner_id
  • valid_from <= :timestamp
  • valid_until >= :timestamp
  • artifact namespace

The query uses positional parameters so it stays SQLite-safe and portable across MySQL and PostgreSQL.

Read abilities — the OData entity-set gates — are checked against the same grant set. ODataReadAuthorizer asks SdkContext::abilities() whether the actor holds the set's Read ability: a denied root set answers 403, a denied $expand target is dropped with a sap-messages warning. Read and Access are enforced on the server; they are not part of the snapshot the client receives, but explain lists them.

Explain mode ​

Class:

Shell\Context\ExplainAbilitiesContributor

Same AbilityGrantQuery, projected into a verbose structure:

php
[
  'access' => [ /* same shape as 'act' */ ],
  'act' => [
    'closePeriod' => [
      'granted' => true,
      'sources' => [
        [
          'type' => 'role',
          'role' => 'Accountant',
          'role_id' => 7,
          'assignment_id' => 15,
          'valid_from' => ...,
          'valid_until' => ...,
        ],
      ],
    ],
  ],
]

Explain returns all four buckets, in the order access, act, see, read (see and read have the same shape). Every ability of the app is listed with granted; each granted one carries one or more sources (duplicates removed). A source's type is one of:

  • direct — a direct ability assignment.
  • role — a role assignment; also carries role and role_id.
  • group — a group assignment; carries group plus either via_role (the group grants a role that holds the ability) or direct: true (the group holds the ability itself).

Every source also carries assignment_id, valid_from and valid_until.

Explain is descriptive, not normative.

The snapshot and explain modes share the same SQL backbone — they cannot disagree.

Why one query ​

A single union-based grant resolution gives you:

  • Deterministic counts — no race between filters and joins.
  • No N+1 — every grant path is one query, not one query per grant.
  • Index-friendly — the query uses simple equality filters and timestamp ranges.
  • Testable — every grant path can be exercised by the synthetic test DSL.
  • Explainable — every granted ability can attribute its sources without re-querying.

See also ​