Chio/Docs

LearnActors & Federation

Model Cards

A model card binds model weights to allowed capability scopes and banned tools.

The binding contract

A capability token answers what an agent may do. Workload identity answers which process may present it. A model card answers a third question: which weights are behind the provider the kernel is about to route to, and what scope those weights are cleared for. In the autonomous-commerce loop, the kernel checks model provenance before it binds a provider. The ModelCard type binds:

  • weights_hash: the loaded weights this card describes
  • allowed_capability_set: the maximum scope the kernel may grant under this card
  • banned_tools: tools the kernel must not route to under this card
  • training_data_class: a coarse data classification carried for operator compliance reasoning

Provenance and liveness fields, issuer, issued_at, and expires_at, round out the declaration. The card crate is chio-weights(repo-relative crates/trust/chio-weights); the kernel composes its types into the bind-time decision, but does not own the schema.


Card schema

The v1 schema is at spec/schemas/model-card.v1.json. All fields are required and unknown fields are rejected. A decoded card must re-encode to the input bytes or validation rejects it.

FieldTypeMeaning
card_versionstringSchema version. Must be the literal "1" for v1 cards; any other value rejects at validation.
weights_hashstringLowercase 64-character hexadecimal SHA-256 digest (^[0-9a-f]{64}$) of the canonical weights blob.
allowed_capability_setstring setSorted unique scopes. Upper bound on what the kernel may grant. Provider bind rejects when the requested set is not a subset.
banned_toolsstring setSorted unique tool identifiers the kernel must not route to. Any intersection with the requested set rejects at bind.
training_data_classstringFree-form data classification. Surfaced to operators; not interpreted by the kernel.
issuerstringLogical issuer identity (URI / OIDC subject / SAN). Cosign verification pins this against the verified certificate identity.
issued_atRFC 3339 UTCTimestamp at which the issuer minted the card.
expires_atRFC 3339 UTCExpiry. Must be at or after issued_at. Bind rejects expired cards fail-closed.

A minimal card, shown with keys in canonical (sorted) order:

