Skip to content

TheProtocol registry card, version 0.6 ​

Version 0.6, 30 September 2026

This specification is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0, https://creativecommons.org/licenses/by/4.0/). Copyright 2025-2026 Raphael Zbigniew Jeziorny.

Conformance ​

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL in this document are to be interpreted as described in RFC 2119. A conforming registry serves a card that meets every requirement in section 10. A conforming verifier implements the verification procedure of section 7.4 and the digest check of section 5.4. The companion JSON Schema, registry-card-v0.6.schema.json (JSON Schema draft 2020-12), describes the same fields, types and value ranges as this text. Everything in this document that is not marked as a requirement, and every value described as the reference implementation's, is informative.

1. Overview ​

A registry card is to a federation registry what an A2A agent card is to an agent: a self-describing public JSON document that a registry serves about itself, signed with Ed25519, which any participant can fetch, verify, cache and display.

Terms used in this document:

  • Registry: a server that hosts agent identities and agent cards and takes part in a federation.
  • Trust domain: the SPIFFE trust domain a registry belongs to. Peers prove it to each other with mutual TLS.
  • Frame: a registry with its own trust domain, its own ledger and its own settlement currency.
  • Operator registry (cloud operator): a registry hosted under a frame. It shares the frame's trust domain and settles through the frame's ledger.
  • Operator: the person or organization that runs a registry. The operator block describes them, and the editable parts of a card are theirs to state.
  • Peer: another registry that this registry federates with.
  • Verifier: any party that checks a card's signature and digests.

Version 0.6 adds the compliance block and covers it by one signed digest (section 5). The set of signed paths can grow without a new version string, because a verifier reads the list of signed paths from the card it verifies (section 7); two such additions were made within version 0.6 (identity.did_namespace and the three federation.licence leaves).

2. Endpoints ​

A registry serves these paths at the root of its public origin, without a prefix. None of them requires authentication.

PathMethodContent
/.well-known/registry-card.jsonGETThe card (sections 3 to 7).
/.well-known/registry-jwks.jsonGETThe public keys that verify the card signature (section 7.6). This is the only location of those keys.
/.well-known/registry-card-schema.jsonGETThe JSON Schema for version 0.6 cards.
/api/v1/jurisdiction/authorizationsGET{"authorizations": [...], "count": n}: the same list of live regulatory authorizations the card publishes (section 5.3), unsigned.
/api/v1/jurisdiction/contracts/{cid}GETThe proof document for an attestation contract named by an authorization's contract_ref (section 5.6).

Caching of the card:

  • The response carries ETag: "<lifecycle.card_etag>" (the tag in double quotes) and Cache-Control: public, max-age=300, stale-while-revalidate=600.
  • A request whose If-None-Match equals the current tag (quoted or unquoted) receives 304 Not Modified with the same ETag and Cache-Control headers and no body.
  • The tag a registry sends with a 304 MUST be identical to the lifecycle.card_etag it would embed in a full response at the same moment (section 8).

The key set response is {"keys": [...]} with Cache-Control: public, max-age=300, stale-while-revalidate=600.

A registry MAY also serve a convenience envelope at GET /api/v1/public/registry-card, which returns its own card, or with ?peer=<name> its cached copy of a peer's card, together with its own verdicts about that peer. Readers MUST NOT treat the envelope as a substitute for verifying the card it carries.

Operators edit the editable blocks through an administrative interface that this specification does not define.

3. Document structure ​

A card is a JSON object with these top-level keys. The reference implementation emits them in the order shown; key order carries no meaning.

KeyTierSourceSigned
registry_card_versionMUSTconstant "0.6"yes
identityMUSTderivedseven leaves (section 4.1)
operatorSHOULDeditablelatitude, longitude
descriptionSHOULDeditableyes
tagsSHOULDeditableno
visualSHOULDeditablesovereign_variant
capabilitiesSHOULDderivedthe whole block
feesMUSTderivedseven leaves (section 4.6)
federationMUSTderivedsix leaves (section 4.7)
policySHOULDderivedno
economicMAYderivedno, and excluded from the entity tag
statsMAYderivedno, and excluded from the entity tag
endpointsSHOULDderivedno
linksSHOULDeditableno
commitmentsSHOULDeditableno
lifecycleMUSTderivedcard_etag
complianceMAYderiveddigest (section 5)
signatureMUST when a signing key existsderivednot applicable

"Derived" means the registry computes the value from its own state; the operator cannot set it. "Editable" means the operator states it.

A minimal conforming card carries the MUST keys only. A card MUST NOT contain keys that this specification does not define, with three exceptions whose shape is open: endpoints, federation.licence and federation.licence.operator.

4. Blocks ​

Types below use JSON terms. "Timestamp" means an ISO 8601 date-time string with a UTC offset. "URL" means an absolute http or https URL unless stated otherwise. A field shown as nullable MAY be null.

4.1 identity (MUST) ​

