Skip to main content

Module client_contract

Module client_contract 

Expand description

Per-op contract digests (cratestack#1123, EXT-14): the 32 bytes that move only when the wire shape of one op changes.

schema_digest hashes the whole IR, so a policy, an @@index, a view’s SQL or a new procedure changes it and every signed client built before that edit is refused. An op’s contract is the closure a peer decodes it under: its transport, key and kind, its input and output roots, and every model, type, enum and view reachable from them (through fields, generic arguments, relations and @computed(params:)), each in its wire projection (@server_only fields removed).

Out entirely: the datasource, config blocks, extension blocks, the auth block, the schema-level MCP config, mixins (the parser expands them into fields), queries, every other op, docs and spans. Attributes on the reviewed DROPPED_ATTRIBUTES list are filtered; every other attribute, known or not, stays in, so a new one moves digests until it is reviewed onto that list. The digest is SHA-256(OP_CONTRACT_DOMAIN || canonical JSON); if the derivation rules change, the domain tag moves to op-contract/v2.

The COSE AAD binds the digest of the op a message calls (binding version 2; ADR 0006 §4); [table] holds the tables generated code carries and how a route finds its row. cratestack contract digest|print shows the digests.

A server may keep accepting older contracts of an op: ContractLock records the ones in the field, classify decides which are still wire-compatible with the current one, and include_server_schema!’s contracts = argument turns the survivors into ACCEPTED_CONTRACTS (an incompatible entry is a compile error). cratestack contract lock|check|prune maintain the file.

Structs§

ContractLock
A parsed, integrity-checked lock file.
Generation
One locked moment: the client contract digest and each op’s digest then.
Incompatible
A locked contract the current one no longer stays compatible with.
Pruned
What a prune removed.

Enums§

LockError
Why a lock could not be read or used.
Verdict
The classifier’s answer for one op.

Constants§

BATCH_CONTRACT_KEY
The row of a signed /rpc/batch: the route the AAD already binds.
CLIENT_CONTRACT_DOMAIN
Domain-separation tag of client_contract_digest.
LOCK_FORMAT
The lock file format this build reads and writes.
OP_CONTRACT_DOMAIN
Domain-separation tag of an op digest.

Functions§

bound_contracts
The digest a call to method route binds, for every key of schema (what the macros emit as OP_CONTRACTS): each op’s digest, plus the batch row for transport rpc. In key order, which nothing relies on.
classify
Judge the contract old (a canonical op contract, as stored in a lock) against new (the schema’s current one), both as JSON values of the shape op_contract_json prints.
client_contract_digest
A build identity for the client-facing contract: a hash over the sorted [key, hex(op digest)] table. It moves when any op’s contract moves, a new op appears or one disappears.
digest_hex
Lowercase hex of a digest.
find_contract
The row a call belongs to: an RPC route is its op_id (or batch); a REST route is bound as the template, and keyed "<METHOD> <template>". No allocation: an RPC id never starts with a method and a space. A HEAD is the GET op’s (axum serves it from that route; the binding still names the method it was sent with).
op_contract_digest
The digest of the op key (an RPC op_id, or "<METHOD> <route>" on REST), or None when the schema exposes no such op.
op_contract_digests
Every op’s digest, sorted by key.
op_contract_json
The canonical JSON an op digest hashes (what cratestack contract print shows), or None when the schema exposes no op with that key.
op_keys
The key of every op schema exposes, sorted: the keys the generated routers and clients expose, asserted equal to the macro’s OPS / ROUTE_TRANSPORTS by the parity test.

Type Aliases§

AcceptedContracts
What a server accepts: per op (and batch), the digests a request may bind, current first, then older compatible ones newest first.
OpContracts
What a client stamps: one digest per op (and batch), emitted in key order for a stable diff. Lookup (find_contract) is a linear scan: nothing relies on the order.