pub(super) fn check_s1(
store: &Store,
ctx: &RpcContext,
req_agent_id: &str,
) -> Result<(), String>Expand description
S1: the caller may only act on itself.
ctx.agent_id is stamped at bus:register and is the agent’s slug;
req_agent_id is whatever the request named. Both are compared as
canonical db_agents.ids when they are not already byte-identical, so a
caller that authenticates with its slug and names itself by id (or by a
differently-cased slug) is recognised as itself.
Both sides resolve, or neither does. Phase 4 of
SPEC_CANONICAL_AGENT_ID_MIGRATION_2026_09_21.md §6 proposed stamping the
resolved id into RpcContext at registration instead, and flags the hazard
itself: resolving one side of this equality while the other stays a slug
silently denies every caller, turning an authz boundary into a
permanent outage that no test of the allow path would catch. Resolving both
here, at the single point of comparison, has no such window — there is no
deploy ordering in which one side has migrated and the other has not.
The byte-equality fast path is deliberate and load-bearing: it is the
overwhelmingly common case, it costs no store lookup, and it makes this
function’s behaviour for existing callers identical to the pre-migration
version. Resolution only ever runs where the old code would already have
returned FORBIDDEN, so this can admit calls that used to be rejected but
can never reject one that used to be admitted.
Admitting more is safe here precisely because resolution is per-input and
fails closed: two different inputs resolve to one id only when they name
one agent, and an ambiguous slug (two agents, one slug) resolves to
Err on both sides — so a collision denies rather than granting.
A failed resolution is reported as agent_id mismatch, never “no such
agent”: whether some other agent exists is not something an unauthorised
caller should be able to probe through this boundary.
Takes the store rather than &AppState because the handlers do not agree
on what they capture — some clone the whole state into the closure, others
clone mstore alone — and a borrowed &AppState cannot escape into a
'static future.