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.
The situation
A workflow running as a service account needs to read the requesting person’s calendar, and also call a model with the company key. The activity should not know where each credential lives.
The recipe
import { getActivityIdentity } from '@hotmeshio/long-tail';
export async function proposeSlots(input: { orderId: string }) { const identity = getActivityIdentity(); if (!identity) throw new Error('getActivityIdentity runs inside an activity'); const google = await identity.getCredential('google'); const anthropic = await identity.getCredential('anthropic'); // ...call both APIs}Each call resolves through the same cascade:
- The executing principal’s stored credential (
lt_oauth_tokens, refreshed automatically when expired). - The initiating person’s credential, when
execute_asis active and the principal lacks one. - A well-known environment variable such as
ANTHROPIC_API_KEY.
The guarantee
When nothing in the cascade resolves, getCredential() throws MissingCredentialError. The interceptor catches it and opens an escalation with category: 'missing_credential', so a person can supply the value through a masked field and the run continues instead of failing. The credential source (user, bot or system) is tracked for billing and audit. OAuth tokens are encrypted at rest with AES-256-GCM. See Identity.
Related patterns
Identity
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 escalation boundary
Ask for what is missing
When a run lacks a fact it needs, it asks the one person who has it with a form for just that field, then continues.
Identity
Ephemeral credential tokens
A password typed into a form reaches the tool as an opaque token, exchanged for plaintext only at the moment of the call.
