Skip to content

Contract identity

In Aster, every service contract has a deterministic, content-derived identity. The contract_id is the BLAKE3 hash of the contract’s canonical byte representation. It is not assigned, not incremented, and not coordinated – it is computed from the content itself.

A contract_id is derived from the contract definition, not assigned by a registry or chosen by a developer. Two implementations of the same service interface – whether written in Python, Java, Rust, or any other supported language – produce the same contract_id if they define the same methods, types, and semantics.

This is the same principle behind content-addressed storage systems like Git (SHA-1 hashes of objects) or IPFS (CIDs). The content determines the identity. No coordination required.

contract_id = blake3(canonical_xlang_bytes(ServiceContract))

For content addressing to work across languages, the serialized bytes must be identical regardless of which language produces them. Aster defines a constrained serialization profile called the canonical XLANG profile:

  • Fields emitted in ascending field ID order. Not the language’s natural field order, not alphabetical – a fixed, deterministic order based on field IDs.
  • Schema-consistent mode. No per-object type metadata in the payload. Types are known statically from the contract definition.
  • No reference tracking. Contract descriptors are acyclic trees (with self-references handled by a placeholder mechanism), so reference tracking overhead is unnecessary.
  • Standalone serialization. No session context, no shared caches, no state from prior objects. Each canonical byte sequence is self-contained.
  • No compression. Canonical bytes are stored and hashed uncompressed.
  • UTF-8 strings. All strings are encoded as UTF-8, even though Fory may normally choose LATIN1 or UTF-16 opportunistically.
  • NFC-normalized identifiers. Method names, type names, package names, and other identifiers are normalized to Unicode NFC form before encoding. This prevents different Unicode representations of the same visual string from producing different hashes.

With these constraints, a conforming Fory implementation in any language produces byte-identical output for the same contract definition.

Types reference each other by hash, forming a Merkle DAG (directed acyclic graph):

Contract and type referencesA ServiceContract references three MethodDefs. The first method references two TypeDefs. Each references a FieldDef, and one field references another TypeDef.ServiceContractMethodDefMethodDefMethodDefTypeDefTypeDefFieldDefFieldDefTypeDef
Each definition references the types it uses. Changes propagate through those references.

Each TypeDef is independently hashed. A MethodDef references its request and response types by hash. A ServiceContract references its methods. Changing any type in the graph changes its hash, which propagates upward through every contract that references it.

This gives you automatic change detection. If a type changes, every contract that uses it gets a new contract_id. Consumers looking up the old contract_id will find no providers – a clean signal that the interface has changed, rather than a runtime deserialization error.

Type graphs can contain self-references (e.g., a tree node type that references itself). The canonical encoding handles this with an SCC-based (strongly connected component) cycle-breaking algorithm. Self-referential types use a placeholder hash in place of their own hash when computing canonical bytes, ensuring the encoding terminates and remains deterministic.

A ServiceContract is the top-level descriptor for a service:

Field Description
name Service name (e.g., "TaskManager")
version Integer version number
methods List of MethodDef entries, sorted by name
serialization Default serialization modes for the service
scope Service scope: shared (stateless) or stream (session-scoped)

The contract_id is the BLAKE3 hash of the canonical XLANG serialization of this structure.

Each method in a service has a MethodDef:

Field Description
name Method name (e.g., "assign_task")
pattern RPC pattern: UNARY, SERVER_STREAM, CLIENT_STREAM, BIDI_STREAM
request_type BLAKE3 hash of the request TypeDef
response_type BLAKE3 hash of the response TypeDef
idempotent Whether the method is safe to retry
timeout_ms Default timeout in milliseconds
capabilities Optional CapabilityRequirement for authorization

Methods can declare authorization requirements that the trust model enforces:

Kind Meaning
ROLE Caller must have a specific role attribute
ANY_OF Caller must have at least one of the listed capabilities
ALL_OF Caller must have all of the listed capabilities

These requirements are checked by framework interceptors against the attributes in the caller’s CallContext, which are populated from their verified enrollment credential.

Version-safe discovery. A consumer looks up a contract_id in the registry. If the service interface has changed, the old contract_id returns no results. The consumer knows immediately that it needs an updated client, rather than discovering the mismatch at runtime through deserialization failures.

Compatibility detection. The registry can store compatibility reports between contract versions. A consumer can check whether its known contract_id is compatible with a provider’s contract_id before attempting a call.

Cross-language interop verification. A Python service and a Java client that compute the same contract_id are guaranteed to be wire-compatible. The hash proves that both sides agree on the exact same method signatures, type definitions, and serialization semantics.

When a contract is published to the registry, it is packaged as an iroh blob collection with a ContractManifest at index 0. The manifest is a JSON document that maps logical names to blob hashes:

Member Content Required
manifest.json ContractManifest metadata Yes
contract.xlang Canonical XLANG bytes of ServiceContract Yes
types/{hash}.xlang Canonical XLANG bytes of each TypeDef Yes
schema.fdl Human-readable Fory IDL source No
docs/ Documentation bundle No

The manifest also carries VCS provenance information (commit hash, repository URL) when available, linking the published contract back to its source. Consumers verify the contract by checking that blake3(contract.xlang bytes) == contract_id before trusting the bundle.