FieldTypeRequiredSignedMeaning
spiffe_idstring, starts with spiffe://, at most 512 charactersyesyesThe registry's SPIFFE ID, of the form spiffe://<trust_domain>/registry/<name>.
registry_namestring, 1 to 128 charactersyesyesThe registry's federation name.
display_namestring, 1 to 128 charactersyesnoHuman-readable name.
trust_domainstring, 3 to 255 charactersyesyesThe registry's SPIFFE trust domain.
did_namespacestring or null, at most 253 charactersnoyesThe issuer segment of the agent DIDs this registry mints (did:theprotocol:<did_namespace>:<uuid>). See below.
public_urlURL, at most 500 charactersyesyesThe registry's browser-reachable public base URL.
federation_base_urlURL or nullnonoThe public address peers dial for mutually authenticated federation calls.
founded_attimestampyesnoWhen the registry came into being: the operator's stated date, or else the creation time of the registry's first account.
tierenumyesyesThe registry's role, below.
software_versionstring or null, at most 64 charactersnoyesThe release tag of the software the registry runs.

tier values:

  • sovereign-mainframe: a frame that declares itself a central registry. It has its own trust domain and ledger and mints its own currency.
  • regional-mainframe: a frame registry that is not a central registry.
  • cloud-operator: an operator registry hosted under a frame.
  • sandbox: a test registry.
  • standalone: a registry configured to run without federation.

did_namespace rules:

  • A registry that mints agent DIDs with an issuer segment MUST publish that segment here, and it MUST equal the registry's trust_domain. A registry that mints DIDs without an issuer segment omits the key.
  • The value MUST match ^[a-z0-9]([a-z0-9.-]{0,61}[a-z0-9])?$ and MUST NOT be one of the reserved second segments of system DIDs: operator, cloud, org, agora-gateway, developer, api-tester, system, teg, treasury, gateway, fx, fee, fee_collector, admin, registry, frame, template.
  • A receiver refuses a card whose did_namespace is malformed or reserved (did_namespace_invalid), differs from the card's own trust_domain or from the trust domain the peer proved over mutual TLS (did_namespace_mismatch), or equals the receiver's own namespace (did_namespace_self). The last check does not apply to a frame receiving the card of one of its own operator registries, or to an operator registry receiving its own frame's card, because they share one trust domain by design; the receiver decides that from its own record of the peer, never from the card.

4.2 operator (SHOULD) ​

The operator's persona. All fields are editable. display_name (string, 1 to 128 characters) is required when the block is present.

FieldTypeMeaning
display_namestringOperator name.
biostring or null, at most 2000 charactersShort biography.
companystring or null, at most 256 charactersCompany.
urlURL or nullOperator web site.
github_urlstring or null, starts with https://github.com/Source profile.
twitter_handlestring or null, pattern ^@?[A-Za-z0-9_]{1,15}$Social handle.
timezonestring or nullTime zone name.
location_labelstring or nullHuman-readable location.
operator_sincetimestamp or nullSince when the operator runs the registry.
operator_avatar_urlstring or nullAvatar. MUST be a same-origin path beginning /static/; a registry MUST NOT publish an external image link here.
contact_emailstring or nullContact address. Opt-in: omitted unless the operator explicitly sets it. A role address is RECOMMENDED.
successor_contactstring or nullWho to contact if the operator is unreachable.
support_contactobject or null{email, matrix, signal}, each a string or null.
latitudenumber or null, -90 to 90SIGNED. Location of the registry for map placement.
longitudenumber or null, -180 to 180SIGNED.

4.3 description and tags (SHOULD) ​

description is a string of at most 2000 characters and is SIGNED. tags is an array of at most 20 strings of at most 48 characters each.

4.4 visual (SHOULD) ​

