Skip to main content
The registry asks an accredited issuer to attest an agent through one seam, AccreditedIssuerClient in the Python SDK (grantex.issuers). This stack governs what a principal has allowed an agent to do; an accredited issuer attests who the agent and its developer are. The adapter is where the two meet, and it is deliberately small so that an issuer can write one without reading the rest of this repository.

The interface

Three operations, nothing else: The adapter only ever sees public material. An issuer verifies the agent’s possession of proved_key its own way (its own challenge, signed by the agent); the registry has already done the same for its record. Private keys, API credentials and the issuer’s URL never pass through the registry. Every failure raises IssuerAdapterError(code, message). The loader’s codes are adapter_not_configured, adapter_not_installed, adapter_ambiguous and adapter_invalid; an adapter uses issuer_unreachable, issuer_response_invalid, key_binding_mismatch, and otherwise the issuer’s own refusal code (key_unproven, scope_not_accredited, passport_revoked, …), so a relying party sees one vocabulary. Nothing is caught and turned into a success; a status that cannot be fetched is an error, not valid.

Selecting an adapter

Adapters are found through the grantex.issuers entry point group and chosen by name:
With GRANTEX_ISSUER_ADAPTER=mock everything runs locally and in CI with no external party. Any other name must be installed: a name with no entry point fails closed with adapter_not_installed and a message naming the variable. The loader never falls back to the mock, and two packages providing the same name are refused (adapter_ambiguous) rather than picked between.

Writing one

An adapter is a class with the three operations and a factory that takes the IssuerAdapterConfig the SDK read from the environment. This one, from the SDK’s test suite, talks to a fictional issuer at issuer.example:
Package it on its own, outside this repository, with the entry point in its pyproject.toml; install it beside the SDK; set the variables; select it. Nothing in this repository changes, and nothing here needs to know the issuer’s name. The SDK’s test test_stub_entry_point_is_loaded proves the path with a stub distribution. What the adapter must guarantee:
  • request_attestation returns an attestation whose key_thumbprint is the thumbprint it was given, or raises key_binding_mismatch; the registry refuses an attestation for an unproven key with key_unproven in any case.
  • The JWS header’s typ is exactly grantex-attestation+jwt and carries the kid of a key in issuer_metadata().jwks (or resolvable from the Entity Configuration).
  • external_credential_hash is sha-256: + base64url(SHA-256) of the issuer-signed credential, as the Agent Passport hash rule states.
  • fetch_status reflects the issuer’s status list or API as of checked_at; a stale or unreadable source is an error.

The mock issuer’s adapter

GRANTEX_ISSUER_ADAPTER=mock builds grantex.issuers.MockIssuerClient, which drives the mock issuer CLI in a subprocess (no network): keys for metadata, issue-passport then attest for an attestation, status for status. It reads: The mock runs both sides of the possession proof itself, so it needs the agent’s private key file: the one issue-passport --generate-agent-key writes under <state dir>/agents/<thumbprint>.json, or a path named per thumbprint. That is a property of the mock only; a real issuer proves possession with the agent directly. The base URL and credential variables are ignored by the mock, which has no network side.
Last modified on October 1, 2026