Adapter conformance + capability report

July 25, 2026 ยท View on GitHub

bernstein adapters check answers one question:

Of the adapters this install declares, which ones are reachable on this machine, which ones conform to the contract, and which ones are missing binaries or have drifted?

The command is a one-shot inventory. Re-running it is cheap (no network, no agent spawn) and the output is consumable both by humans (Rich table) and by CI dashboards (--format json).

TL;DR

CommandWhen to use it
bernstein adapters listQuick "what ships?" listing - one line per adapter, no contract probing
bernstein adapters list-statusCompact conformance status per adapter (Rich table)
bernstein adapters checkFull table - binary path, version, capabilities, conformance, notes
bernstein adapters check <name>Drill down to one adapter
bernstein adapters check --format jsonCI-consumable payload keyed on adapters and summary
bernstein adapters check --strictExit non-zero on any conformance == "fail" row
bernstein adapters verify <name>Re-derive the admission fingerprint and check it against the sealed receipt
bernstein adapters verify <name> --sealSeal and anchor a fresh admission receipt from the current evidence

What every row contains

AdapterStatus(
    name="claude",
    module_path="bernstein/adapters/claude.py",
    binary_resolved="/usr/local/bin/claude",
    version_string="1.0.42",
    capabilities=frozenset({"--model", "--effort", "--permission-mode", ...}),
    conformance="ok",
    conformance_detail="",
    last_modified_utc="2026-05-17T16:15:21.849480+00:00",
    contract_hash="8a3f42703dc55c2442ca8c0620bd25fd1823748e970d03519434ac885439dbc5",
)

Field-by-field:

  • name - registry key.
  • module_path - repo-relative source path of the adapter module.
  • binary_resolved - shutil.which(binary) output, or None when the binary is not on PATH (or the adapter is a no-binary wrapper such as mock, generic, or openai_agents).
  • version_string - first non-empty trimmed line of <binary> --version, captured with a 5-second timeout. None when the call fails, times out, or the binary is missing.
  • capabilities - flags + subcommands the contract YAML declares as the required surface. Empty when no contract exists.
  • conformance - one of ok / fail / skip. See verdict table below.
  • conformance_detail - human-readable reason. Empty on ok.
  • last_modified_utc - ISO-8601 UTC mtime of the adapter module file. Useful as a cache key in dashboards.
  • contract_hash - SHA-256 of the loaded contract bytes. Empty when the adapter has no contract on disk.

Verdict table

VerdictWhen?
okBinary present, <binary> --help advertises every required flag and subcommand
failBinary present but help text is missing at least one required token (contract drift)
skipNo contract on disk, or binary missing, or --help failed / timed out

skip is the default state for adapters that simply have no contract yet - operators see "we know about it, but we haven't promised what it does". fail is the only verdict that should ever turn red on a healthy install.

JSON contract

{
  "adapters": [
    {
      "name": "claude",
      "module_path": "bernstein/adapters/claude.py",
      "binary_resolved": "/usr/local/bin/claude",
      "version_string": "1.0.42",
      "capabilities": ["--effort", "--include-hook-events", "--model"],
      "conformance": "ok",
      "conformance_detail": "",
      "last_modified_utc": "2026-05-17T16:15:21.849480+00:00",
      "contract_hash": "8a3f4270..."
    }
  ],
  "summary": {
    "total": 46,
    "reachable": 10,
    "conform": 6,
    "fail": 0,
    "skip": 38
  }
}

Capabilities are emitted as a sorted list so downstream diffs are stable.

Strict mode

--strict flips the process exit code:

  • No fail rows -> exit 0.
  • Any fail row -> exit 1.

skip rows are tolerated - they typically reflect "operator has not installed this CLI yet", which is informational not actionable.

Conformance check details

The check is in-process and never spawns pytest:

  1. shutil.which(binary) resolves the upstream CLI.
  2. If reachable, <binary> --version is captured with a 5s timeout.
  3. The contract YAML (when present) is loaded and hashed.
  4. The contract's required flags + subcommands are matched against <binary> --help output (token-boundary regex; ANSI escapes are stripped).

Adapters that lack a contract YAML fall through with conformance == "skip" and an empty capability set. The report is a survey surface, so a skip here is informational.

At the spawn boundary the same skip is not informational. Admission receipts refuse an adapter whose conformance verdict is anything other than ok and seal a refusal record naming the reason and the remediation, so an unverified adapter cannot receive task context on the strength of being importable. See adapter admission receipts.

CI integration

# .github/workflows/adapters-check.yml
- name: Adapter conformance gate
  run: bernstein adapters check --strict --format json | tee adapters.json

Dashboards parse adapters.json and surface per-adapter trend lines. The contract_hash field is the recommended cache key when stitching historical reports.

  • bernstein adapters list - quick "what ships?" enumeration; no contract probing.
  • bernstein adapters contract-check <name> - deeper contract check for a single adapter (includes model-list assertions when an auth secret is set).
  • bernstein adapters verify <name> - re-derive the deterministic admission fingerprint and check it against the sealed receipt; exits non-zero when the adapter would be refused at spawn.
  • bernstein doctor - host-readiness checks; surfaces a subset of the adapter status alongside other environment signals.