aeat.entrypoints.mcp._identity_gate module

Per-session block-first-mutation identity gate (ADR I2 / I4).

A tax filing MUST be tied to the correct taxpayer: Erika must not file while Erik is the active profile. This module enforces, in the pre-tool-use layer, that an identity READ has happened before the first state-changing call of a session - and re-arms that requirement whenever the active profile could have changed. It cannot verify the agent CHOSE the right taxpayer (only the human can, through the I4 elicitation echo); what it enforces is that an identity read HAPPENED, the same fail-closed ordering discipline the project takes on under-declaration.

Like _hitl / _elicitation this is SDK-independent pure logic: a per-session state object plus a decision function, unit-tested directly without the stdio transport. _server owns the per-session state instance and calls the decision function byte-identically on the direct call path and the execute meta path so the two cannot diverge.

Mutability is read from the DECLARED risk table (command_classification()), never a leaf-name heuristic: a call is mutating when its classification is not read_only.

PROFILE_SWITCHING_COMMANDS: frozenset[str]

Verbs that change WHICH taxpayer profile is active. Executing one re-arms the gate so the NEXT mutating call must re-confirm identity. This is the closed set of active-identity-changing verbs (switch, create-and-activate, enter a sandbox), not every config profile mutation - editing or renaming the current profile does not change who is active, so it does not re-arm.

IDENTITY_READ_COMMANDS: frozenset[str]

The read VERBS that constitute an identity read and clear the gate. These carry registry command keys, so identity_gate_refusal() matches them on both the direct and execute paths. The console identity reads (no command key) are IDENTITY_READ_CONSOLE_TOOLS.

IDENTITY_READ_CONSOLE_TOOLS: frozenset[str]

The console identity-read TOOLS (no registry command key) that clear the gate; _server records the read on dispatch. whoami is the primary identity assertion. harness.load counts because P02 made the harness floor carry the active-identity block (build_whoami_identity()), so an agent that loaded the harness has already seen who is active - exactly what the gate guarantees - and it is the near-universal first call, so counting it cuts friction without weakening safety: a profile SWITCH still re-arms the gate, so identity is re-confirmed before any mutation after a switch.

class SessionIdentityState[source]

Bases: object

Per-session identity-read state for the block-first-mutation gate.

identity_confirmed starts False (armed): the first mutating call is refused until an identity read flips it True. A profile-switching verb re-arms it back to False so the next mutation re-confirms. The server holds exactly one instance per built session and shares it across the direct and execute paths.

property identity_confirmed: bool

Whether an identity read has occurred since session start / last switch.

record_identity_read()[source]

Mark that an identity read (whoami / status) has occurred this session.

Return type:

None

rearm()[source]

Re-arm the gate after a profile change; the next mutation must re-confirm.

Return type:

None

identity_gate_refusal(command_key, *, state)[source]

Return a localized refusal for an unconfirmed first mutating call, else None.

Evaluated for every verb call on both the direct and execute paths, in order:

  • A profile-switching verb re-arms state and is allowed (it is how identity is established, so it is never itself blocked).

  • An identity-read verb records the read on state and is allowed, whatever its declared mutability.

  • Any other read-only call is allowed and leaves the state untouched.

  • A mutating, non-switch call is refused (returns the localized refusal text) unless an identity read has occurred since session start or the last switch.

The refusal text carries no interpolation, so it is byte-identical on both call paths - the gate-invariance both prior MCP ADRs require.

Return type:

str | None

Parameters:
identity_elicitation_echo(*, active_profile_label)[source]

Return the localized identity echo prefixed to a CONFIRM elicitation (ADR I4).

Names the active-profile LABEL (never the redacted UUID) so the human approving a destructive or handoff verb sees whose data it touches and can catch an Erik/Erika mismatch at the gate. None (no active profile) renders a neutral placeholder rather than an empty name.

Return type:

str

Parameters:

active_profile_label (str | None)