Skip to content

Actors, access and identity

Concept Rule
Actor { id, roles }, passed as runtime.dispatch(cmd, { actor }) (default null, anonymous). Never read from the payload: a payload field named actor is stripped. decide gets it as ctx.actor.
SYSTEM Reactions and sent commands run as SYSTEM ({ id: 'system', roles: ['system'] }). Pass the exported SYSTEM object itself to dispatch as the system from the edge (a subscriber writing back); a structural copy does not count.
Reserved An external actor claiming the id or role system, or whose roles is not an array, is forbidden (params { actor: 'reserved' }) on every command and query before access is read.
Access Required on every command (ATLAS014): 'public' (anyone), 'user' (any actor), 'internal' (only sent by another capability), or ['role', ...] (any of these roles). Checked before reads and decide. Only public commands see actor: Actor | null.
Internal 'internal' commands are never dispatched, not even as SYSTEM: capabilities send them.
Membership, ownership Not in the actor. Check them in decide against records you read (order.customerId !== actor.id).
Query access Required on every query (ATLAS021): 'public', 'user' or roles, never 'internal'.

Default to 'user'; use 'public' only for anonymous use. For “my record”, key the read by the actor: key: (input, { actor }) => actor.id; never add a user id to the input.

Atlas never authenticates. An adapter resolves the actor. With Express, identify(req) is the identity port:

import { atlasRouter, unauthenticated } from '@ts-atlas/core/express';
app.use(atlasRouter(runtime, {
identify: async (req) => {
const token = req.header('authorization');
if (!token) return null; // anonymous
const session = await sessions.verify(token); // your auth
if (!session) throw unauthenticated(); // bad credentials: 401
return { id: session.userId, roles: session.roles };
},
}));

Since 0.5.0, any throw other than unauthenticated() is an outage (503 store-failed, reported to onError), so a client never drops a valid token because the session store failed. identifyErrors: 'unauthenticated' restores the 0.4 answer while migrating. See the Express guide.