@@ -1009,12 +1009,13 @@ def _build_request_context(
10091009 :param auth_info: Optional verified principal info — when present
10101010 and carrying a non-``None`` principal, ``auth_principal`` is
10111011 populated from ``auth_info.principal``. Otherwise the helper
1012- falls back to :data:`adcp.server.auth.current_principal` —
1013- the ContextVar :class:`BearerTokenAuthMiddleware` populates —
1014- so bearer-flow callers get a typed read for "who's calling?"
1015- without reaching into framework-private state. Returns
1016- ``None`` outside both flows (no-op for unauthenticated dev
1017- fixtures).
1012+ synthesizes an :class:`AuthInfo` (``kind="bearer"``,
1013+ ``credential=None``) from :data:`adcp.server.auth.current_principal`
1014+ — the ContextVar :class:`BearerTokenAuthMiddleware` populates —
1015+ so bearer-flow callers get both a typed ``ctx.auth_info`` and
1016+ ``ctx.auth_principal`` read without reaching into framework-
1017+ private state. ``ctx.auth_info`` stays ``None`` outside both
1018+ flows (no-op for unauthenticated dev fixtures).
10181019 :param store: The AccountStore that produced ``account``. Required
10191020 for the production cache-isolation guarantee; the dispatch
10201021 adapter always supplies it. Test fixtures may pass ``None``
@@ -1028,34 +1029,45 @@ def _build_request_context(
10281029 # Local import to avoid a circular at module-load time. dispatch.py
10291030 # is imported by serve.py; context.py and accounts.py both reach
10301031 # back into adcp.decisioning, so the cycle is real if we hoist.
1031- from adcp .decisioning .context import RequestContext
1032+ from adcp .decisioning .context import AuthInfo , RequestContext
10321033 from adcp .decisioning .resolve import _NotYetWiredResolver
10331034
1034- # ``auth_principal`` is the typed "who's calling?" read for
1035- # adopter handlers. Two sources populate it :
1035+ # ``auth_info`` / `` auth_principal`` are the typed reads adopter
1036+ # handlers use . Two sources populate them :
10361037 #
10371038 # * Signed-request flows hydrate ``AuthInfo`` upstream and the
10381039 # adapter passes it as ``auth_info``; ``auth_info.principal``
10391040 # carries the verified caller label.
10401041 # * Bearer-token flows (:class:`BearerTokenAuthMiddleware`) never
10411042 # construct an ``AuthInfo``; they stash the principal in the
10421043 # :data:`adcp.server.auth.current_principal` ContextVar instead.
1043- # Read it as the fallback so bearer adopters can gate on
1044- # ``ctx.auth_principal`` without reaching into the framework-
1045- # private ContextVar themselves. ``.get()`` returns ``None``
1046- # outside a bearer flow — that's the desired no-op for non-
1047- # bearer callers (signed-request without ``AuthInfo``,
1048- # unauthenticated dev fixtures).
1044+ # Synthesize one here so bearer adopters can branch on
1045+ # ``ctx.auth_info.kind == "bearer"`` (the typed flow
1046+ # discriminator) without reaching into the framework-private
1047+ # ContextVar themselves. ``credential=None`` is passed
1048+ # explicitly so :meth:`AuthInfo.__post_init__` skips the
1049+ # flat-field synthesis path and the accompanying
1050+ # :class:`DeprecationWarning` (see context.py:396-426): the
1051+ # sentinel default fires synthesis, an explicit ``None`` does
1052+ # not. We don't know the bearer's ``key_id`` / ``scopes`` —
1053+ # bearer tokens are opaque to the SDK — so we leave those
1054+ # fields at their dataclass defaults; adopters who want richer
1055+ # data should write their own ``context_factory``.
10491056 #
10501057 # Local import keeps the layering local — read the bearer ContextVar
10511058 # without forcing a top-level dep on adcp.server.auth.
10521059 from adcp .server .auth import current_principal as _current_principal
10531060
1054- auth_principal = (
1055- auth_info .principal
1056- if auth_info is not None and auth_info .principal is not None
1057- else _current_principal .get ()
1058- )
1061+ if auth_info is None :
1062+ bearer_principal = _current_principal .get ()
1063+ if bearer_principal is not None :
1064+ auth_info = AuthInfo (
1065+ kind = "bearer" ,
1066+ principal = bearer_principal ,
1067+ credential = None ,
1068+ )
1069+
1070+ auth_principal = auth_info .principal if auth_info is not None else None
10591071
10601072 # ctx_metadata credential gate — fail-closed before any platform
10611073 # method sees the metadata. Buyers can populate ``context``
0 commit comments