Display styling, editable by the operator: icon_url (a same-origin /static/ path, a URL, or null), orb_color and orb_glow_color (#rrggbb), orb_pulse_speed_hz (0.5 to 3.0), orb_size_px (integer, 10 to 24), halo_enabled (boolean), halo_thickness (0.5 to 2.5), theme (neural, sovereign or custom), sovereign_variant (string or null, SIGNED), flare_corner_variant, flare_encirclement_variant and flare_encirclement_overlay (strings or null).

The variant fields name display styles from a renderer's catalog. Visual styling is not identity: any registry may use any known style, and a receiver MUST NOT treat styling as evidence of identity. A receiver MAY drop a sovereign_variant it does not know and use the operator default styling instead. A receiver MAY check the three flare_ fields against its catalog of known styles and refuse a card that names an unknown one. flare_encirclement_overlay MUST NOT be set unless flare_encirclement_variant is set. A receiver refuses a card whose orb_color or orb_glow_color is not #rrggbb.

4.5 capabilities (SHOULD, signed as a whole block) ​

FieldTypeMeaning
federation_enabledbooleanThe registry federates.
fiat_onramp_enabledbooleanThe registry sells its currency for fiat money.
fiat_providersarray of stripe, mollieFiat payment providers in use.
minting_capabilitysovereign or disabledsovereign for a central registry that mints its own currency. Verifiers also accept the older values genesis_only and limited.
bridge_capabilitynone, frame_to_frame or multi_frameCross-frame bridging.
a2a_versions_supportedarray of stringsA2A protocol versions, for example ["1.0"].
registry_card_versions_servedarray of stringsA version 0.6 registry MUST include "0.6".
supports_payment_protocolarray of stringsPayment protocols; a2a-payment-v1 names the A2A payment extension (see a2a-payment-extension-v1.md).
zkp_phases_enabledarray of integersZero-knowledge attestation phases the registry has enabled (numbering defined by the implementation).

The block lists capabilities that matter to peers and buyers. It MUST NOT carry internal configuration toggles. Because the whole block is signed, every value in it is covered by the card signature.

4.6 fees (MUST) ​

Required keys: intra_registry, cross_registry, hosting, fee_collector_address, settlement_currency.

FieldTypeSignedMeaning
intra_registryobjectnoFee on transfers inside the registry: base_rate_percent, max_rate_percent (0 to 100), velocity_scaling_factor (at least 0), destination (string), customizable and locked_by_network (booleans).
cross_registry.base_rate_percentnumber, 0 to 100yesFee on cross-registry transfers.
cross_registry (other fields)nofee_recipient (for example receiver_registry), customizable, locked_by_network, and future_customization_in_bounded_params ([min, max] or null: the range the rate may move within if operators may customize it).
hosting.enabledbooleanyesWhether the registry charges a hosting fee on agent service fees.
hosting.rate_percent_of_agent_service_feenumber, 0 to 100yesThe hosting fee as a percentage of an agent's service fee.
hosting (other fields)nomin_avt (decimal string), applies_to_agent_dids (array of DIDs, or null for all agents), description.
fee_collector_addressstringnoThe ledger account that collects fees.
fee_waterfall_policystring or nullnoHow collected fees are recycled.
settlement_currencystring, pattern ^[A-Z]{2,8}$yesThe code of the currency this registry settles in (its frame's currency).
economic_disclosureobjectnospec_version, fee_floor_avt (decimal string), rounding (ROUND_HALF_EVEN, ROUND_HALF_UP or ROUND_DOWN), fees_emit_event_types (ledger event types that record fees).
fx.enabledbooleanyesWhether this registry hosts a cross-frame currency exchange.
fx (other fields)noSee below.
exchange.enabledbooleanyesWhether this registry hosts an AGORA market.
exchange.stock_card.digeststring or null, pattern ^sha256:[0-9a-f]{64}$yesDigest of the venue's catalog, see below.
exchange (other fields)noSee below.

fx (currency exchange). When enabled is false the block is {"enabled": false, "route_enabled": <boolean>}, plus route_gateway_registry_id when route_enabled is true (the registry routes exchanges through that gateway registry). When enabled is true it also carries currency, rate_mode (fixed or pool), fee_pct, fee_split (pool_retained_pct, fee_collector_pct), size_cap and reserve_floor (objects mapping a currency code to a number; a cap of 0 means uncapped), slippage_max, breaker_pct and note. The unsigned fx fields are informative and apply to exchanges this registry hosts as the destination.

exchange (the AGORA market). When enabled is false the block is {"enabled": false, "passport_enabled": <boolean>}. When true it also carries currency, endpoints (listings, order, passport_inbound: paths), fee_bps_default (integer basis points, 0 to 10000), note, and two pointers:

  • stock_card: url (/.well-known/stock-card.json), digest (SIGNED), count, updated_at, and optionally market_epoch and market_freshness. The digest covers the catalog only, not live market fields, so trades do not change it.
  • solvency_card: url (/.well-known/solvency-card.json), digest, as_of, epoch, count, freshness. UNSIGNED. epoch is a coarse five-minute bucket.

passport_enabled says whether agents homed on other registries may trade on this venue through their home registry. The stock card and solvency card documents are outside this specification.

4.7 federation (MUST) ​

Required keys: protocol_version, supported_auth_methods, policy_hash, is_genesis_registry, is_sovereign_frame, active_peer_count.

FieldTypeSignedMeaning
protocol_versionstringnoFederation protocol version, for example "1.0".
supported_auth_methodsarray of stringsnoFor example ["mtls", "jwt_svid", "federation_license"].
trust_domains_acceptedarray of strings, sortednoTrust domains of the registry's active peers.
spire_bundle_endpointURL or nullnoWhere the registry's SPIFFE trust bundle is published.
policy_hashstring or null, at most 80 charactersyessha256: followed by the lowercase hex SHA-256 of the canonical JSON (section 6) of the registry's resolved policy document: its stored policy merged onto the shipped defaults. The registry's policy compliance endpoint publishes the same hash without the prefix.
policy_versioninteger, at least 0noVersion of the registry's policy document. 0 means never amended.
policy_updated_attimestamp or nullnoWhen the policy document last changed.
policy_sourcefactory_default or amendednoWhether policy_hash describes the shipped defaults or an amended document. factory_default means never amended, not unknown.
is_genesis_registrybooleanyesTrue when the registry is a central registry with authority to mint its currency.
is_sovereign_framebooleanyesTrue when identity.tier is sovereign-mainframe.
uptime_started_attimestamp or nullnoSince when the registry has been up.
active_peer_countinteger, at least 0noNumber of active peers. Volatile, so excluded from the entity tag.
trusted_peer_spiffe_idsarray of strings, sortednoThe peer roster. Published only when the operator opts in; omitted by default.
license_id, license_statusstring or nullnoIdentifier and status of a legacy admission licence, if any. Not the signed network licence.
licenceobjectthree leavesThe signed network licence, below.

federation.licence is derived from the licence certificates the registry holds; the operator cannot set it. It is absent when the registry has no network configured, its licensing is off and it holds no licence, so such a card is identical to one produced by software that predates licensing. Its fields and the certificate format are defined in federation-licence-certificate-v1.md. In summary: state (the registry's own reading of its licence), hash (SHA-256 of the certificate), network, issuer, plan, expires_at, certificate (the compact JWS), chain (at most two certificates), checked_at and reason; a frame adds key (its licence-signing public key) and operator_policy; an operator registry adds operator, its own licence. The signed leaves are federation.licence.state, federation.licence.hash and federation.licence.operator.hash. A verifier MUST check the certificate itself and MUST NOT rely on the state a card states about itself.

4.8 policy (SHOULD) ​

The registry's declared policy posture and the network constants it is pinned to.

FieldTypeMeaning
locked_params_hashstring or nullsha256: followed by the hex SHA-256 of the canonical JSON of the network's locked parameters.
policy_sourcefactory_default or amendedAs in federation.policy_source.
policy_versioninteger, at least 0Equal to federation.policy_version.
declared_paramsobjectFor each bounded parameter: {value, source, allowed}. value is the value the registry applies (number, string or boolean). source is policy when the registry set it and default when it is the shipped default. allowed is {min, max} (numbers or null), the range the network permits.
bounded_paramsobjectThe same keys as declared_params, flattened to {key: value}, kept for readers of earlier card versions.
policy_modified_attimestamp or nullOPTIONAL.
drift_statuscompliant, drift_detected, quarantined or unknownPresent only when derived from a real compliance state; otherwise omitted.
consul_compliance_stateactive, stale, quarantined or unknownPresent only when truthfully derived; otherwise omitted.

The reference implementation declares these bounded parameters: transaction_fees.base_rate_percent, transaction_fees.max_rate_percent, transaction_fees.velocity_scaling_factor, staking_rewards.max_apy_percent, enforcement.reputation_bond_min_avt, enforcement.bond_maturity_min_age_days, governance.standard_quorum_percent, governance.standard_pass_percent. The parameter names contain dots but are single object keys.

A registry MUST publish the values it actually applies. It MUST NOT publish a placeholder, such as the lower bound of a permitted range, as if it were the applied value.

4.9 economic (MAY) ​

Supply facts about the registry's ledger. The block is unsigned and excluded from the entity tag. A registry MUST NOT fabricate values: it publishes a measured value or omits it.

FieldTypeMeaning
supply_invariant_statusOK, BREACH, FEDERATED or UNKNOWNThe supply invariant verdict. FEDERATED means the registry does not mint and its supply is issued and audited at its frame.
supply_invariant_deltastringThe invariant delta.
supply_last_audited_attimestamp or nullTime of the verdict.
tokens_issued, total_circulating, tokens_destroyed, tokens_fees_collecteddecimal string or nullThe supply terms (null on a FEDERATED block).
supply_sourceauditor or eventstoreWhether the numbers come from an external auditor or from the registry's own ledger read. Absent on FEDERATED blocks.
supply_notestring or nullExplanation, present on FEDERATED blocks.
external_auditor_endpointURL or nullThe external auditor that attests this registry's supply. A registry MUST NOT name an auditor that its operator has not configured.

The supply invariant and its attestation are specified in supply-audit-standard-v1.0.md.

4.10 stats (MAY) ​

Counts, unsigned and excluded from the entity tag: agent_count, native_agent_count, federated_agent_count, sovereign_agent_count, developer_count, active_peers (integers, at least 0) and computed_at (timestamp). A registry MUST publish only counts it actually computes.

4.11 endpoints (SHOULD) ​

A catalog of the registry's public paths. The shape is open. It MUST NOT list administrative paths. The reference implementation publishes openapi_spec, agent_discovery, agent_card_well_known (/.well-known/agent-card.json), topology, network_stats, federation_info, policy_compliance, health_check, registry_card (/.well-known/registry-card.json), registry_jwks (/.well-known/registry-jwks.json), spire_bundle, fee_config, external_auditor (URL or null) and public_status_page (URL or null).

Optional URLs stated by the operator, each a URL or null: whitepaper, docs, github_source, status_page, blog, rss, terms_of_service, privacy_policy, code_of_conduct, support_url, discord, matrix, incident_log.

4.13 commitments (SHOULD) ​

Operator-stated service levels and legal statements:

FieldType
data_residencystring or null, at most 32 characters
gdpr_compliantboolean
incident_disclosure_sla_hoursinteger, 1 to 720
uptime_sla_pctnumber, 0 to 100
support_response_hoursinteger, 1 to 720
deprecation_notice_daysinteger, 0 to 365
open_source_licensestring or null, at most 64 characters
breach_disclosure_policystring or null, at most 500 characters
key_rotation_max_daysinteger, 1 to 365
card_refresh_max_minutesinteger, 1 to 60
legally_binding_disclaimerstring or null, at most 1000 characters

open_source_license states the licence of the software the registry runs. When the operator leaves it empty, the reference implementation fills in the licence of its own code, AGPL-3.0-or-later; an operator's own value is kept.

4.14 lifecycle (MUST) ​

FieldTypeSignedMeaning
statusactive, degraded, quarantined or retiringnoRegistry status.
quarantine_reasonstring or nullnoWhy the registry is quarantined.
card_updated_attimestampnoTime of the operator's last edit; before any edit, identity.founded_at.
card_etagstring, at most 80 charactersyesThe entity tag, section 8.
served_attimestampnoWhen this copy was produced.
next_card_refreshtimestamp or nullnoWhen a reader should fetch again (the reference implementation uses five minutes after served_at).

5. The compliance block (MAY) ​

5.1 Absence rule ​

The compliance key MUST be absent when the registry has declared no jurisdiction profile and holds no live regulatory authorization. It MUST NOT be an empty object or null in that case: a registry that has declared nothing must not publish a compliance shape that implies it has. Receivers show no compliance information for a card without the key.

The legal shapes are:

Situationcompliance
No profile, no live authorizationsabsent
Profile declared, no live authorizations{jurisdiction_profile, digest}
No profile, live authorizations{regulatory_authorizations, digest}
Both{jurisdiction_profile, regulatory_authorizations, digest}

digest MUST be present whenever the block is present.

5.2 compliance.jurisdiction_profile ​

The operator's own declaration of which feature surfaces the law applicable to it permits. A registry MUST build this block from its current declaration (not from a stale cache), because the card is the public attestation of it.

FieldTypeMeaning
namestring, 1 to 120 charactersProfile name.
versioninteger, at least 1Profile version.
features_deniedarray of unique strings, sortedThe features the profile denies. Vocabulary: exchange (orders on the AGORA market and purchases from a launch allocation), fx (cross-frame currency exchange), guild (posting funded work tasks), forge (evaluation arena runs), fiat (the fiat purchase lane), cross_transfer (cross-registry transfers). An absent profile allows everything; a feature a declared profile does not name stays allowed.
declared_attimestamp or nullWhen the profile was declared.
profile_hashstring, pattern ^sha256:[0-9a-f]{64}$sha256: followed by the hex SHA-256 of the canonical JSON of the full profile document. The full document can carry private fields, so readers treat this value as an opaque commitment.
declared_by"operator"Constant.
regulated_actionsobjectOPTIONAL. Present only when the profile declares acts that require a state authorization.

regulated_actions maps an action to {authority, scope, mode}, with keys sorted. Private notes the operator keeps with a declaration MUST NOT be published.

  • Actions (closed vocabulary): exchange.listing (approving a listing), exchange.ipo (opening a launch allocation), fiat.lane (the fiat purchase lane), registry.operation (the deployment's own operating licence). registry.operation is displayed only and never gates anything, because gating a deployment's own operating licence would freeze the deployment that declares it. A registry MUST refuse to record an action outside this vocabulary.
  • authority: the state body that grants the authorization (string, 1 to 120 characters, or null).
  • scope: registry or per_subject. Defaults: exchange.listing and exchange.ipo default to per_subject; fiat.lane and registry.operation default to registry.
  • mode: shadow (default) or enforce. With shadow the registry records that an act lacked a covering authorization and lets it proceed. With enforce the registry refuses the act until a live authorization covers it. An entry's enforce applies even when the registry's global jurisdiction mode is shadow; a registry whose global jurisdiction mode is off gates nothing.

5.3 compliance.regulatory_authorizations ​

Every live authorization the registry has recorded, in its public shape. An authorization is live when its status is active, valid_from is at or before now, and valid_until is null or after now. Liveness is recomputed at every build, never stored. The list is published even when no profile is declared, because a recorded authorization is an attestation in its own right. It MUST be sorted by the tuple (action, subject_ref or the empty string, reference_id).

FieldTypeMeaning
actionone of the four actionsThe act authorized.
subject_refstring or null, at most 255 charactersThe subject for a per_subject authorization; null means registry-wide.
authoritystring, 1 to 200 charactersThe granting body, as attested by the operator.
jurisdictionstring, 1 to 120 charactersJurisdiction.
reference_idstring, 1 to 255 charactersThe authority's own reference. Public on purpose: a false reference is provable misrepresentation.
valid_fromtimestampStart of validity.
valid_untiltimestamp or nullEnd of validity; null means open-ended.
evidence_hashstring or null, pattern ^sha256:[0-9a-f]{64}$Hash of the evidence the operator holds.
contract_refstring or null, at most 255 charactersThe identifier of a countersigned attestation contract (section 5.6).
contract_digeststring, pattern ^sha256:[0-9a-f]{64}$Present only when earned (section 5.6); otherwise the key is omitted.

A registry MUST NOT publish internal fields of an authorization record (its record identifier, its status and liveness flag, the identity of whoever recorded it, evidence notes, the method by which the recorder proved presence, and its creation and revocation data). Revocation changes the record's status only; a renewal is a new record.

The registry does not verify a state's stamp. The operator attests the authorization, with the operator's liability behind it; the claim is public and signed.

5.4 compliance.digest ​

digest = "sha256:" + lowercase_hex(SHA-256(canonical_json(compliance without the "digest" key)))

The canonical JSON is defined in section 6 and applies recursively, so the nested objects of regulated_actions and each authorization are sorted too.

A verifier recomputes the digest from the card it received: take the compliance object, remove digest, serialize canonically, hash, and compare with digest. compliance.digest is a signed leaf (section 7), so a verifier that checks both the signature and the digest has verified the whole block. A verifier that skips the recomputation has only verified that nobody changed the digest, not that nobody changed the block.

Signing one scalar leaf whose value is itself a hash keeps the signature independent of how implementations re-serialize a nested block: the signature check and the digest check are separate, independently testable steps, and editing any byte of the block changes the recomputed digest. The same pattern covers the exchange catalog through fees.exchange.stock_card.digest.

When a card carries no compliance block, the signed leaf compliance.digest resolves to null, exactly like any other absent optional leaf. The digest alone therefore cannot distinguish a card that never had a block from one whose block was deleted; the signed entity tag does, because its basis includes the compliance state (section 8).

5.5 Worked example ​

Illustrative values:

json
"compliance": {
  "jurisdiction_profile": {
    "name": "example-provider-no-fiat",
    "version": 3,
    "features_denied": ["fiat"],
    "declared_at": "2026-09-01T12:00:00+00:00",
    "profile_hash": "sha256:<64 lowercase hex digits>",
    "declared_by": "operator",
    "regulated_actions": {
      "exchange.ipo": {"authority": "Example Markets Authority", "scope": "per_subject", "mode": "shadow"},
      "fiat.lane": {"authority": "Example Markets Authority", "scope": "registry", "mode": "enforce"}
    }
  },
  "regulatory_authorizations": [
    {
      "action": "fiat.lane",
      "subject_ref": null,
      "authority": "Example Markets Authority",
      "jurisdiction": "Example",
      "reference_id": "EX-2026/0001",
      "valid_from": "2026-09-01T12:00:00+00:00",
      "valid_until": "2027-09-01T12:00:00+00:00",
      "evidence_hash": "sha256:<64 lowercase hex digits>",
      "contract_ref": null
    }
  ],
  "digest": "sha256:<64 lowercase hex digits>"
}

5.6 Attestation contracts ​

An authorization MAY name, in contract_ref, a countersigned contract between the parties that attests it. The registry adds contract_digest to the published authorization only when that contract is attestation-grade: it exists on the registry, its status is agreed, and its total amount is zero. Otherwise the key is omitted, so the card never claims what the proof endpoint would refuse.

GET /api/v1/jurisdiction/contracts/{cid} is the proof endpoint:

  • 404 unless at least one live authorization references the contract.
  • 409 when a live authorization references a contract that is missing, not zero-value, or not agreed; the body names the problem.
  • 200 with {"contract": <subset>, "digest": "sha256:...", "status": "agreed", "authorization_references": [<reference_id>, ...]} otherwise.

The subset contains the contract's immutable fields only: id, title, contract_type, total_amount (as a string), client_did, provider_did, terms, created_at, agreed_at (timestamps). status is excluded so the digest is stable from the moment of agreement. digest and contract_digest are "sha256:" + lowercase_hex(SHA-256(canonical_json(subset))). The title, parties and terms of a referenced contract are public: countersigning an attestation publishes it.

6. Canonical JSON ​

Signatures, digests and the entity tag of this specification use one canonical JSON form. It is the UTF-8 text that Python's json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) produces:

  • Object keys are sorted by Unicode code point, at every level.
  • There is no whitespace between tokens: , separates members and elements, : separates a key from its value.
  • Strings use \" and \\; the control characters U+0008, U+0009, U+000A, U+000C and U+000D use \b, \t, \n, \f and \r; every other character below U+0020 and every character from U+007F upward is written as \u followed by four lowercase hex digits, with characters above U+FFFF written as a UTF-16 surrogate pair. / is not escaped.
  • true, false and null are written as such.
  • Numbers are written as Python writes them: an integer in plain decimal; a float in the shortest form that reads back to the same value, which keeps .0 on a float with no fractional part (0.0, 1.0) and uses Python's exponent form for very large or very small magnitudes (for example 1e-07, 1e+16).

A verifier in another language MUST reproduce this form byte for byte. In particular it MUST keep the distinction between an integer and a float with no fractional part exactly as the card writes the number (0 and 0.0 produce different bytes).

7. Signature ​

7.1 The signature block ​

json
"signature": {
  "alg": "EdDSA",
  "kid": "spiffe://registry.example.org/registry/example-registry#1",
  "jwks_url": "/.well-known/registry-jwks.json",
  "signed_canonical": ["registry_card_version", "identity.spiffe_id", "..."],
  "signed_at": "2026-09-30T12:00:00.000000+00:00",
  "expires_at": "2026-09-30T13:00:00.000000+00:00",
  "value": "<base64url Ed25519 signature>"
}
FieldMeaning
algEdDSA (Ed25519).
kid<identity.spiffe_id>#<rotation_seq>. Selects the verification key.
jwks_urlAlways /.well-known/registry-jwks.json.
signed_canonicalThe dotted paths this signature covers.
signed_atSigning time (timestamp).
expires_atsigned_at plus 3600 seconds.
valueThe base64url encoding, without padding, of the 64-byte Ed25519 signature.

signature MAY be null when the registry has no signing key available. A null signature is a legal wire shape but an unusable trust artifact.

7.2 Signed paths ​

A registry MUST declare in signed_canonical the list of paths it signs, and MUST sign exactly those paths. The reference implementation signs these 28 paths, in this order:

 1  registry_card_version
 2  identity.spiffe_id
 3  identity.registry_name
 4  identity.trust_domain
 5  identity.did_namespace
 6  identity.tier
 7  visual.sovereign_variant
 8  federation.is_genesis_registry
 9  federation.is_sovereign_frame
10  federation.policy_hash
11  fees.cross_registry.base_rate_percent
12  fees.hosting.enabled
13  fees.hosting.rate_percent_of_agent_service_fee
14  fees.settlement_currency
15  lifecycle.card_etag
16  identity.public_url
17  identity.software_version
18  operator.latitude
19  operator.longitude
20  capabilities
21  fees.exchange.enabled
22  fees.exchange.stock_card.digest
23  fees.fx.enabled
24  description
25  compliance.digest
26  federation.licence.state
27  federation.licence.hash
28  federation.licence.operator.hash

Every entry except capabilities is a single leaf; capabilities covers the whole block. Because the signed payload is an object keyed by path and is serialized with sorted keys (section 7.3), the order of the list does not change the bytes that are signed.

7.3 Signing ​

  1. For each path in signed_canonical, resolve its value in the card: split the path on . and descend through objects by key. If a key is missing, or a step reaches a value that is not an object, the value is null. A path that resolves to null or is absent is still part of the payload, with the value null.
  2. Build the payload object: each path string maps to its resolved value.
  3. Add "_signed_at" and "_expires_at" with the string values of signed_at and expires_at from the signature block. The freshness window thus lives inside the signed payload but not in the card body, so it never changes the entity tag, and moving expires_at after signing breaks verification.
  4. Serialize the payload as canonical JSON (section 6) and sign the resulting bytes with the registry's Ed25519 key.
  5. Encode the 64-byte signature as base64url without padding into value.

The signature is a raw Ed25519 signature over those bytes; there is no JWS header and no JWS signing input.

7.4 Verification ​

  1. Read the card's own signature.signed_canonical. A verifier MUST use this list and MUST NOT substitute a list of its own; this is what lets the signed set grow without breaking verifiers.
  2. Resolve every declared path as in section 7.3 step 1, including paths that resolve to null.
  3. Add _signed_at and _expires_at from the signature block, exactly as they appear there.
  4. Serialize canonically (section 6).
  5. Fetch /.well-known/registry-jwks.json from the registry and select the key whose kid equals signature.kid. Verify the Ed25519 signature over the serialized bytes with that key.
  6. Reject the signature if the current time is later than expires_at, even when the signature itself is valid.
  7. If the card carries a compliance block, recompute its digest (section 5.4) and reject the card if it differs from compliance.digest.
  8. If the card carries federation.licence with a certificate, check that hash equals the SHA-256 of the certificate, and evaluate the certificate itself as specified in federation-licence-certificate-v1.md.

7.5 Freshness ​

A signature is valid for one hour after signed_at. Registries re-sign on every full build, and peers fetch cards about every five minutes, which leaves a margin of about twelve fetches against clock skew and fetch latency while still rejecting a stale replayed card.

7.6 Keys and rotation ​

Each key in /.well-known/registry-jwks.json is an Ed25519 public key in JWK form: {"kty": "OKP", "crv": "Ed25519", "x": "<base64url public key>", "kid": "<spiffe_id>#<rotation_seq>", "alg": "EdDSA", "use": "sig"}.

The first key has rotation_seq 1; each rotation adds one. After a rotation the previous key stays in the key set for a grace period of seven days, so cards signed shortly before the rotation remain verifiable. The key set lists the current key and every key still inside its grace period.

8. Entity tag ​

lifecycle.card_etag is the card's entity tag. It is signed, and the HTTP ETag header carries the same value in double quotes.

The tag changes on operator edits and on changes to a small set of stable inputs, and on nothing else. Requirements:

  • The tag MUST change when the operator edits the editable card data.
  • The tag MUST change when a stable identity field (spiffe_id, registry_name, trust_domain, tier, display_name, public_url, founded_at, did_namespace), the policy hash or the fee commitments change.
  • Declaring or changing a jurisdiction profile, and recording or revoking an authorization, MUST change the tag. Because the tag is signed, this is what protects the compliance block against deletion (section 5.4).
  • A change of licence state or hash, of the network licence or of an operator registry's own licence, MUST change the tag, so a rotation, a revocation or a verdict change is never hidden behind a 304.
  • The tag MUST NOT change with volatile data: the economic and stats blocks, federation.active_peer_count, the peer roster, and timestamps such as served_at.
  • The tag a registry returns on the 304 path MUST equal the tag it embeds in a full build (section 2).

Consequently a reader that holds a copy with the current tag has current values for everything the tag covers; values outside it (for example identity.software_version) can be newer on the registry than in a cached copy.

Reference construction (informative): the tag is "sha256:" + lowercase_hex(SHA-256(canonical_json(basis))), where basis is an object with a format discriminator "v": "0.6", a builder revision number, the operator-editable card data, the stable identity subset (spiffe_id, registry_name, trust_domain, tier, display_name, public_url, founded_at, did_namespace; software_version is deliberately excluded), the policy hash, the fee commitments, fingerprints of the currency exchange (FX) and AGORA market settings together with the catalog digest, a coarse five-minute solvency epoch and a coarse market epoch (never the live digests, which would force every peer to re-fetch on every trade), and two tokens:

compliance token = profile_hash (or "") + "|ra:" + authorizations_digest (or "")
                   null when there is neither a profile nor a live authorization
authorizations_digest = "sha256:" + lowercase_hex(SHA-256(canonical_json(regulatory_authorizations array)))

licence token = state + "|" + hash + "|" + operator.state + "|" + operator.hash
                each part empty when absent

9. Receiving a card ​

A receiver fetches a peer's card over the federation channel, conditionally with If-None-Match.

  • A receiver SHOULD refuse a card body larger than 32 KiB, nested deeper than 8 levels, or containing any string longer than 2048 characters. A registry MUST therefore keep its card within these limits.
  • A receiver refuses a card without a string registry_card_version or without an identity object, with an orb_color or orb_glow_color that is not #rrggbb, or with an invalid did_namespace (section 4.1).
  • A receiver MAY drop a sovereign_variant it cannot render, and MAY refuse a card that names a flare_ variant outside its catalog (section 4.4).
  • A receiver verifies the signature (section 7.4) and records whether it verified. A receiver MAY keep its previous copy of a peer's card rather than replace it with one that is unsigned or fails verification.
  • A receiver evaluates a peer's network licence from the certificate the card carries, not from the card's own state (see federation-licence-certificate-v1.md).

10. Conformance requirements ​

A version 0.6 conforming registry:

  1. Serves the MUST blocks at /.well-known/registry-card.json with a valid Ed25519 signature verifiable against /.well-known/registry-jwks.json.
  2. Declares registry_card_version: "0.6" and includes "0.6" in capabilities.registry_card_versions_served.
  3. Declares its signed paths in signature.signed_canonical and signs exactly those paths with the freshness window folded in (section 7.3).
  4. Emits compliance only when a profile is declared or at least one live authorization exists, and always emits compliance.digest with the block.
  5. Computes compliance.digest exactly as in section 5.4, so any third party can recompute it from the served card.
  6. Publishes only the public fields of an authorization (section 5.3).
  7. Omits contract_digest on any authorization whose contract_ref does not resolve to an attestation-grade contract.
  8. Keeps the entity tag as specified in section 8, and keeps the 304 path and the embedded lifecycle.card_etag identical.
  9. Contains no field outside this specification, except inside endpoints, federation.licence and federation.licence.operator.
  10. Publishes identity.did_namespace equal to its trust domain whenever it mints DIDs with an issuer segment.
  11. Derives federation.licence from the certificates it holds and never from editable card data.

Appendix A. Abbreviated example ​

Illustrative values; most blocks are omitted.

json
{
  "registry_card_version": "0.6",
  "identity": {
    "spiffe_id": "spiffe://registry.example.org/registry/example-registry",
    "registry_name": "example-registry",
    "display_name": "Example Registry",
    "trust_domain": "registry.example.org",
    "public_url": "https://registry.example.org",
    "federation_base_url": "https://federation.registry.example.org",
    "founded_at": "2026-09-01T00:00:00+00:00",
    "tier": "sovereign-mainframe",
    "software_version": "<release tag>",
    "did_namespace": "registry.example.org"
  },
  "fees": {
    "intra_registry": {"base_rate_percent": 0.5, "max_rate_percent": 5.0, "velocity_scaling_factor": 1.0,
                       "destination": "treasury_recycle", "customizable": false, "locked_by_network": true},
    "cross_registry": {"base_rate_percent": 0.5, "fee_recipient": "receiver_registry",
                       "customizable": false, "locked_by_network": true},
    "hosting": {"enabled": false, "rate_percent_of_agent_service_fee": 0.0},
    "fee_collector_address": "teg:system:fee_collector",
    "settlement_currency": "AVT",
    "fx": {"enabled": false, "route_enabled": false},
    "exchange": {"enabled": false, "passport_enabled": false}
  },
  "federation": {
    "protocol_version": "1.0",
    "supported_auth_methods": ["mtls", "jwt_svid", "federation_license"],
    "policy_hash": "sha256:<64 lowercase hex digits>",
    "policy_version": 0,
    "policy_source": "factory_default",
    "is_genesis_registry": true,
    "is_sovereign_frame": true,
    "active_peer_count": 3
  },
  "lifecycle": {
    "status": "active",
    "quarantine_reason": null,
    "card_updated_at": "2026-09-20T09:30:00+00:00",
    "card_etag": "sha256:<64 lowercase hex digits>",
    "served_at": "2026-09-30T12:00:00+00:00",
    "next_card_refresh": "2026-09-30T12:05:00+00:00"
  },
  "signature": {
    "alg": "EdDSA",
    "kid": "spiffe://registry.example.org/registry/example-registry#1",
    "jwks_url": "/.well-known/registry-jwks.json",
    "signed_canonical": ["registry_card_version", "identity.spiffe_id", "identity.registry_name"],
    "signed_at": "2026-09-30T12:00:00+00:00",
    "expires_at": "2026-09-30T13:00:00+00:00",
    "value": "<base64url Ed25519 signature>"
  }
}

The signed_canonical list is shortened here; a real card lists every signed path (section 7.2).

Server components AGPL-3.0-or-later · SDKs and the auditor Apache-2.0 · this documentation CC BY 4.0. If a doc and the running stack disagree, trust the stack. Legal notice (Impressum) · Privacy · Terms