Skip to content

ctx.caller_identity is composite scope-key, not bare principal_id (docs misleading) #571

@bokelley

Description

@bokelley

What

The RequestContext docstring (adcp/decisioning/context.py:476-509) describes auth_principal as "who's calling" and caller_identity as "what's the cache scope key" / opaque. In practice for bearer-token adopters:

  • BearerTokenAuthMiddleware populates current_principal ContextVar with the bare principal_id (whatever the adopter's validate_token returns on Principal.caller_identity)
  • auth_context_factory reads that contextvar and sets ToolContext.caller_identity from it
  • BUT — by the time downstream _impl code reads ctx.caller_identity, it's been mutated to the composite scope key <store_module>.<store_qualname>:<account_id> (e.g., core.stores.accounts.SalesagentAccountStore:wonderstruck:default)
  • ctx.auth_principal stays None for bearer flows — that field only populates on signed-request flows where AuthInfo.principal is set

Net: a bearer-token adopter trying to read "who's calling?" from ctx.caller_identity gets the composite, not the bare id. salesagent's _create_media_buy_impl uses identity.principal_id for a Principal-table lookup, which 404'd on the composite key.

Repro

# core/platforms/_delegate.py
def _build_identity(ctx):
    # WRONG — returns composite scope-key in bearer flows
    principal_id = ctx.caller_identity  # → "core.stores.accounts.SalesagentAccountStore:wonderstruck:default"
    return ResolvedIdentity(principal_id=principal_id, ...)

The fix in salesagent was to import the framework's contextvar directly:

from adcp.server.auth import current_principal
principal_id = current_principal.get()  # bare value, e.g. "wonderstruck_buyer"

That works but requires reaching into module-private state.

What we'd want

Either:

  1. Docs clarify that caller_identity is mutated to composite by downstream framework code, and document the correct read path for bearer adopters (adcp.server.auth.current_principal.get() or a typed helper)
  2. Populate ctx.auth_principal from the bearer flow too — give bearer adopters a typed read for "who's calling" without reaching into ContextVars
  3. Stop mutating ctx.caller_identity from its initial bare-id value; do composite-scoping inside the idempotency middleware via a separate field

(2) is probably the cleanest — auth_principal already exists as the "typed convenience field"; populating it on bearer flows matches its docstring intent.

Filed by

salesagent live GAM CRUD probe — surfaced when _create_media_buy_impl Principal lookup failed because identity.principal_id was the composite. Tracked as salesagent #42.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions