Skip to main content

Safety demonstrations

The release gate asks that replay, issuer mix-up, cross-tenant access, stale approval, retries, restart, rollback, revocation, and rotation are demonstrated safely. Each is one named test, in both SDKs, that runs a real journey — Atlas, Ada's personal agent, with Manzanita, the reference company, over HTTP in one process with real keys — then does the unsafe thing and asserts the safe outcome: refused, or done exactly once.

pytest personal_agent_protocol/__tests__/test_security_demonstrations.py
node --test src/__tests__/security-demonstrations.test.mjs # after npm run build
PropertyWhat the test doesWhat it showsPython testTypeScript test
ReplaySends again, byte for byte, a captured token request (client and Session assertions, DPoP proof), an API read, and an operation's confirmation.Each is refused (400/401): assertion and proof IDs are one-time. The confirmation performs nothing more; the agent, with fresh proofs, carries on.test_replay_is_refusedreplay is refused
Issuer mix-upBegins Direct Sign-In at Manzanita, then completes it with the answer claiming another issuer (iss, RFC 9207).issuer_mismatch before any code reaches a token endpoint; the sign-in state is spent, so the genuine answer cannot be raced in afterwards; the Session stays signed out; starting again works.test_issuer_mix_up_is_refusedissuer mix-up is refused
Cross-tenant accessPresents Ada's signed-in token from another tenant of the same agent, and reads her conversation as another user.401: proofs are made with each tenant's own DPoP key, which the token is not bound to; another user's Session sees conversation_not_found. Ada's tenant is unaffected.test_cross_tenant_access_is_refusedcross-tenant access is refused
Stale approvalApproves revision 1 of an exchange; Manzanita revises it to revision 2 (a price difference); confirms with the old approval.terms_changed with the changed terms; the old approval does not fit revision 2 (approval_mismatch, refused locally); nothing is performed until the user approves revision 2; the stale approval presented again performs nothing.test_stale_approval_is_refusedstale approval is refused
RetriesSends one message ID twice; loses the answer to a confirmation that Manzanita received and performed; retries the confirmation.One conversation, one message; the lost answer is read back by the same operation ID, never assumed failed; the operation is performed once.test_retries_are_saferetries are safe
RestartRestarts Manzanita, twice, over its store (which holds the default replay cache), between a sign-in, a proposal, and its confirmation.A DPoP proof seen before the restart is still refused after it; the Session and the proposal carry over; the operation confirmed after one restart is not performed again after the next.test_restart_is_saferestart is safe
RollbackRolls Manzanita back to its previous release (no website) over the same store, after an Account Token rotation and a confirmed operation.The rotated-away Account Token stays refused and the current one works; the earlier release does not perform the confirmed operation again; Sessions and the API continue.test_rollback_is_saferollback is safe
RevocationSigns in two Sessions from one sign-in, then revokes the Account Token.Both signed-in tokens read as signed out at once (403), not at expiry; the Account Token is gone locally and refused at the company; the Session continues signed out.test_revocation_is_saferevocation is safe
RotationRefreshes the Account Token twice at once; then rotates the agent's signing key with an overlap and retires the old one.Refreshes are serialized, each rotating; the rotated-away token is refused, the live one works. Both signing keys verify during the overlap; once the company's cached key set expires, the retired key is refused (invalid_client) and the new one verifies.test_rotation_is_saferotation is safe

What a deployment must provide​

The restart and rollback demonstrations restart the company over one store, as a deployment's replicas share one (company guide). The default replay cache keeps its identifiers in that store (StoreReplayCache, an atomic create with the identifier's expiry), so a DPoP proof captured just before a restart, or at another replica, is refused after it: a durable, shared store implies a shared replay cache. With the in-memory store, the cache is one process, as the store is. A company that contributes its own pap.company.replayCache must share it across replicas the same way.

The same properties are also tested piecemeal, closer to the code: the conformance cases (security.*-replay, lifecycle.rotated-token, lifecycle.sign-out-*, operations.terms-changed, operations.confirm-timeout-recovery, conversations.retry-replays), the company store's restart test (test_company_store.py), the overlapping JWKS test (test_guide_security.py), and, for the pap CLI and live-session actions, the tenant tests (test_cli_protocol.py, test_live_session_actions.py).