Initiator and principal
A run records who asked and who executes as separate facts, so a service account can do the work while the audit still names the person.
The situation
A nightly report runs with a read-only database credential that no person holds. When someone triggers it by hand, it should still run as the report account, and the audit should show both who pressed the button and which account did the work.
The recipe
Name the service account on the workflow’s profile:
workers: [{ taskQueue: 'reports', workflow: nightlyReport, config: { invocable: true, executeAs: 'data-bot' },}],Read both identities inside any activity:
import { getActivityIdentity } from '@hotmeshio/long-tail';
export async function loadOrders(input: { since: string }) { const identity = getActivityIdentity(); if (!identity) throw new Error('getActivityIdentity runs inside an activity'); identity.principal.id; // data-bot's user id identity.principal.type; // 'bot' identity.principal.roles; // the roles data-bot holds identity.initiatingPrincipal; // the person who started the run}A caller can also pass execute_as in the invocation body. A superadmin may act as any account; a caller with an admin-type grant may act as a non-superadmin account whose every role the caller holds at the same or a higher type.
The guarantee
The initiator is stored as initiated_by and never changes, even when execution is delegated. The principal decides RBAC and credential access. Every task row carries initiated_by, principal_type and executing_as, so “every run this account executed for that person” is one query. An execute_as override never exceeds the caller’s own authority; anyone else is refused with 403. See Identity.
Related patterns
Identity
Credential cascade
An activity asks for a provider credential by name and gets the principal's, then the initiator's, then the system's, with the source recorded.
Identity
Delegation tokens
A tool call carries a five-minute, scope-limited token for the person behind the run, which an external server trades for that person's access.
The escalation boundary
Know who resolved it
The resolver's identity rides the wake as $resolution and lands on the row as resolved_by, so follow-on work can go to the same person.
