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 thegrantex.issuers entry point group and chosen
by name:
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 theIssuerAdapterConfig the SDK read from the environment. This one, from the
SDK’s test suite, talks to a fictional issuer at issuer.example:
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_attestationreturns an attestation whosekey_thumbprintis the thumbprint it was given, or raiseskey_binding_mismatch; the registry refuses an attestation for an unproven key withkey_unprovenin any case.- The JWS header’s
typis exactlygrantex-attestation+jwtand carries thekidof a key inissuer_metadata().jwks(or resolvable from the Entity Configuration). external_credential_hashissha-256:+ base64url(SHA-256) of the issuer-signed credential, as the Agent Passport hash rule states.fetch_statusreflects the issuer’s status list or API as ofchecked_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.