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 describesallowed_capability_set: the maximum scope the kernel may grant under this cardbanned_tools: tools the kernel must not route to under this cardtraining_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.
| Field | Type | Meaning |
|---|---|---|
card_version | string | Schema version. Must be the literal "1" for v1 cards; any other value rejects at validation. |
weights_hash | string | Lowercase 64-character hexadecimal SHA-256 digest (^[0-9a-f]{64}$) of the canonical weights blob. |
allowed_capability_set | string set | Sorted unique scopes. Upper bound on what the kernel may grant. Provider bind rejects when the requested set is not a subset. |
banned_tools | string set | Sorted unique tool identifiers the kernel must not route to. Any intersection with the requested set rejects at bind. |
training_data_class | string | Free-form data classification. Surfaced to operators; not interpreted by the kernel. |
issuer | string | Logical issuer identity (URI / OIDC subject / SAN). Cosign verification pins this against the verified certificate identity. |
issued_at | RFC 3339 UTC | Timestamp at which the issuer minted the card. |
expires_at | RFC 3339 UTC | Expiry. Must be at or after issued_at. Bind rejects expired cards fail-closed. |
A minimal card, shown with keys in canonical (sorted) order:
{
"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
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:
- Weights match. The provider's loaded
weights_hashbyte-equals the card'sweights_hash. - Scope is a subset. Each requested scope is in the card's
allowed_capability_set. - 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.
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:
| Gate | Error | URN |
|---|---|---|
| Weights mismatch | CardMismatch | urn:chio:error:weights:card-mismatch |
| Scope not a subset | ScopeNotSubset | urn:chio:error:weights:scope-not-subset |
| Tool banned | ToolBanned | urn: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
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.
| Mode | Wire value | Behavior |
|---|---|---|
| Disabled | disabled | Default. No card check; provider bind succeeds without a signed card. |
| Required | required | Bind must present a signed card whose weights match. Any bundle the configured trust root accepts is sufficient. |
| Required with pin | required_with_pin | Like required, plus the certificate SAN must match a configured issuer_san_regex. |
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.
$ 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 requiredWhen the card loads cleanly, the resolved binding is printed for review:
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: requiredThe --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.
| Variant | URN | Cause |
|---|---|---|
Encoding | urn:chio:error:weights:internal-encoding | Canonical-JSON encode or decode failed, including non-canonical bytes. |
MissingField / SchemaRejected | urn:chio:error:weights:schema-rejected | A structural invariant failed, e.g. weights_hash not 64 lowercase hex chars. |
Expired | urn:chio:error:weights:card-expired | Card expired before the verifier evaluated it. |
BundleRejected | urn:chio:error:weights:bundle-rejected | The cosign bundle verify path rejected the bundle. |
CardMismatch | urn:chio:error:weights:card-mismatch | Loaded weights_hash does not match the card. |
ScopeNotSubset | urn:chio:error:weights:scope-not-subset | A requested scope is not in allowed_capability_set. |
ToolBanned | urn:chio:error:weights:tool-banned | A 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_requiredcontract - TEE Deployment · the Sigstore trust root and attestation context the cosign path builds on
- CLI Reference ·
chio bindand its flags