No inheritance, no wildcards
An entity a role doesn’t name is an entity that role can’t touch. There’s no*, and no role
extends another.
It’s more typing. What you get for it is that reading one role file tells you exactly what that role
can do, without resolving a chain to find out, and a new entity isn’t silently readable by everyone
the moment somebody adds it.
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 surprise people. 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. Those look inconsistent. A list returning fewer rows leaks nothing, while 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 isn’t something an unrelated caller gets to learn.
the role denies it
You have a relationship, and the roles it grants don’t 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 there 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’s on purpose. Two implementations that have to
agree catch mistakes a single one can’t.
