SDK capabilities & conformance

The aitp wheel (PyPI distribution aitp-sdk) ships a core surface (identity, handshake, TCT verify, delegation, revocation) plus several post-v0.1 surfaces — renewal, session bundles, SPKI pinning, the TCT verification cache, multi-hop delegation verification. Since aitp-sdk 0.4.0 all of these ship by default on the published wheel; only an older 0.3.x wheel or a custom --no-default-features build omits some. The playground therefore probes the installed wheel at runtime rather than assuming. This page covers that probe, how scenarios degrade cleanly, and the RFC conformance harness.

Source: src/aitp_playground/capabilities.py, src/aitp_playground/conformance.py, src/aitp_playground/api/health.py.

Feature detection

capabilities.py probes the installed wheel by hasattr (the same convention as tests/unit/test_sdk_blocked_features.py) and reports a stable set of feature keys. The probe never raises — if the wheel is absent (e.g. CI without it), every feature reports False and sdk_available is False.

Feature keyDetected byBacking surface
oidchasattr(aitp, "JwksProvider")RFC-AITP-0002 OIDC identity binding
session_bundlehasattr(aitp, "SessionBundleBuilder")RFC-AITP-0010 session bundles
spki_pinninghasattr(aitp, "SpkiPinVerifier")SPKI client-cert pinning
tct_renewalhasattr(AitpAgent, "build_renewal_request")RFC-AITP-0013 / RFC-AITP-0004 §8.1 in-band renewal
tct_cachehasattr(aitp, "TctStore")SDK-side cache for RFC-AITP-0005 TCT verification
multihop_delegationhasattr(aitp, "verify_delegation_multihop")RFC-AITP-0011 multi-hop delegation

The keys are stable across releases — scenarios reference them by name when declaring a required capability, so don't rename them. The probe is LRU-cached (the installed wheel doesn't change during a process lifetime); tests that monkeypatch the SDK call get_capabilities.cache_clear() to force a re-probe.

Helpers: get_capabilities() (full report dict), has_feature(name), sdk_available().

GET /capabilities

The probe is exposed so operators can see the wheel's true surface at runtime:

curl -s http://localhost:8000/capabilities | jq .
{
  "sdk_available": true,
  "version": "0.4.0",
  "features": {
    "oidc": true,
    "session_bundle": true,
    "spki_pinning": true,
    "tct_renewal": true,
    "tct_cache": true,
    "multihop_delegation": true
  }
}

version comes from aitp.__version__ if present, otherwise the installed distribution metadata for aitp-sdk (falling back to a bare aitp dist) — the compiled wheel doesn't always set __version__.

Getting a feature-complete wheel

Nothing special: the PyPI wheel is feature-complete. uv sync installs aitp-sdk with the full default surface, and a plain source build (maturin develop --release in aitp-rs/bindings/aitp-py) compiles the same defaults. A slimmed-down wheel only appears if someone builds with --no-default-features — the probe above is what keeps that (or an old 0.3.x wheel) from crashing scenarios. The Docker build compiles the wheel from the sibling aitp-rs source; its INSTALL_EXTRAS wiring is in docker.md. The Cargo feature gates and what each one turns on are documented by the SDK itself — aitp-rs · sdk-python.md § Build and the aitp-py README.

Graceful degradation

Scenarios that exercise a feature-gated surface check GET /capabilities (or the SDK raises) and degrade cleanly when the wheel lacks the feature — the step records a "feature not available" outcome instead of crashing the run. This is why you can run the whole scenario catalog against a core-only wheel and still get a clean event log.

The feature-gated step types and their scenarios:

FeatureStep typesDemo scenario
oidc(handshake with an identity_type: oidc agent)intra-org/oidc-identity
tct_renewalrenew_tctintra-org/tct-renewal
tct_cachetct_cache_statsintra-org/tct-cache-perf
session_bundleexport_session_bundle, verify_session_bundleintra-org/session-bundle
spki_pinningspki_pin_checkintra-org/spki-pinning
multihop_delegationdelegate / redeem_delegation (2-hop)intra-org/delegation-multihop

See aitp-integration.md for where each SDK surface is actually called.

Conformance harness

conformance.py catalogs the RFC conformance fixtures shipped by the specs repo (agentidentitytrustprotocol/schemas/conformance/, located as a sibling checkout) and reports which ones the installed wheel could execute. It's a metadata/readiness report — it does not run the fixtures; it classifies them. (The fixtures are owned by the spec; the SDK's own pass/fail status against them is the aitp-rs conformance matrix.)

Run it from the CLI:

uv run python -m aitp_playground.cli conformance
# Conformance corpus: /…/agentidentitytrustprotocol/schemas/conformance
#   installed SDK: aitp 0.4.0
#   fixtures: 53  (required for v0.1: 1)
#   by RFC:   {'RFC-AITP-0001': 3, 'RFC-AITP-0004': 11, 'RFC-AITP-0005': 10, ...}
#   by tier:  {'core': 46, 'draft': 7}
#   wheel readiness: {'available': 7, 'core': 46}
#   ok  fixture metadata valid

uv run python -m aitp_playground.cli conformance --json          # raw report
uv run python -m aitp_playground.cli conformance --fixtures-dir <path>

Each fixture carries metadata (id, rfc, status, required_for_v0_1, feature). The harness:

  • validates metadata — required fields present, status is one of core/draft/extension/reserved, rfc is RFC-AITP-####-shaped, and a non-core fixture can't be required_for_v0_1. Any violation makes the command exit non-zero (CI can gate on a malformed corpus).
  • classifies readiness per fixture against the installed wheel:
    • core — no feature gate; always runnable.
    • available — feature-gated and the wheel exposes the feature.
    • skipped — feature-gated and the wheel lacks the feature.
    • unknown-feature — the fixture names a feature the playground doesn't map (FEATURE_TO_CAPABILITY).

build_report() returns the structured form (total, required_for_v0_1, by_rfc, by_tier, by_readiness, metadata_errors, valid) — the same dict the --json flag prints.