No inheritance, no wildcards
An entity a role does not name is an entity that role cannot touch. There is no*, and no role
extends another.
This is more typing, and it buys the thing worth buying: reading one role file tells you exactly
what that role can do. You never have to resolve a chain to find out, and a new entity is not
silently readable by everyone the moment it is added.
What enforcement actually does
Grants are checked on every operation, through the API as well as through the interface. Two behaviours are worth knowing because they are not what people expect. Read responses drop fields the role may not read. Not an error, just absent. A list silently drops a filter on a hidden field, but an aggregate refuses it. These look inconsistent and are not. A list returning fewer rows leaks nothing. An aggregate that quietly ignored agroupBy would hand back a confidently wrong number, so it answers 403 instead.
404 and 403 mean different things
no relationship
You have no relationship to this app at all. It answers exactly as a missing app does. Whether an
app exists is not something an unrelated caller gets to learn.
the role denies it
You have a relationship, and the roles it grants do not permit this operation.
Writing them
upsert_role and remove_behaviour are the operations. Or edit roles/*.cordango.yaml directly.
In a generated application
A standalone application generated bycordango build carries its own users, authentication and
permissions, built from the same role definitions. The enforcement is a second implementation of
the same rules, pinned by hand-written fixtures that both products assert against.
Same roles, same grants, no shared code path. That is on purpose: two implementations that must
agree catch mistakes one implementation cannot.
