The resolver prepares bounded facts. The policy chooses which facts authorize an operation; it contains the business decision, including read-to-export relationships.
Shared code · Intaker + Web APIReviewed · 2026-10-27
AuthorizationContextRequest(UserId, Product, Resource) selects context. The resolver creates AuthorizationGrantRequest(UserId, Product, ResourceKinds) for the reader. Planning then uses AuthorizationPlanRequest(RequestId, Principal, Resource, Action).
Implemented preparation
LegacySqlAuthorizationGrantAdapter.ReadAsync() loads active assignments, role permissions and direct/group/public access from the database bound to the product. SQL narrows scope anchors and supported permissions before materialization.
Normalize(rows, diagram, resourceKinds) converts numeric storage codes to named grants and validates their scopes. The raw LegacyAuthorizationGrantRow stays next to this adapter.
SmartExchangeAuthorizationContextResolver.ResolveAsync() selects root + annotated targets, validates selected facts and emits roles: ["user"], attr.product and attr.grants. It filters again even when another reader is used.
AuthorizationGrant has six fields: assignment provenance, scoped record ID, authorized resource, action, scope and public-assignment flag. IsPublic=false can mean direct user or group access; it does not identify the membership route.
The temporary */admin/Instance fact is still retained in source. Both the legacy admin pseudoaction and * resource are planned for removal. The resolver does not automatically map a database role named exportAdmin into a Cerbos role.
Two grants → two derived roles → two complete policies
Both examples follow the same steps and use the same shared implementation. Person export accepts a related Company/read grant. Proposed InterestedParty export accepts InterestedParty/read in Project scope. This read-to-export choice is explicit policy logic; it does not change the grant action or imply access from Project/read.
Both complete demo policies use version presentation-demo, import an existing derived-role set and contain one export ALLOW rule. They intentionally isolate the relationship path; no exportAdmin, direct or instance-wide root bypass is included. Unmatched requests are denied. These are documentation proposals, not the current full production/default policies.
Person/export
The synthetic CompanyReader assignment normalizes to Company/read/Resource. The demo policy permits Person export through that Company relationship.
# Runtime snapshot: aks-smartcloud-nonprod / dev-authorization / derived-role-person-company-read
# Read on 2026-10-27. YAML content only; not a Kubernetes installation manifest.
apiVersion: api.cerbos.dev/v1
description: >-
Person.companyIds ReBAC condition: the user has a normalized Company/read
grant for at least one related record. This definition alone grants no action.
derivedRoles:
name: person_company_read
variables:
local:
personCompanyReadAll: >-
has(P.attr.grants) && P.attr.grants.exists(g,
g.resource == "Company" && g.action == "read" && g.scope == "Instance")
personCompanyReadIds: >-
has(P.attr.grants) ? P.attr.grants.filter(g,
g.resource == "Company" && g.action == "read" && g.scope == "Resource" &&
g.resourceId != "00000000-0000-0000-0000-000000000000").map(g, g.resourceId) : []
definitions:
- name: person_with_readable_company
parentRoles: [user]
condition:
match:
expr: >-
R.kind == "Person" &&
R.attr.companyIds.exists(relatedId,
V.personCompanyReadAll || relatedId in V.personCompanyReadIds)
Resource policy · complete demo YAML
Resource policy · complete demo YAML
# Documentation proposal only; not deployed. Separate version, no admin/direct bypass.
apiVersion: api.cerbos.dev/v1
description: Person export using read access to a related Company.
resourcePolicy:
version: presentation-demo
resource: Person
importDerivedRoles:
- person_company_read
rules:
- name: export-persons-through-readable-company
actions: [export]
derivedRoles: [person_with_readable_company]
effect: EFFECT_ALLOW
The snapshot-backed Project View assignment normalizes to InterestedParty/read/Project. Export is the proposed additional operation accepted by this demo policy.
Same mechanism, different grant meaning. The Person role checks Company/read grants. The InterestedParty role checks InterestedParty/read scoped to Project; it must not be replaced with the installed target-Project/read role. Importing a derived-role set alone grants no action; the resource policy must reference its role.
Current source/runtime references, separate from these demos: Person source policy · Person live default · InterestedParty live default. Person source currently spells the relationship inline; the demo shows its equivalent derived-role composition. InterestedParty/default currently enables this scoped role for read, not export.
Source: MicroApps + NuGet repositories. Live policies: non-prod DEV, checked 2026-10-27. No C# build or application deployment performed for this update. Navigate with ← / → outside controls. All assets and downloads work offline.