Skip to main content
Version: Target · roadmap

Permission graph

Grants open, permissions tighten phase by phase (Permissions). At load time, Core builds two graphs to prove these rules do not contradict each other: the profile graph says who can be whom, then the rights graph says what each may do, at each phase.

The profile graph​

An identity can match several profiles, and its rights are the union of their grants. Checking profile by profile is therefore not enough: Core must know which profiles can add up. It reads this in the when conditions:

relationexampleconsequence
exclusiveanonyme (identity.authenticated == false) and premium (identity.authenticated and …)never together
includedpremium implies client when its condition contains client'sa premium customer also has the client's rights
cumulativesupport and manager, two rolesrights are checked for the combination

The graph's nodes are the combinations of profiles an identity can reach, plus the "no profile" case: an identity that matches no profile is allowed nothing.

Loading refuses, or reports:

writteneffect
a profile no identity can fill (its condition contradicts itself)error: it would serve nobody
two profiles with the same conditionwarning: one is enough
a door that admits identities no profile covers (a logged-in customer with no role, for example)reported: they will be allowed nothing

The rights graph​

For each combination of profiles, each skill, each kind and each phase, Core computes the effective right: the combination's grants, intersected with that phase's permissions. For one combination, each phase must be included in the previous one:

grants ⊇ invoke ⊇ present ⊇ select

The submit is checked apart, because whoever chooses is not always whoever submits: HR reserves an account, IT creates it. Those submitted_by names must hold the grant of every write operation the selection can contain.

What breaks this chain is a contradiction, refused at load time:

writteneffect
a permission given to a profile without the operation's granterror: it would never apply
a kind a profile can choose without being able to see iterror: the model could add it by guessing its id
a kind someone can choose, while none of those submitted_by names holds the grant of its write operationerror: the selection could never be submitted
a skill nobody can invoke, a kind nobody seeserror: dead config
a required task no profile can doerror: the playbook could never be submitted
an automation whose named owner (owner) lacks the grants of the playbook it startserror: it would fail every time
a rule that requires a proof no provider of the station can makeerror: the authentication request would be impossible
a skill exposed on a portal whose provider cannot open its rulewarning: unavailable on that portal, with no request

The matrix​

The rights graph reads as a matrix, published with the config:

combinationarticles · the other itemsarticles · kind premium
anonymeinvoke, present, selectinvoke; removed at present
premiuminvoke, present, selectinvoke, present, select
no profile——

The config's tests: can assert it:

tests:
- name: "a visitor does not see premium products"
given: {profile: anonyme}
when: {invoke: {skill: articles}}
expect: {never_kinds: [premium]}

What the graph does not decide​

  • A dynamic condition — an amount, a budget, $params.team in identity.teams — is not decided at load time. The graph counts it as "maybe", never as "always".
  • A profile condition Core cannot compare (beyond ==, !=, in, and, or, not on identity fields) makes its profiles "cumulative" out of caution: the graph then checks the union.

Two keys​

The graph proves the config is coherent, at load time. It does not replace the check at run time: when Core calls the tenant's API, it checks the grants in the config again, and the present filter applies to the real result. If the graph were wrong, execution would still refuse.