ARC resolves caller information from trusted credentials before it builds a session view. The resulting CallerInfo can carry a DID, authentication method, roles, and, when the server supplies a trusted instance context, an instance DID.
What the runtime knows
CallerInfo (AFS core type) is the handoff shape after resolution:
| Field | Meaning |
|---|---|
did | Caller DID, or null for anonymous. Treat null as anonymous, never as a sentinel string. |
roles | Role strings after resolution (often a one-element array). |
pk | Public key (hex) when available. |
authMethod | How the caller authenticated (for example passkey, access-key, did-connect). |
instanceDid | Instance context stamped by the server when membership or display needs it. |
displayName | Optional display name for UI. |
authSource | "cookie" or "bearer" — how the credential arrived. |
Mutating AFS RPC operations that care about CSRF require authSource: "bearer". A cookie alone is not enough for those writes.
Credential inputs
The shared caller core (services/shared → both Node and Cloudflare) accepts:
| Input | Resolution path |
|---|---|
Cookie: login_token=<JWT> | Browser session JWT. |
Authorization: Bearer <JWT> | Programmatic / mobile JWT (not an access key). |
Authorization: Bearer blocklet-… | Blocklet-service access key. |
Anonymous requests (no JWT candidate and no Authorization header) resolve to null. Invalid or unresolvable credentials also return null; the core does not throw for ordinary unauthenticated traffic.
Membership overlay
Instance membership is an overlay on trusted server context:
- The server resolves the request's tenant / instance DID.
- When that value is present, the general caller path sets
membership: trueand feeds it into connect-serviceresolveIdentity. - Connect-service can return a per-instance role (
owner/admin/member/guest) on top of the user's global role. - The client cannot create that relationship by naming an instance in a UI message or by forging an instance header on paths that strip or replace client-supplied instance identifiers.
Some ingress paths remove or replace a client-supplied instance header before resolution. Treat that as an ingress-specific safeguard, not as a universal guarantee for every ARC entry point you invent later.
Role ordering fails closed
Role comparison uses a fixed ordinal:
| Role | Level |
|---|---|
guest | 0 |
member | 1 |
admin | 2 |
owner | 3 |
Any unrecognised or absent role maps to level 0 (guest). Do not invent intermediate roles in application code and assume the runtime will honor them.
Keep presentation separate from authorization
Use a session fact to decide whether a page should show an action. Do not treat that condition as the authorization check. The provider and runtime still decide whether the resolved caller may read, write, execute, or traverse a resource.
The same rule applies to identity input. A text field, a URL parameter, a page route, or a $session substitution cannot establish a caller identity. It may name a resource, but only server-side credential resolution can determine who is asking.
AUP-facing guidance for the same boundary: Caller identity and safety.
Evidence
| Claim | Evidence |
|---|---|
Shared credential → CallerInfo mapping | services/shared/src/caller.ts (mapToCallerInfo, resolveCallerFromCredentials) |
| Node daemon auth wire-up | runtimes/node/src/daemon/auth/index.ts (setupAuthService, resolveCaller) |
| Role fail-closed ordinal | packages/aos/src/session/role-level.ts |
CallerInfo field contract | packages/core/src/type.ts |
| Read-only memberships AFS view | @aigne/afs-members (providers/basic/members/) — list/get only; not a membership-admin API |
Current boundary
This page documents caller resolution and its fail-closed role behavior. It does not document account creation, DID recovery, membership administration UI, or a complete instance-identity lifecycle. Do not infer those flows from the presence of a DID or a role in a session.