model-card.json
{
  "allowed_capability_set": ["tool:read", "tool:write"],
  "banned_tools": ["tool:exec"],
  "card_version": "1",
  "expires_at": "2026-05-30T12:00:00Z",
  "issued_at": "2026-04-30T12:00:00Z",
  "issuer": "https://example.com/issuer",
  "training_data_class": "public-internet",
  "weights_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

Canonical encoding

The on-the-wire byte form is RFC 8785 canonical JSON (JCS): object keys sorted by UTF-16 code units, string sets serialized as sorted unique arrays, timestamps as RFC 3339 UTC, no inter-token whitespace. Two implementations produce the same bytes for the same card. The cosign signature covers those bytes. A noncanonical re-encoding changes the digest and fails bundle validation.

Set fields are typed as StringSet, a sorted unique collection with entry hygiene enforced at construction: covers tests subset containment and intersects tests overlap. Empty entries, entries with surrounding whitespace, and duplicate entries are all rejected. Decoding via ModelCard::from_canonical_json validates the structure, then re-encodes and byte-compares against the input, so pretty-printed or key-reordered bytes do not decode as valid signed material.

Signatures cover the canonical bytes

The cosign bundle signs the RFC 8785 bytes, not a re-serialized object. A card presented as anything other than its canonical byte form fails verification even if every field value is correct. Store and transmit the exact bytes the issuer signed.

Provider binding checks

The kernel binding refusal lives in chio_kernel::weights_binding. It evaluates a WeightsBindingRequest against a cosign-verified card and returns Ok(()) only when these three checks pass, in order:

  1. Weights match. The provider's loaded weights_hash byte-equals the card's weights_hash.
  2. Scope is a subset. Each requested scope is in the card's allowed_capability_set.
  3. No banned tool. No requested tool intersects the card's banned_tools.

The first failing check stops evaluation. The order is stable, so a request that fails multiple checks reports weights mismatch first.

rust
use chio_kernel::weights_binding::{evaluate_weights_binding, WeightsBindingRequest};
use chio_weights::card::StringSet;

let request = WeightsBindingRequest {
    // lowercase hex sha-256 the provider computed from its loaded blob
    loaded_weights_hash: provider.loaded_weights_hash(),
    requested_scopes: &requested_scopes,
    requested_tools: &requested_tools,
};

// Ok(()) only when weights match, scope is a subset, and no tool is banned.
evaluate_weights_binding(&card, &request)?;

Each check maps to a stable URN for telemetry and audit records:

GateErrorURN
Weights mismatchCardMismatchurn:chio:error:weights:card-mismatch
Scope not a subsetScopeNotSubseturn:chio:error:weights:scope-not-subset
Tool bannedToolBannedurn:chio:error:weights:tool-banned

A banned-tool intersection rejects at provider binding, before a tool call can reach the provider.

Hosted weights fail closed

The refusal recomputes the digest from the loaded weights blob and rejects on mismatch. A hosted or protocol adapter that cannot expose recomputable loaded bytes passes an error into the loaded-hash gate; the kernel maps that unavailable state to a schema-level refusal. It does not accept a caller-supplied hash in its place. Required modes reject missing evidence.

Cosign verification

Before the kernel trusts a card, the cosign bundle is verified through verify_model_card_bundle, which wraps the existing Sigstore verifier in chio-attest-verify. Model cards use the existing trust root and signature path. The same cosign bundle verifier, including its PQ-hybrid interface, is used. A successful verify returns a VerifiedModelCard, pairing the parsed card with the upstream VerifiedAttestation (Fulcio certificate identity, OIDC issuer, Rekor log index, signing time).

An Ok result means the bundle verified against the supplied card bytes; the certificate identity matched the caller's expected identity and its OIDC issuer matched; the card decoded cleanly from the canonical bytes; the card's issuer equals the verified certificate identity; and the card's expires_at is strictly after the verifier's current time. An otherwise valid expired card returns urn:chio:error:weights:card-expired. The Sigstore trust root and confidential-compute context are covered in the TEE deployment guide.


Enforcement policy

Whether the kernel enforces card binding is set by policy.weights_card_required, loaded once at kernel start and serialized as a snake_case string for parity with the crypto floor.

ModeWire valueBehavior
DisableddisabledDefault. No card check; provider bind succeeds without a signed card.
RequiredrequiredBind must present a signed card whose weights match. Any bundle the configured trust root accepts is sufficient.
Required with pinrequired_with_pinLike required, plus the certificate SAN must match a configured issuer_san_regex.
policy.yaml
policy:
  weights_card_required:
    mode: required_with_pin
    # Pinned against the cosign Fulcio certificate SAN. Required in pin mode.
    issuer_san_regex: "https://issuer\.example\.com/.*"

Config validation is fail-closed at load, not at first bind. required_with_pin without an issuer_san_regex is rejected, as is an empty or non-parsing regex. A misconfigured deployment fails at startup. The pin regex is forwarded verbatim to the attestation verifier's expected-identity check.


Binding a card with the CLI

Operators bind a card to a provider with chio bind. The command loads the card, verifies the cosign bundle when supplied, and prints the resolved binding for review before enabling a required mode.

bash
$ chio bind srv-model --card ./model-card.json \
    --bundle ./cosign-bundle.json \
    --issuer-san-regex "https://issuer\.example\.com/.*" \
    --issuer-oidc https://token.example.com \
    --weights-binding-mode required

When the card loads cleanly, the resolved binding is printed for review:

bash
provider: srv-model
card: ./model-card.json
card_version: 1
issuer: https://issuer.example.com/models/prod
weights_hash: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
allowed_capability_set:
  - tool:read
  - tool:write
banned_tools:
  - tool:exec
training_data_class: public-internet
issued_at: 2026-04-30T12:00:00+00:00
expires_at: 2026-05-30T12:00:00+00:00
cosign: verified
weights_binding_mode: required

The --weights-binding-mode flag accepts not_required (default), required, required_with_pin, and unavailable. The two required modes reject the binding unless --bundle and the issuer pins are present. The unavailable mode is an explicit hosted-provider failure state for health evaluation, not a schema error. Each failure path carries a stable urn:chio:error:weights:* code. See the CLI reference for all flags.


Lineage anchoring

Publishing a verified card to the public registry emits a ModelCardLineageAnchor, produced by anchor_model_card and checked by verify_model_card_anchor. The anchor reuses the chio-lineage digest and signing-state shapes verbatim (schema chio.weights.lineage-anchor/v1), so the registry uses one format for lineage-graph and model-card anchors. The digest binds the SHA-256 of the canonical card bytes together with the attestation's subject digest, certificate identity, OIDC issuer, Rekor log index, and inclusion-verified flag.

A consumer reading an anchor recomputes the digest from the original inputs and rejects mismatches. These anchors do not construct a signed state themselves. An unavailable hybrid signature is recorded as UnsignedSoftDepAbsent, and an imported signed state is rejected until a local verifier can authenticate it against a trusted key.


Error codes

Each Err from the card API is typed and carries a stable URN.

VariantURNCause
Encodingurn:chio:error:weights:internal-encodingCanonical-JSON encode or decode failed, including non-canonical bytes.
MissingField / SchemaRejectedurn:chio:error:weights:schema-rejectedA structural invariant failed, e.g. weights_hash not 64 lowercase hex chars.
Expiredurn:chio:error:weights:card-expiredCard expired before the verifier evaluated it.
BundleRejectedurn:chio:error:weights:bundle-rejectedThe cosign bundle verify path rejected the bundle.
CardMismatchurn:chio:error:weights:card-mismatchLoaded weights_hash does not match the card.
ScopeNotSubseturn:chio:error:weights:scope-not-subsetA requested scope is not in allowed_capability_set.
ToolBannedurn:chio:error:weights:tool-bannedA requested tool is in banned_tools.

The URN registry these codes are drawn from is documented in the schemas and errors reference.


Where it fits

Model cards extend the zero-ambient-authority stance one layer below the capability. A capability constrains what an agent may request; a card constrains what the model behind the provider is cleared to serve. The card's allowed_capability_set is the ceiling that each capability against that provider must fit inside. The kernel checks this at bind with the signature and expiry checks.

Next steps

  • Capabilities · the scoped tokens that must fit inside a card's allowed set
  • Policy Schema · the full weights_card_required contract
  • TEE Deployment · the Sigstore trust root and attestation context the cosign path builds on
  • CLI Reference · chio bind and its flags