Appearance
Authorization & Identity
In Atelier, every entity, action, and surface is declared, not coded — and authorization works the same way. When you declare a new entity type, application, or role, you never write a permission branch by hand. Access rules are data, evaluated on every request, and compiled into the query itself. The result is one consistent authorization path that serves anonymous citizens, staff operators, and automated service principals alike — the single guarantee that sits beneath the generated portal, the staff admin, and the platform control plane.
Policy as data
Authorization in Atelier is resolved, not hardcoded. On every read and write, the platform asks a central question — who is this principal, what entity type and action, and in which tenant? — and gets back a set of grants. Those grants compile directly into the operation:
| Operation | What the platform does |
|---|---|
| List | Grants compile into a query filter, so a principal only ever sees rows they're allowed to see. |
| Detail read | Grants compile into a predicate evaluated against the record; denied access returns not-found. |
| Create / update / delete | Grants compile into a write guard evaluated against the request and the record's prior state; denied writes are refused. |
Because access is expressed as data and identity as graph membership, you extend authorization by declaring rules — never by editing code. Adding an entity type, a new application, or a new scope is a declaration the platform compiles, not a pull request against a permission engine.
Identity in four signals
Every request resolves to a principal described by four signals:
- Tenant — which customer environment the request belongs to, taken from the verified token. This is the root of all isolation.
- Application namespace — which generated application the principal is acting through.
- Organization & role membership — the principal's place in the organization hierarchy and the roles they hold, expressed as a membership graph.
- Per-application policy — the access rules declared for that application's entity types and actions.
Identity is membership in a graph, and policy is data layered on top of it. That's what lets a single code path serve a citizen submitting a report, a staff member triaging it, and a workflow acting on it automatically.
Fail-closed by construction
Two guarantees are locked into the platform and cannot be configured away:
- If the authorization service is unreachable, the request fails closed. Atelier never serves data it cannot prove the principal is allowed to see. An outage degrades to denial, never to exposure.
- If no grants are returned, access is denied. A principal with no applicable grants — including one whose organization membership is empty — resolves to deny-all. This is a permanent, deterministic denial, distinct from a transient outage.
There is no implicit-allow fallback anywhere in the path. Access is something you are explicitly granted, evaluated fresh on every request.
Per-tenant isolation
Tenant identity comes from the verified token, never from a client-supplied header. Atelier treats inbound tenant hints as advisory: they must agree with the verified identity or the request is rejected. The result is strict, structural isolation — one tenant's principals cannot read or write another tenant's data, and there is no request shape that lets a caller assert a tenant it doesn't belong to.
Organization membership is the single source of truth for what a principal can do. Roles flow from a principal's place in the organization hierarchy, and authorization reads from that membership graph directly — not from claims a client could shape.
Roles and organization membership
Roles are declared as organization membership — a principal belongs to an organization with a role. From those memberships, Atelier derives everything downstream:
- Scoped access. Membership in an organization grants access scoped to that organization and, where the hierarchy implies it, the organizations beneath it.
- Administrative tiers. An administrative role on a tenant's root organization confers tenant-level authority. An administrative role on the platform's own root organization confers platform-level authority over the control plane. Tiers are derived from membership, not granted by a separate, parallel mechanism.
You manage who-can-do-what by managing memberships and the rules declared against entity types — a model that stays legible as your organization grows.
Authoring access policy
Because policy is data, you author it through declarative surfaces rather than code:
- Access rules are declared per application, referencing the fields and shape of the entity types they govern. An entity type that carries an organization relationship is automatically scoped to that organization; one bound through a parent is governed through that parent; and membership-aware policy templates can reference the caller's own organization memberships so a single rule adapts to whoever is asking.
- Roles are organization-membership rows. Writing a membership flattens automatically into the underlying access graph, and an administrative role on a root organization elevates into the appropriate tier.
- Action gating is authored alongside each action, declaring who may run it and under what conditions (see The Action Engine).
- Public and citizen-facing surfaces are declared as public surfaces and portal pages (see The Surface Plane and The Citizen Portal).
The platform derives concrete access rules from these declarative inputs — the entity's schema, which application owns it, and the role-to-action matrix — so the rules you author stay close to the data they protect.
How access is provisioned
Authorization provisions itself the same way the rest of the platform does — automatically, from declarations.
- At startup, the platform registers each application and its entity types from a template application, seeds the baseline access rules and administrative tiers, and reconciles the membership graph.
- At runtime, writing an organization membership flattens it into the access graph; authoring an action reconciles its gating rules; and creating a new application provisions its policy namespace and identity client.
- On demand, reconcilers re-assert the membership graph without downtime.
When you fork a tenant from a template, its applications, entity-type ownership, configuration, and actions copy across and rewire to the new tenant. The tenant's first root-organization administrator is seeded automatically and becomes that tenant's administrative tier. The platform's own control-plane tenant is provisioned once, and its root-organization administrators become the platform tier — administrative authority is always an explicit membership, never a standing backdoor. Citizens self-provision an identity and tenant membership on their first authenticated interaction.
Extending authorization
Because everything is declared, the common extensions are declarations, not code changes:
- A new entity type to protect — declare its schema and its owning application; scoping is inferred from whether it carries an organization relationship, is bound through a parent, or is global. Access rules emit automatically under that application's namespace.
- A new scoping dimension — declare it, with its operator and policy template, and author it through the access-rules declaration block.
- A new application — write the application declaration; the platform provisions its policy namespace, entity types, and identity client.
- A new administrative capability — extend the relevant tier's grant set.
In each case the platform turns the declaration into compiled, enforced policy — no permission branch to write, no engine to modify.
Cross-tenant authoring
Platform and tenant administrators can author into the tenants they're entitled to manage. That entitlement is checked against an independent attestation of tier membership — separate from ordinary data access — so the right to configure a tenant is never conflated with the right to read its data. This keeps administrative authority explicit, auditable, and scoped to exactly the tenants an operator owns.
Example: policy as data
Access is declared, not coded. An action carries a permissions_policy naming the roles allowed to see it, apply it, and edit its form. A public surface declares exactly which states and fields ever leave the system. Everything else is denied by default.
yaml
actions:
- key: validate_proposal
target_model: proposal
# who may view the action, apply it, and edit its form
permissions_policy:
view: ["admin", "staff"]
apply: ["admin"]
edit: ["admin"]
public_surfaces:
proposal:
# only these states are ever publicly readable...
published_states: [validated, in_voting, winner]
status_field: status
# ...and only these fields are projected for disclosure
public_fields: [title, status, location]
is_active: trueRow-level rules can be membership-aware, scoping records to a user's own organizations — declared as policy and compiled into every query, never branched in application code.