Permissions
Who is allowed what. A permission is a rule policy that bears on
the actor rather than on the action; it reads the identity established at the door.
Everything is closed, grants open operations
By default, nobody is allowed anything. A profile receives operations of the tenant's APIs,
named by their API: boutique.searchProducts, boutique.createOrder. An operation that none of the
identity's profiles received is never called for it.
profiles:
anonyme:
when: "identity.authenticated == false"
grants: [boutique.searchProducts, boutique.createOrder]
premium:
when: "identity.authenticated and 'premium' in identity.plans"
grants: [boutique.searchProducts, boutique.createOrder]
support:
when: "'support' in identity.roles"
grants: [paiement.proposeRefund, paiement.createRefund]
manager:
when: "'support-manager' in identity.roles"
grants: [paiement.proposeRefund, paiement.createRefund]
- A grant bears on the operation, not on the skill. One operation can be reached by a skill, a playbook task, the submit or an automation: guarding it guards them all.
- Grants add up across the profiles of one identity.
- Grants are the only place that opens. Everything else (permissions, guards, budgets, approvals, sub-identities) only tightens what they opened.
- The anonymous visitor gets no exception: what it may do, the
anonymeprofile receives. - Operations are a closed set, read from the OpenAPI: a grant naming an unknown operation is refused at load time.
- Core checks the grant when it calls the tenant's API, reading the config again. Even if the compiled graph were wrong, the call would be refused.
Permissions tighten, phase by phase
Within what grants opened, a permission guard reserves a precise moment to some profiles. Each
phase answers one question:
| phase | question | what rules it |
|---|---|---|
invoke | who may call the skill? | the grant of the operation read, then a permission in invoke.guards; on return, kinds classify the items |
present | which kinds are shown, and to whom? | a permission in kinds.<k>.present |
select | who may choose which kind? | a permission in select or kinds.<k>.select |
submit | who may submit? | the grant of the write operation, and submitted_by |
A skill has no right of its own: it can be used if the operations it calls are granted, and if
its permissions allow it. A playbook opens if its required tasks can be done. An
automation has no profile of its own: what it starts acts under the room's owner it names
(owner), with that owner's grants.
For a rule used only once, a require guard on the identity is enough, without creating a
profile: require: "'phone' in identity.verified".
When a proof can open the rule, Core does not only refuse: it asks for authentication, before
the call for invoke and present, at the choice for select, at the submit for submit, then
runs the call again. See Asking for authentication.
What a profile sees: the permission in present
Customers who are not premium do not see premium products:
skills:
- name: articles
invoke: {method: searchProducts}
kinds:
premium:
when: "$fields.tier == 'premium'"
present: {guards: [{permission: {profiles: [premium]}}]}
present:
fields:
name: {from: name, type: text, role: title}
tier: {from: tier, type: text}
A permission in present is a security rule, not a display choice:
- Core removes the item from the result before the model, on both doors. Neither the model nor the widget sees it.
- Counts are made after the filter: "12 products" does not betray the hidden ones.
- What you cannot see, you cannot choose:
selectinherits frompresent. Otherwise the model could add a premium product by guessing its id. - When the tenant's API can filter, pass it the filter, through a parameter set by the identity
(
set_by: [identity]). The premium product then never leaves the tenant.
per_profile.present without a permission stays interface: more columns for a manager. See
Profiles.
Reserving a kind to the choice
skills:
- name: rembourser
invoke: {method: proposeRefund}
kinds:
gros-montant:
when: "$fields.amount > 50"
select: {guards: [{permission: {profiles: [manager]}}]} # support sees, the manager chooses
present:
fields:
name: {from: label, type: text, role: title}
amount: {from: amount, type: price, role: billed}
Support sees the large refund and can flag it; only the manager can choose it.
Rules combine by intersection: an action is allowed only if every layer allows it (station, skill, playbook, profile, sub-identity). See How the layers combine. That these layers do not contradict each other is checked at load time by the permission graph.
Who submits
The right to submit is set in the submit_guard, by submitted_by: the room's verified owner, the
person who sent the agent, the approver, a role, or the agent itself under a condition. Whoever
submits must also hold the grant of the write operation. See
The submit.
Delegated sub-identities
A customer of the station creates sub-identities for its agents, on the tenant's page. Their
limits — amount, hours, addresses, forbidden skills, confirmation above an amount — are rules like
the others. They can only restrict; a limit that would widen the station is refused. The station
declares what it lets be limited in station.delegation.restrictions.