Canonical encoding contract

Draft protocolSource at 169cbd2 ↗Source SHA-256 d103d1c…728
Status
Reviewed engineering design; bytes not frozen; executable conformance not run
Specification
Revision 14, 2026-09-22
Makechain Commonware pin
v2026.9.0, commit d476a2361ce6840d2b9d0aa6fb30a924429046d4
Tempo source pin
commit 25b216a661c2cac72807174583371b582b75c286

This document turns the semantic contracts in the specification and its state-machine, client API, and settlement companions into the selected wire contract. Tables marked Proposed identify engineering selections that have not yet passed executable conformance; they are not pending product decisions. Tables marked Fixed restate an accepted specification or pinned-source rule. Section 15 lists the remaining qualification inputs that prevent a byte-complete claim.

This document is the normative wire contract. Canonical Rust schema and release types are its executable authority. The format manifest records the selected revision, tags, domains, dependency pins, and artifact digests; it is not a separate schema language and does not generate Rust. Transport docs, bindings, and metadata are generated from the Rust registry and checked against reviewed vectors. Source-language declaration order, RPC JSON, protobuf field numbers, and a dependency's working tree never define canonical bytes.

1. Primitive registry

Unless a row says otherwise, integers use fixed-width unsigned big-endian bytes. Arithmetic rejects overflow and the semantic maximum where the specification reserves it.

Type Bytes Encoding and use Status
U8, U16, U32, U64, U128 1, 2, 4, 8, 16 Unsigned big-endian Proposed
Amount256 32 Unsigned big-endian; credits, fees, deposits, prices, max_charge, and ledger values Fixed by specification §16.3
Bool 1 00 false, 01 true; every other byte invalid Fixed by pin
Option<T> variable 00; or 01 || T; every other tag invalid Fixed by pin
Digest32 32 Full BLAKE3, Keccak256, or registered external digest according to the containing field; Keccak256 exactly on the proof path of specification section 6.2 Fixed
AccountId 20 Raw address bytes Fixed
Height, Epoch, View, Sequence, Revision, Generation, Counter, TimestampMs 8 U64; reject u64::MAX where revision 4 requires a reset/end Proposed
CommandIndex, CommitmentIndex, SameCommandIndex 4 U32 Proposed
NativeMmrLocation variable Exact commonware_storage::merkle::Location<mmr::Family> codec: shortest-form UInt<u64> with the MMR range check Fixed by pin
RulesetId 8 U64; genesis chooses the first ID, and every non-parameter continuing release increments it by one Fixed by specification §18.3
SchemaId, ProfileId, PriceScheduleId, WorkTableId, LayoutId, CodecId, SchemeId, ReleaseDigest, KeyId, ProjectId 32 Full Digest32; no truncation Proposed
EnvelopeVersion, SchemaRevision 2 U16 Proposed
enum tag 1 Manifest-owned U8; unassigned and retired values are invalid Proposed
fixed bytes stated size Bytes without a length prefix Fixed by pin
BoundedBytes<B> variable shortest-form Commonware usize count followed by bytes; count must be 0..=B Proposed
NonEmptyBytes<B> variable same framing; count must be 1..=B Proposed
BoundedUtf8<B> variable BoundedBytes<B> plus strict UTF-8 and field-specific character rules Proposed
BoundedVec<T,C,B> variable shortest-form Commonware usize item count followed by items; at most C items and B aggregate encoded bytes Proposed
SortedVec<T,C,B> variable BoundedVec; canonical encodings strictly increase and contain no duplicate Proposed

Amount256 is a 32-byte scalar, not a Commonware usize and not a varint. Implementations use checked 256-bit arithmetic for addition and subtraction. Multiplication either widens before the range check or rejects an overflowing intermediate. The settlement contract's slot-zero uint256 cumulative therefore maps without narrowing to AccountLedger.credited.

At the pin, commonware-codec supplies the selected mechanics in codec/src/types/primitives.rs, codec/src/types/vec.rs, and codec/src/varint.rs. The collection count is the pinned usize codec, which writes a shortest-form unsigned u32 varint. It is only collection framing. No semantic protocol integer uses it. Decoders apply both C and B before allocation and consume the complete enclosing slice.

2. Tag registry

Tags are explicit values in this contract and the canonical Rust registry. Adding a variant consumes a new value. Removing one reserves its old value forever. Values 0x80..=0xff are reserved for a future format revision and are invalid in revision 1.

2.1 Command and envelope tags

Value CommandPayload
0x00 INIT_ACCOUNT
0x01 ROOT_CONFIGURE
0x02 KEY_AUTHORIZE
0x03 KEY_REVOKE
0x04 PROJECT_CREATE
0x05 PROJECT_SET_METADATA
0x06 PROJECT_SET_STATUS
0x07 ROLE_SET
0x08 TRANSFER_OFFER
0x09 TRANSFER_ACCEPT
0x0a PUSH
0x0b RECLAIM
0x0c USERNAME_CLAIM
0x0d USERNAME_RELEASE
0x0e IMPORT_CREDITS
0x0f SCHEDULE_RELEASE
0x10 VALIDATOR_SET_SCHEDULE
Registry Allocations
IntentAuthority 00 Account, 01 PermissionlessSystem, 02 GovernanceSystem
EnvelopeAuthorization 00 Genesis, 01 Root, 02 Delegated, 03 None, 04 Governance
UnsignedEvidence 00 None, 01 PushWitnesses, 02 TempoCredit, 03 TempoRefresh
ConsensusProfile 00 FixedEpochZeroV1, 01 DynamicReshareV1
KeyFamily 00 Secp256k1, 01 Ed25519Sshsig, 02 P256Direct, 03 WebAuthnP256
GenesisAuthorization 00 SingleKey, 01 Configured
RootAuthorizationNode 00 SingleKey, 01 Configured
OwnerApproval 00 Key, 01 Account
VerificationPolicy 00 None, 01 WebAuthn
Capability 00 Social, 01 Write, 02 Project, 03 Account
ProjectStatus 00 Active, 01 Frozen
ProjectRoleKind 00 Writer, 01 Maintainer
Expected<T> 00 Absent, 01 Present(T)
KeyAuthorize 00 Authorize, 01 Renew
KeyRevoke 00 One, 01 All
RoleSet 00 Grant, 01 Revoke
TransferOfferAction 00 Set, 01 Clear
RefOp 00 Create, 01 Set, 02 Delete
MembershipWitness 00 SameCommand, 01 Location, 02 Proof
ReclaimTarget 00 DelegatedKey, 01 TransferOffer, 02 ProjectRole, 03 UsernameLease
UsernameClaim 00 Claim, 01 Extend, 02 Restore, 03 Rename
ImportCredits 00 Credit, 01 Refresh
ScheduleRelease 00 Activate, 01 Defer, 02 Cancel, 03 EmergencyActivate
ValidatorSetSchedule 00 Append, 01 Cancel
ReleaseClass 00 Parameter, 01 FormatPreserving, 02 VersionedState, 03 Reset
ReleaseScheduleEntry 00 Activate, 01 Defer, 02 Cancel, 03 EmergencyActivate
ActivationInstaller 00 WriteSettlementAnchor, 01 RotateGovernanceSet, 02 ReserveValidatorKey, 03 QualifiedHandler
ValidatorScheduleRecord 00 Schedule, 01 Cancel, 02 KeyReservation, 03 ScheduleState
ProfileEpochInfo 00 Fixed, 01 Dynamic
FinalityEvidence 00 Direct, 01 DescendantAncestry
StateFamilyTag 00 AccountLedger, 01 AccountRoot, 02 DelegatedKey, 03 Project, 04 ProjectMetadata, 05 SlugClaim, 06 ProjectRole, 07 TransferOffer, 08 Ref, 09 UsernameLease, 0a AccountUsername, 0b SettlementAnchor, 0c ProtocolReleaseEntry, 0d ProtocolScheduleState, 0e ValidatorScheduleEntry
HashAlgorithmTag 00 Blake3Flat, 01 MkitBmtV1, 02 GitSha1, 03 GitSha256
CanonicalDecodingTag 00 MkitObjectV1, 01 GitLooseObjectV1
ReleaseRecordKind 00 Descriptor, 01 ScheduleEntry, 02 ActivationMarker
ValidatorEntryKind 00 Schedule, 01 Cancel, 02 KeyReservation, 03 ScheduleState
PublicKeyV1 00 Secp256k1, 01 Ed25519Sshsig, 02 P256Direct, 03 WebAuthnP256
SignatureV1 00 Secp256k1, 01 Ed25519Sshsig, 02 P256Direct, 03 WebAuthnP256
ProtocolReleaseRecordV1 00 Descriptor, 01 ScheduleEntry, 02 ActivationMarker
ProofPayloadV1 00 CurrentState, 01 Membership, 02 Command, 03 Finality, 04 ReleaseLineage, 05 PublicationReceipt
DkgRevealModeV1 01 ContributorAware; 00 is retired and invalid
SettlementConfigurationV1 00 DisabledDiagnostic, 01 Enabled

3. Common wrappers

3.1 Command intent and complete envelope

Field order is the order shown.

CommandIntentV1 {
    chain_id: Digest32,
    envelope_version: U16 = 1,
    ruleset_id: RulesetId,
    price_schedule_id: PriceScheduleId,
    authority: IntentAuthority,
    payload: CommandPayload,
}

IntentAuthority::Account {
    actor: AccountId,
    key_id: KeyId,
    authorization_generation: Generation,
    sequence: Sequence,
    max_charge: Amount256,
    valid_before_timestamp_ms: Option<TimestampMs>,
}
IntentAuthority::PermissionlessSystem {}
IntentAuthority::GovernanceSystem { governance_revision: Revision, governance_nonce: U64 }

SignedCommandV1 {
    intent: CommandIntentV1,
    authorization: EnvelopeAuthorization,
    evidence: UnsignedEvidence,
}

EnvelopeAuthorization::Genesis { witness: GenesisAuthorizationV1 }
EnvelopeAuthorization::Root { witness: RootAuthorizationNodeV1 }
EnvelopeAuthorization::Delegated { witness: DelegatedAuthorizationV1 }
EnvelopeAuthorization::None {}
EnvelopeAuthorization::Governance { witness: GovernanceAuthorizationV1 }

UnsignedEvidence::None {}
UnsignedEvidence::PushWitnesses { evidence: PushEvidenceV1 }
UnsignedEvidence::TempoCredit { evidence: TempoCreditEvidenceV1 }
UnsignedEvidence::TempoRefresh { evidence: TempoRefreshEvidenceV1 }

This union keeps account replay and charging fields out of permissionless and governance commands. It does not use zero-address or zero-sequence sentinels. The command tag is the first byte of payload; it is not duplicated beside the payload.

The allowed cross-product is closed:

Command Authority Authorization Evidence
INIT_ACCOUNT Account Genesis None
account commands other than INIT_ACCOUNT Account Root or Delegated, subject to semantic capability PushWitnesses only for PUSH, otherwise None
IMPORT_CREDITS.Credit PermissionlessSystem None TempoCredit
IMPORT_CREDITS.Refresh PermissionlessSystem None TempoRefresh
both governance commands GovernanceSystem Governance None

Every other combination is invalid before cryptography. intent_digest covers the complete CommandIntentV1. command_id covers the complete SignedCommandV1, including replaceable PUSH and Tempo evidence.

For root or genesis authorization, key_id is the account's permanent root slot: BLAKE3("makechain.root-slot" || actor). Its authorization generation is the exact current AccountRoot.version, including zero for INIT_ACCOUNT; a root command remains bound to the version read before execution even when ROOT_CONFIGURE advances it. sequence advances the permanent AccountRoot.root_sequence mark and is never reset by reconfiguration.

3.2 Key and signature wrappers

PublicKeyV1::Secp256k1 { compressed_sec1: FixedBytes33 }
PublicKeyV1::Ed25519Sshsig { key: FixedBytes32 }
PublicKeyV1::P256Direct { compressed_sec1: FixedBytes33 }
PublicKeyV1::WebAuthnP256 {
    compressed_sec1: FixedBytes33,
    credential_id: NonEmptyBytes<max_webauthn_credential_id_bytes>,
}
AccountScopedKeyV1 { account: AccountId, public_key: PublicKeyV1 }
KeyIdV1 = BLAKE3(registered key-id domain || canonical(AccountScopedKeyV1))

SignatureV1::Secp256k1 { r: FixedBytes32, s: FixedBytes32, recovery_id: U8 }
SignatureV1::Ed25519Sshsig { signature: FixedBytes64 }
SignatureV1::P256Direct { r: FixedBytes32, s: FixedBytes32 }
SignatureV1::WebAuthnP256 {
    authenticator_data: FixedBytes37,
    client_data_json: BoundedBytes<max_webauthn_client_data_json_bytes>,
    r: FixedBytes32,
    s: FixedBytes32,
}

VerificationPolicyV1::None {}
VerificationPolicyV1::WebAuthn {
    rp_id: NonEmptyBytes<max_webauthn_rp_id_bytes>,
    origins: SortedVec<BoundedUtf8<max_webauthn_origin_bytes>,max_webauthn_origins,max_webauthn_origins_bytes>,
}

VerificationPolicyV1::None is required for secp256k1, SSHSIG, and P256 direct. The WebAuthn family requires the WebAuthn policy. SEC1 points must be compressed, canonical, on-curve, and non-identity. ECDSA signatures require nonzero scalars and low S. The recovery ID is 0 or 1.

GenesisAuthorization::SingleKey {
    compressed_secp256k1_key: FixedBytes33,
    signature: SignatureV1::Secp256k1,
}
GenesisAuthorization::Configured {
    genesis_configuration: OwnerConfigurationV1,
    node: RootAuthorizationNodeV1::Configured,
}

DelegatedAuthorizationV1 { signature: SignatureV1 }

RootAuthorizationNodeV1::SingleKey {
    account: AccountId,
    compressed_secp256k1_key: FixedBytes33,
    signature: SignatureV1::Secp256k1,
}
RootAuthorizationNodeV1::Configured {
    account: AccountId,
    configuration: OwnerConfigurationV1,
    approvals: BoundedVec<OwnerApprovalV1, max_root_approvals, max_root_approvals_bytes>,
}
OwnerApprovalV1::Key { owner_index: U8, public_key: PublicKeyV1, signature: SignatureV1 }
OwnerApprovalV1::Account   // tag 0x01 stays reserved; invalid under every launch ruleset

GovernanceAuthorizationV1 {
    governance_revision: Revision,
    approvals: SortedVec<GovernanceApprovalV1, max_governance_keys, max_governance_witness_bytes>,
}
GovernanceApprovalV1 { key_index: U16, signature: FixedBytes64 }

SingleKey is valid only while the addressed account is at version 0 with an unset commitment; its key must derive the account's Ethereum address. The first reconfiguration permanently removes this branch. A configured genesis account carries the configuration needed to recompute its CREATE2 address, and is valid only once the chainspec multisig_recovery_factory value is set. P256 and WebAuthn can be owners inside that configuration; this revision does not define them as standalone single-key account identities.

Root approvals increase by owner_index, contain no duplicate, reach the threshold on the last approval, and contain no later approval. Every approval is a key approval: the node has one level, and an owner-account approval (OwnerApprovalV1::Account) is invalid. Governance approvals increase by key index into the current governance set and contain no duplicate. Ordinary operations contain the shortest prefix of supplied approvals whose count reaches the current threshold; no approval follows the threshold-crossing signature. Emergency activation contains every current key exactly once. A signature verifies raw Ed25519 over governance_digest = BLAKE3("makechain.governance" || intent_digest). The intent already binds the governance revision and nonce. This proposed domain is distinct from account and consensus signing.

The revision-1 SSHSIG profile stores only the raw Ed25519 signature. The verifier reconstructs the OpenSSH PROTOCOL.sshsig signed data with magic SSHSIG, namespace makechain.command:<lowercase chain_id hex>, empty reserved bytes, hash algorithm sha512, and message hash SHA-512(intent_digest). The public-key algorithm is exactly ssh-ed25519; security-key and certificate key types are invalid.

The revision-1 WebAuthn profile accepts a 37-byte assertion authenticator data value: 32-byte rpIdHash, one flags byte, and four-byte big-endian counter. UP and UV must both be set; AT and ED must both be clear; BE and BS must be a valid WebAuthn pair; the counter is ignored. The client data must be one UTF-8 JSON object with exactly type, challenge, and origin, plus optional crossOrigin; duplicate or unknown keys are invalid. type is webauthn.get, crossOrigin is absent or false, challenge is unpadded base64url that decodes to the required 32-byte digest, and origin exactly matches an authorized origin. Verification hashes the exact received JSON bytes and never reserializes them.

The proposed EIP-712 domain is EIP712Domain(string name,string version,uint256 chainId,bytes32 salt) with name Makechain, version 1, Tempo host chainId, and Makechain chain_id as salt. Each account command has its own primary type. A top owner signs Makechain<Command>(bytes20 account,uint64 configVersion,bytes32 intentDigest,<Command>Action action). There is no nested owner type. The command-specific action exposes these common fields in order: bytes32 chainId, uint16 envelopeVersion, uint64 rulesetId, bytes32 priceScheduleId, address actor, bytes32 keyId, uint64 authorizationGeneration, uint64 sequence, uint256 maxCharge, bool validBeforeTimestampMsPresent, uint64 validBeforeTimestampMs, and uint8 commandTag. The selected command's action then appends its section 4 payload fields in table order, beginning with uint8 variantTag for a tagged subvariant. It never replaces those fields with a payload or action hash. AccountId maps to EIP-712 address; 32-byte identifiers map to bytes32; fixed integers and amount map to the same-width uintN; enums map to uint8; text maps to string; other byte strings map to bytes; structs and vectors map to named structs and typed arrays recursively. ProjectAuthorityContextV1 expands in its stated field order. Optional fields use an explicit bool <field>Present followed by the field's zero value when absent, and the verifier requires the zero value in that case. Tagged unions expose the variant tag plus every field of the selected variant. bytes and string fields use EIP-712's standard hash; arrays use the standard concatenated element-struct-hash rule. The canonical Rust registry generates the literal primary/action type strings; vectors publish them for every command and variant. The verifier derives all typed fields from canonical decoded intent bytes, never from wallet-supplied typed JSON.

3.3 Reusable domain values

CommitmentV1 { scheme_id: SchemeId, native_id: BoundedBytes<max_native_id_bytes> }
OwnerEntryV1 { owner_address: AccountId, weight: U8 }
OwnerConfigurationV1 {
    salt: FixedBytes32,
    version: U64,
    threshold: U8,
    owners: BoundedVec<OwnerEntryV1, 48, max_owner_configuration_bytes>,
}
ValidityWindowV1 { starts_at_ms: TimestampMs, ends_at_ms: TimestampMs }
DepositV1 { payer: AccountId, paid: Amount256 }
IncarnationV1 { owner: AccountId, allocated_counter: Counter }

Owners increase by raw 20-byte address; equal addresses are duplicates even when weights differ. The configuration never stores a key family or public key. A key approval supplies its family/key and proves that its derived address equals the indexed owner address. An owner is always a key address; a configured account is invalid as an owner, and Ed25519 is invalid as an owner. Every weight is nonzero, weights sum to at most 255, and the threshold is from 1 to 8 and does not exceed the total weight. A version-zero configuration must not list its own derived address; a positive-version configuration may list the account's original key address.

Secp256k1 uses the standard Ethereum address of the uncompressed public key. P256 direct and WebAuthn decompress the canonical SEC1 key to 32-byte X and 32-byte Y and derive keccak256(X || Y)[12..32], with no SEC1 prefix. This P256 rule is pinned to Tempo commit 25b216a661c2cac72807174583371b582b75c286, crates/primitives/src/transaction/tt_signature.rs:805-814; owner recovery at lines 280-332 uses the same helper for both families.

The adopted account profile is the Tempo TIP-1107 cluster at commit e7ba69eb35349d49633e3637f751ce8a3558ebb9 (the text of the open upstream proposal; parent TIP-1107, file SHA-256 ff829cf882848c6722aff3567e0cfdbac033829cacfabc8e6860f222313686b9). The adopted children and their file SHA-256 values are:

TIP Adopted for File SHA-256
TIP-1114 configuration schema, owner and threshold bounds, approval order and exact-stop rule e1e72eca900e578c2bc83b2990f51c88596657bfa6adc2451093c8e9045c6423
TIP-1110 address derivation (account_salt, CREATE2) e19e6f369903cc429e0dc965a7e1d3b89d6dc2b37481d94f0cce035c769c1513
TIP-1108 configuration commitment preimage 9c05d32afb021251d01e9d51dd711f36857ec41e6876d4c4b01b5a6251176bcc
TIP-1113 in-place migration salt and version 1 bc9a9bf194c508569e6dfc0aeebd7d67b436867483bc1916c74d4bf39120689c
TIP-1112 permanent root-key disablement after the first configuration abe642539fd8444be832075dbb51369f118f3fd87e33bac1bad016be3a135639

TIP-1109 (configuration precompile, SHA-256 455186f6df8f2f0151ea55914305dc3ca0f08f439eeac75fbad7aaaa82a9f619) is read for its error conditions only, and TIP-1111 (access keys) is not adopted. The exact formulas are:

account_salt = keccak256(
    "tempo:multisig:account" || salt32 || threshold_u8 || owner_count_u8 ||
    each(owner_address20 || weight_u8)
)
config_commitment = keccak256(
    "tempo:multisig:config" || salt32 || version_u64_be || threshold_u8 || owner_count_u8 ||
    each(owner_address20 || weight_u8)
)

account = keccak256(0xff || factory20 || account_salt || wallet_init_code_hash)[12..32]. The wallet init-code hash is 0x583cc63a2e37f645b43eac911b1a6d6de08b83abdc308c61364edda8cfc3bd37. The factory is the chainspec value multisig_recovery_factory: TIP-1110 marks MULTISIG_RECOVERY_FACTORY as unpublished until Tempo's release activates and supersedes the earlier public factory address, so the value is unset at launch, the configured genesis form is invalid while it is unset, and a release sets it once as a governed set-once parameter. A derived address equal to the zero address is invalid. The codec's vector count must equal the u8 count used by the Keccak formula. Configuration updates preserve salt. Under TIP-1113 the first single-key upgrade installs configuration version 1 with salt = keccak256("tempo:multisig:upgrade" || account_address20); later updates preserve that salt. A slug and a username are valid only in canonical form: ASCII lowercase letters a to z, digits 0 to 9, and the hyphen, neither starting nor ending with a hyphen and never containing two consecutive hyphens, within max_slug_bytes and the username length bounds of section 13. No normalization is performed: a string outside canonical form is invalid rather than normalized, so the stored value is the submitted bytes. This is the field-specific character rule BoundedUtf8 names for those two fields.

This commit pins Makechain's adopted account semantics. The upstream proposal is an open draft and does not establish a live Tempo feature; a later change to it reaches Makechain only through a release that re-pins it.

4. Command payload registry

The payload tag precedes the fields below. Variant tags precede variant fields, and a variant carries its tag byte at every position it appears, including inside another variant's fields. Expected<T> uses the tags in section 2. Every project-authorized payload starts with its signed authority context so execution can reject stale control or grants without interpreting later fields:

ProjectAuthorityContextV1 {
    project_id: ProjectId,
    control_generation: Generation,
    role_grant_generation: Option<Generation>,
}
Command / variant Fields in canonical order after tags Status
INIT_ACCOUNT no payload fields Proposed
ROOT_CONFIGURE expected_root_version: U64, new_configuration: OwnerConfigurationV1 Proposed
KEY_AUTHORIZE.Authorize expected_keychain_revision: Revision, public_key: PublicKeyV1, capability: Capability, project_scope: Option<ProjectId>, validity_window: Option<ValidityWindowV1>, verification_policy: VerificationPolicyV1, expected_predecessor: Expected<Generation> Proposed
KEY_AUTHORIZE.Renew expected_keychain_revision: Revision, key_id: KeyId, expected_generation: Generation Proposed
KEY_REVOKE.One expected_keychain_revision: Revision, key_id: KeyId, expected_generation: Generation Proposed
KEY_REVOKE.All expected_keychain_revision: Revision, expected_delegation_epoch: Epoch Proposed
PROJECT_CREATE client_salt: FixedBytes32, scheme_id: SchemeId, slug: BoundedUtf8<max_slug_bytes>, description: BoundedUtf8<max_project_description_bytes>, origin: Option<CommitmentV1> Proposed
PROJECT_SET_METADATA context: ProjectAuthorityContextV1, slug: BoundedUtf8<max_slug_bytes>, description: BoundedUtf8<max_project_description_bytes>, expected_project_mutation_counter: Option<Counter> Proposed
PROJECT_SET_STATUS context: ProjectAuthorityContextV1, expected_lifecycle_revision: Revision, status: ProjectStatus Proposed
ROLE_SET.Grant context: ProjectAuthorityContextV1, grantee: AccountId, role: ProjectRoleKind, expected_predecessor: Expected<Generation>, expected_project_mutation_counter: Option<Counter> Proposed
ROLE_SET.Revoke context: ProjectAuthorityContextV1, grantee: AccountId, expected_generation: Generation Proposed
TRANSFER_OFFER.Set context: ProjectAuthorityContextV1, destination: AccountId, expected_offer: Expected<Revision>, expected_project_mutation_counter: Option<Counter> Proposed
TRANSFER_OFFER.Clear context: ProjectAuthorityContextV1, expected_offer_revision: Revision Proposed
TRANSFER_ACCEPT project_id: ProjectId, expected_control_generation: Generation, expected_offer_revision: Revision, destination_slug: BoundedUtf8<max_slug_bytes>, expected_project_mutation_counter: Counter Proposed
PUSH context: ProjectAuthorityContextV1, expected_project_mutation_counter: Option<Counter>, commitments: BoundedVec<CommitmentV1,max_commitments_per_push,max_commitments_bytes>, ref_ops: BoundedVec<RefOpV1,max_ref_ops_per_push,max_ref_ops_bytes> Proposed
RECLAIM.DelegatedKey account: AccountId, key_id: KeyId, expected_generation: Generation Proposed
RECLAIM.TransferOffer project_id: ProjectId, expected_revision: Revision Proposed
RECLAIM.ProjectRole project_id: ProjectId, grantee: AccountId, expected_generation: Generation Proposed
RECLAIM.UsernameLease name: BoundedUtf8<max_username_bytes>, expected_incarnation: IncarnationV1, expected_revision: Revision Proposed
USERNAME_CLAIM.Claim name: BoundedUtf8<max_username_bytes>, years: U16, expected_allocation_counter: Counter, expected_lease: Expected<UsernameLeaseGuardV1> Proposed
USERNAME_CLAIM.Extend name: BoundedUtf8<max_username_bytes>, expected_incarnation: IncarnationV1, expected_revision: Revision, years: U16 Proposed
USERNAME_CLAIM.Restore name: BoundedUtf8<max_username_bytes>, expected_incarnation: IncarnationV1, expected_revision: Revision, years: U16 Proposed
USERNAME_CLAIM.Rename source: UsernameLeaseGuardV1, destination_name: BoundedUtf8<max_username_bytes>, destination_years: U16, expected_allocation_counter: Counter, expected_destination: Expected<UsernameLeaseGuardV1> Proposed
USERNAME_RELEASE name: BoundedUtf8<max_username_bytes>, expected_incarnation: IncarnationV1, expected_revision: Revision Proposed
IMPORT_CREDITS.Credit beneficiary: AccountId, settlement_profile_id: ProfileId Proposed
IMPORT_CREDITS.Refresh settlement_profile_id: ProfileId Proposed
SCHEDULE_RELEASE.Activate descriptor: ReleaseDescriptorV1 Proposed
SCHEDULE_RELEASE.Defer pending_release_digest: ReleaseDigest, new_activation_height: Height Proposed
SCHEDULE_RELEASE.Cancel pending_release_digest: ReleaseDigest Proposed
SCHEDULE_RELEASE.EmergencyActivate descriptor: ReleaseDescriptorV1 Proposed
VALIDATOR_SET_SCHEDULE.Append target_epoch: Epoch, expected_schedule_revision: Revision, participants: SortedVec<ValidatorParticipantV1,64,max_validator_participants_bytes>, directory: NativeValidatorDirectory, proofs_of_possession: SortedVec<ValidatorPopV1,2,max_validator_pop_bytes> Proposed
VALIDATOR_SET_SCHEDULE.Cancel entry_id: Digest32, expected_schedule_revision: Revision Proposed

PROJECT_SET_METADATA.expected_project_mutation_counter is Some(current_counter) exactly when the requested slug differs from the current slug and execution will create a new SlugClaim; it is None exactly when the slug is unchanged. TRANSFER_ACCEPT always creates the destination claim and therefore always carries the exact current project mutation counter. These are closed shape rules, not caller-selected optional guards. ROLE_SET.Grant.expected_project_mutation_counter and TRANSFER_OFFER.Set.expected_project_mutation_counter are Some(current_counter) exactly when their target key is absent and the command allocates a new child; they are None for a live update. Replacement of a resident expired record instead binds its exact generation or revision through expected_predecessor or expected_offer and does not claim absence.

Supporting variant layouts are:

RefOpV1::Create {
    ref_name: BoundedUtf8<max_ref_name_bytes>,
    target: CommitmentV1,
}
RefOpV1::Set {
    ref_name: BoundedUtf8<max_ref_name_bytes>,
    expected_revision: Revision,
    target: CommitmentV1,
}
RefOpV1::Delete {
    ref_name: BoundedUtf8<max_ref_name_bytes>,
    expected_revision: Revision,
}
UsernameLeaseGuardV1 { owner: AccountId, allocated_counter: Counter, revision: Revision }

expected_project_mutation_counter is Some exactly when a PUSH contains at least one CREATE and None otherwise. The payload carries no independent ref-name digest, project ID derivation result, offer expiry, key expiry, username deadline, or allocated revision. Execution derives those values from signed inputs, parent counters, active rules, and block time.

For TRANSFER_OFFER.Set, the mutation counter is Some exactly when expected_offer is Absent; it is None for a live update or expired replacement that names the predecessor revision.

5. Unsigned evidence and external wrappers

PushEvidenceV1 {
    witnesses: BoundedVec<MembershipWitnessV1,max_ref_witnesses,max_push_evidence_bytes>,
}
MembershipWitnessV1::SameCommand { index: SameCommandIndex }
MembershipWitnessV1::Location { location: MembershipLocation }
MembershipWitnessV1::Proof {
    anchor_size: MembershipLocation,
    location: MembershipLocation,
    proof: NativeMembershipProof,
}

TempoCreditEvidenceV1 {
    finalization_certificate_bytes: BoundedBytes<max_tempo_certificate_bytes>,
    tempo_header_rlp: BoundedBytes<max_tempo_header_rlp_bytes>,
    contract_account_proof_nodes:
        BoundedVec<BoundedBytes<max_tempo_proof_node_bytes>,max_tempo_account_proof_nodes,max_tempo_account_proof_bytes>,
    beneficiary_storage_proof_nodes:
        BoundedVec<BoundedBytes<max_tempo_proof_node_bytes>,max_tempo_storage_proof_nodes,max_tempo_storage_proof_bytes>,
}
TempoRefreshEvidenceV1 {
    finalization_certificate_bytes: BoundedBytes<max_tempo_certificate_bytes>,
    tempo_header_rlp: BoundedBytes<max_tempo_header_rlp_bytes>,
}

MembershipLocation and HistoryLocation are aliases of NativeMmrLocation. They are not fixed eight-byte integers. Their semantic domain is the native MMR location range.

PUSH evidence contains exactly one witness per CREATE or SET in operation order and none for DELETE. Tempo evidence does not repeat beneficiary, profile ID, contract, code hash, state root, slot, cumulative, or source chain ID. The verifier derives them from the intent, authenticated header, proof nodes, and active profile.

The external Tempo header codec is RLP, not commonware-codec. At the Tempo pin, crates/primitives/src/header.rs::TempoHeader encodes [general_gas_limit, shared_gas_limit, timestamp_millis_part, inner, consensus_context?] with the trailing consensus context omitted for pre-fork blocks. Makechain requires one exact canonical RLP item, re-encode equality, timestamp_millis_part < 1000, and checked inner.timestamp * 1000 + timestamp_millis_part. The certificate authenticates keccak256(tempo_header_rlp) under the separately pinned certificate profile.

6. State key and value registry

The key formula is:

state_key = BLAKE3(family_domain || canonical(full_identity))

Family domains are the exact ASCII strings below. A terminating NUL is not present. Every value starts with its complete identity fields in the same order. Revision 1 adds no in-band record version; the active ruleset selects the one live representation.

Family Domain Identity fields Value fields after identity, in order Class
AccountLedger makechain.state.account-ledger account: AccountId credited: Amount256, spent: Amount256, locked: Amount256 Permanent
AccountRoot makechain.state.account-root account: AccountId config_commitment: Option<Digest32>, version: U64, root_sequence: Sequence, delegation_epoch: Epoch, key_allocation_counter: Counter, epoch_start_key_counter: Counter, keychain_revision: Revision Permanent
DelegatedKey makechain.state.delegated-key account: AccountId, key_id: KeyId public_key: PublicKeyV1, capability: Capability, project_scope: Option<ProjectId>, validity_window: Option<ValidityWindowV1>, verification_policy: VerificationPolicyV1, expires_at_ms: TimestampMs, authorization_generation: Generation, issued_delegation_epoch: Epoch, sequence_mark: Sequence, deposit: DepositV1 Deposited
Project makechain.state.project project_id: ProjectId owner: AccountId, scheme_id: SchemeId, status: ProjectStatus, control_generation: Generation, lifecycle_revision: Revision, mutation_counter: Counter, origin: Option<CommitmentV1> Permanent
ProjectMetadata makechain.state.project-metadata project_id: ProjectId slug: BoundedUtf8<max_slug_bytes>, description: BoundedUtf8<max_project_description_bytes> Permanent
SlugClaim makechain.state.slug-claim owner: AccountId, slug: BoundedUtf8<max_slug_bytes> project_id: ProjectId, allocated_counter: Counter, deposit: DepositV1 Deposited
ProjectRole makechain.state.project-role project_id: ProjectId, grantee: AccountId role: ProjectRoleKind, grant_generation: Generation, issued_control_generation: Generation, deposit: DepositV1 Deposited
TransferOffer makechain.state.transfer-offer project_id: ProjectId destination: AccountId, revision: Revision, issued_control_generation: Generation, expires_at_ms: TimestampMs, deposit: DepositV1 Deposited
Ref makechain.state.ref project_id: ProjectId, ref_name_digest: Digest32 target: CommitmentV1, revision: Revision, deposit: DepositV1 Deposited
UsernameLease makechain.state.username-lease name: BoundedUtf8<max_username_bytes> owner: AccountId, allocated_counter: Counter, revision: Revision, resolution_deadline_ms: TimestampMs, reservation_deadline_ms: TimestampMs, deposit: DepositV1 Deposited
AccountUsername makechain.state.account-username account: AccountId current_name: Option<BoundedUtf8<max_username_bytes>>, current_incarnation_counter: Option<Counter>, allocation_counter: Counter, last_selection_at_ms: Option<TimestampMs> Permanent
SettlementAnchor makechain.state.settlement-anchor singleton: U8 = 0 settlement_profile_id: ProfileId, group_key: BoundedBytes<max_tempo_group_key_bytes>, from_epoch: U64, installed_at_makechain_height: Height, tempo_height: U64, tempo_timestamp_ms: TimestampMs, accepted_at_makechain_timestamp_ms: TimestampMs, imports_paused: Bool Permanent singleton
ProtocolReleaseEntry makechain.state.protocol-release-entry record_kind: ReleaseRecordKind, entry_digest: Digest32 record: ProtocolReleaseRecordV1 Permanent append-only family
ProtocolScheduleState makechain.state.protocol-schedule singleton: U8 = 0 revision: Revision, governance_nonce: U64, governance_set: GovernanceSetV1, active_release_digest: ReleaseDigest, tail_entry_digest: Option<Digest32>, pending_release_digest: Option<ReleaseDigest>, pending_entry_digest: Option<Digest32>, effective_parameters: EffectiveParametersV1 Permanent singleton
ValidatorScheduleEntry makechain.state.validator-schedule identity: ValidatorScheduleIdentityV1 entry: ValidatorScheduleRecordV1 Permanent append-only family

ProtocolReleaseRecordV1::Descriptor carries one ReleaseDescriptorV1 whose digest is the identity digest. ScheduleEntry carries one immutable activate, defer, cancel, or emergency entry whose separate entry_digest = BLAKE3("makechain.release-schedule-entry" || canonical(entry)) is the identity digest. ActivationMarker carries only the installed release digest and uses entry_digest = release_digest; its distinct record-kind tag prevents collision with the descriptor. This is the required release_applied/{release_digest} marker without adding a sixteenth family. The corresponding ProtocolReleaseIdentityV1 variants are Descriptor { release_digest }, ScheduleEntry { entry_digest }, and ActivationMarker { release_digest } in that tag order.

ValidatorScheduleIdentityV1 is a closed tagged identity union: Schedule { entry_id }, Cancel { entry_id }, KeyReservation { identity_key }, and ScheduleState { singleton: U8 = 0 }. Schedule and cancel entry IDs use their distinct digest domains over the complete immutable record. A reservation is a direct lookup by identity key, and the schedule state is a direct singleton lookup. This lets the family hold immutable lineage entries, permanent key reservations, and the pending pointer without a scan. A future release that permits two simultaneous live value shapes must add an explicit leading record-revision tag; it cannot reinterpret revision-1 bytes.

Ref intentionally does not repeat the clear name. Its value still repeats its complete registered identity: project ID plus digest. SlugClaim and UsernameLease retain clear bounded strings because the string itself is part of their full identity.

Creation values are explicit. INIT_ACCOUNT stores root version zero, the accepted intent sequence as root_sequence, delegation_epoch = 1, key_allocation_counter = 1, epoch_start_key_counter = 1, and keychain_revision = 0; its commitment is None for a single-key account and Some(genesis_configuration_commitment) for a configured account. A new delegated key starts with sequence_mark = 0. PROJECT_CREATE starts control generation and lifecycle revision at one, consumes mutation counter one for its initial slug incarnation, and stores project mutation counter two. The first username lease similarly consumes account username allocation counter one and stores two. Protocol and validator schedule revisions and the governance nonce start at zero as described in section 7. Implementations do not obtain any of these values from language defaults.

7. Header, body, genesis, and profile wrappers

HeaderV1 {
    height: Height,
    timestamp_ms: TimestampMs,
    parent_hash: Digest32,
    consensus_context: ConsensusContextV1,
    active_release_digest: ReleaseDigest,
    state_root: Digest32,
    state_target: NativeStateTarget,
    membership_target: NativeMembershipTarget,
    history_target: NativeHistoryTarget,
    command_root: Digest32,
    command_count: U32,
    genesis_epoch_info_hash: Option<Digest32>,
    dealer_log_hash: Option<Digest32>,
    dkg_outcome_hash: Option<Digest32>,
}
BodyV1 {
    commands: BoundedVec<SignedCommandV1,max_commands_per_block,max_commands_bytes>,
    genesis_epoch_info: Option<ProfileEpochInfoV1>,
    dealer_log: Option<NativeDealerLog>,
    dkg_outcome: Option<NativeEpochInfo>,
}
ConsensusContextV1 { epoch: Epoch, view: View }

command_count equals the command vector count. The three body optionals and their header hashes must correspond exactly. genesis_epoch_info is present exactly at height zero. Under FixedEpochZeroV1, dealer log and DKG outcome are always None; Some(empty) is invalid. The genesis hash covers the complete ProfileEpochInfoV1 wrapper, including its profile tag. The dealer-log and DKG-outcome hashes cover the exact pinned native inner encodings, without their body option tags or a synthetic Payload wrapper.

Under DynamicReshareV1, dealer_log and dkg_outcome are mutually exclusive and project to exactly one pinned glue::dkg::types::Payload variant when present. A final epoch block carries the epoch outcome; an eligible non-final dealing block may carry a dealer log; every other height carries neither. Genesis uses only genesis_epoch_info, projected separately to the required epoch-info payload, and keeps both ordinary DKG options absent.

GenesisChainSpecV1 {
    deployment_incarnation_nonce: FixedBytes32,
    genesis_timestamp_ms: TimestampMs,
    consensus_profile: ConsensusProfileConfigV1,
    genesis_committee: SortedVec<ValidatorParticipantV1,64,max_genesis_committee_bytes>,
    genesis_directory: NativeValidatorDirectory,
    exporter_roster: SortedVec<ExporterRosterEntryV1,max_exporter_roster,max_exporter_roster_bytes>,
    genesis_release: ReleaseDescriptorV1,
    governance_set: GovernanceSetV1,
    format_ids: FormatIdentifiersV1,
    limits: LimitsV1,
    price_schedule: PriceScheduleV1,
    work_table: WorkTableV1,
    scheme_registry: SchemeRegistryV1,
    time_parameters: TimeParametersV1,
    consensus_parameters: ConsensusParametersV1,
    admission_rates: AdmissionRatesV1,
    root_webauthn_origins: SortedVec<BoundedUtf8<max_webauthn_origin_bytes>,max_webauthn_origins,max_webauthn_origins_bytes>,
    membership_window: U64,
    username_claims_paused: Bool = false,
    reclaims_paused: Bool = false,
    multisig_recovery_factory: Option<AccountId>,
    settlement: SettlementConfigurationV1,
}

The outer order and nested catalog are revision-1 design selections. chain_id, private shares, production DKG output, and credit allocations are absent.

exporter_roster lists the non-voting mesh peers that publish history, one entry per exporter, in strictly increasing identity-key order. Specification R-13.43 makes that roster part of the transport set under every profile, and never part of the scheduled, ceremony, or effective signing set. multisig_recovery_factory is the set-once configured-genesis factory address of specification R-7.5 to R-7.7; it is None at launch and a nonzero address once set.

Genesis construction is exact. HeaderV1.height = 0, timestamp_ms is the chainspec value, parent_hash is 32 zero bytes, consensus context is epoch zero/view zero, and active_release_digest is the digest of genesis_release. The body has an empty command vector, the profile's validated ProfileEpochInfoV1, and no dealer log or DKG outcome. command_count is zero and command_root is the root returned by the pinned native BMT builder for zero leaves.

The initial authenticated state seed contains exactly: the genesis release descriptor record, its activation marker, the ProtocolScheduleState singleton with the chainspec's complete effective parameter snapshot, the ValidatorScheduleState singleton, one validator key-reservation record for each genesis identity, and, when settlement is enabled, the SettlementAnchor singleton. It contains no ledger, account, credit frontier, or user record. Encode each full family value, sort the writes by derived 32-byte state key, reject duplicate keys, and seal them as one batch on top of the native current database's bootstrap commit. state_root is that database's resulting canonical current root and state_target is its resulting native ops-root target; neither is a hard-coded empty digest.

The genesis release has activation height zero, a 32-zero-byte predecessor, ruleset ID zero, class Reset, consensus_critical = false, empty declared surfaces, and no installer. Its name and notes digest are explicit chainspec inputs. Its parameter-delta list is empty, its format IDs equal the chainspec format IDs, and its price-schedule and work-table IDs equal the chainspec values. This is a genesis construction rule only; continuing-chain schedule commands never bypass delay, predecessor, installer, or class validation.

The protocol schedule starts at revision and governance nonce zero, with the chainspec governance set, active genesis release, no schedule tail, and no pending release. The settlement anchor copies the enabled chainspec profile ID and bootstrap fields and records installed height zero. The validator schedule starts at revision zero with no tail and no pending entry; each genesis identity has a reservation whose first_entry_id is None.

The genesis effective snapshot copies price_schedule, work_table, limits, and admission_rates; copies both activation delays, block-time bounds, and root WebAuthn origins; constructs ConsensusTimeoutsV1 from the five corresponding consensus fields; copies exporter_roster and multisig_recovery_factory; and maps the chainspec settlement branch to the same effective branch. Both command-pause booleans must be false at genesis, and multisig_recovery_factory is None at genesis. The enabled branch uses the derived settlement singleton key. No other chainspec field is silently projected into governed parameters.

The membership and history databases remain at their native bootstrap commits for genesis: full targets have range [0,1) and compact history has size 1, with the corresponding pin-derived initial roots. Genesis performs no artificial append. When building child height H, append the parent header leaf before the child's commit; header height h is consequently at history location 2h + 1, and the child target size is 2H + 1, with checked arithmetic.

On fresh Start::Genesis, before Stateful starts, the engine opens only the current-state member. If it is at the pin-derived native bootstrap target, it applies the one bounded sorted seed batch, finalizes it, waits for the durability barrier, reopens it, and requires both the expected canonical root and expected native target. If it already equals those two expected values, bootstrapping is an idempotent no-op; any third state is fatal. It then drops the bootstrap handles and starts Stateful. It does not call application genesis execution: Stateful never applies genesis and its fresh-start rewind path cannot advance an empty database. A peer-state-sync start skips the seed and obtains the operations from its selected floor. Membership and history must not receive an empty merkleize, which would advance their bootstrap commits.

Revision 1 proposes this complete nested catalog. Every field is present; profiles do not omit inapplicable fields outside their tagged profile branch.

TimeParametersV1 {
    delegated_key_term_ms: U64,
    transfer_offer_term_ms: U64,
    username_term_ms: U64,
    username_grace_ms: U64,
    username_rename_cooldown_ms: U64,
    username_horizon_ms: U64,
    max_block_advance_ms: U64,
    min_empty_block_interval_ms: U64,
    min_activation_delay_blocks: U64,
    min_emergency_delay_blocks: U64,
    testnet_lead_blocks: U64,
    post_activation_soak_blocks: U64,
}
ConsensusParametersV1 {
    random_version: U16,
    term_length: U64,
    track_historical_votes: Bool,
    marshal_variant: U8,
    retained_marshal_blocks: U64,
    max_pending_acks: U32,
    timeout_initial_ms: U64,
    timeout_min_ms: U64,
    timeout_max_ms: U64,
    timeout_growth_numerator: U32,
    timeout_growth_denominator: U32,
    max_participants: U16,
    min_participants: U16,
    max_additions_per_epoch: U8,
    max_removals_per_epoch: U8,
}
AdmissionRatesV1 {
    per_account_commands_per_window: U32,
    per_account_bytes_per_window: U32,
    anonymous_commands_per_window: U32,
    anonymous_bytes_per_window: U32,
    window_ms: U64,
    max_pool_commands: U32,
    max_pool_bytes: U64,
}
NamespaceInputsV1 {
    consensus_suffix: NonEmptyBytes<max_namespace_suffix_bytes>,
    dkg_suffix: NonEmptyBytes<max_namespace_suffix_bytes>,
    p2p_suffix: NonEmptyBytes<max_namespace_suffix_bytes>,
}
DynamicBootstrapParametersV1 {
    sharing_mode: NativeSharingMode,
    reveal_mode: DkgRevealModeV1,
    transcript_version: U8 = 0,
    bootstrap_timeout_ms: U64,
    reshare_timeout_ms: U64,
}
ExporterRosterEntryV1 {
    identity_key: ValidatorPublicKey,
    address: NativeValidatorAddress,
}
FormatIdentifiersV1 { ... section 8 ... }
LimitsV1 { ... section 8 ... }
PriceScheduleV1 { ... section 8 ... }
WorkTableV1 { ... section 8 ... }
SchemeRegistryV1 {
    schemes: SortedVec<SchemeDescriptorV1,max_schemes,max_scheme_registry_bytes>,
}
SchemeDescriptorV1 {
    scheme_id: SchemeId,
    name: NonEmptyBytes<max_scheme_name_bytes>,
    hash_algorithm: HashAlgorithmTag,
    id_length: U16,
    object_format_version: U16,
    addressing_epoch: U16,
    canonical_decoding: CanonicalDecodingTag,
    permitted_kinds: U32,
    per_kind_preimage_rules: BoundedVec<PreimageRuleV1,max_scheme_kinds,max_scheme_preimage_rules_bytes>,
}
PreimageRuleV1 {
    object_kind: U8,
    rule: PreimageRuleTag,
}

NativeSharingMode uses the pinned Commonware codec and decode mode. DkgRevealModeV1 is a Makechain byte because pinned feldman_desmedt::Reveal has no native Read or Write; tag 01 maps to Reveal::V1, while deprecated Reveal::V0 is rejected. transcript_version is a manifest consistency assertion against the pinned private TRANSCRIPT_VERSION = Version::V0, not a runtime configuration passed to Commonware. Dealer-log capacity is not encoded here: derive blocks - (blocks / 2 + 1) for epoch lengths of at least four, otherwise zero, exactly as pinned glue/src/dkg/types.rs, and reject a chainspec whose derived capacity is below the required dealer logs.

PreimageRuleTag allocates 00 FlatBytes, 01 MkitDomainBmtV1, and 02 GitTypeSpaceLengthNulBody. The launch registry has exactly these three descriptors in increasing scheme_id order: mkit-blake3 at commit cace229e6ae464bdec25ed1b87f063db16e9d1a1, with 32-byte IDs, MkitObjectV1, and its kind-specific flat/BMT rules; git-sha1 with 20-byte IDs, GitLooseObjectV1, and the Git preimage rule; and git-sha256 with 32-byte IDs and the same Git decoding/preimage rule. The manifest computes each scheme_id = BLAKE3("makechain.scheme" || canonical(descriptor_without_scheme_id)) and verifies the stored ID.

The mkit profile permits object kinds blob 1, tree 2, commit 3, remix 4, chunked blob 5, and tag 7. Delta 6 is pack-only and cannot be a standalone native ID. Publication verification requires kind 3. Flat objects hash their canonical bytes. Tree and chunked-blob BMT leaves bind position as big-endian u32; their exact domain bytes are mkit.tree\x00 and mkit.chunked\x00. Mkit domain hashing is BLAKE3(le16(domain_length) || domain || body). The pinned SPEC-OBJECTS and SPEC-MERKLE-OBJECTS vectors remain the external byte authority; Makechain does not reimplement the object schema.

random_version = 1, term_length = 1, track_historical_votes = true, and marshal_variant = 0 for standard deferred marshal at genesis. The seven-validator fixed profile sets min_participants = max_participants = 7, while dynamic sets 7 and 64. Addition and removal limits are each one. Fixed durations are encoded as their exact millisecond values from the state-machine companion. Calibrated durations and ceilings remain required manifest inputs.

ConsensusProfileConfigV1::FixedEpochZero {
    epoch_length: U64 = u64::MAX,
    namespace_inputs: NamespaceInputsV1,
    trusted_public_output: NativeDkgOutput,
    epoch_zero_info: NativeEpochInfo,
}
ConsensusProfileConfigV1::DynamicReshare {
    epoch_length: U64,
    namespace_inputs: NamespaceInputsV1,
    bootstrap_parameters: DynamicBootstrapParametersV1,
}

ProfileEpochInfoV1::Fixed { epoch_info: NativeEpochInfo }
ProfileEpochInfoV1::Dynamic { epoch_info: NativeEpochInfo }

The fixed profile validates exactly seven ordered identities, total 7, required 5, epoch 0, empty revealed set, and identical dealers, output players, players, and next players. These wrappers carry public data only. A reset chooses fresh incarnation, fixture output/shares, chain ID, genesis block, and storage namespaces.

8. Release and pinned-format wrappers

FormatIdentifiersV1 {
    canonical_schema_id: SchemaId,
    canonical_schema_revision: SchemaRevision,
    commonware_codec_id: CodecId,
    state_layout_id: LayoutId,
    membership_layout_id: LayoutId,
    history_layout_id: LayoutId,
    command_bmt_id: LayoutId,
    consensus_certificate_id: CodecId,
    scheme_registry_digest: Digest32,
}

ReleaseDescriptorV1 {
    name: BoundedUtf8<max_release_name_bytes>,
    notes_digest: Digest32,
    predecessor: ReleaseDigest,
    class: ReleaseClass,
    consensus_critical: Bool,
    activation_height: Height,
    ruleset_id: RulesetId,
    price_schedule_id: PriceScheduleId,
    work_table_id: WorkTableId,
    declared_surfaces: SortedVec<ReleaseSurface,max_release_surfaces,max_release_surfaces_bytes>,
    parameter_deltas: SortedVec<ParameterDeltaV1,max_parameter_deltas,max_parameter_delta_bytes>,
    installer: Option<ActivationInstallerV1>,
    format_ids: FormatIdentifiersV1,
}

Each release digest hashes one canonical descriptor under makechain.release. Schedule entries never inline a second copy of mutable current state. The remaining nested schemas are:

GovernanceSetV1 {
    revision: Revision,
    threshold: U16,
    keys: SortedVec<FixedBytes32,max_governance_keys,max_governance_keys_bytes>,
}

ReleaseScheduleEntryV1::Activate {
    entry_sequence: U64,
    predecessor_entry_digest: Option<Digest32>,
    descriptor: ReleaseDescriptorV1,
    authorizing_governance_revision: Revision,
    governance_nonce: U64,
}
ReleaseScheduleEntryV1::Defer {
    entry_sequence: U64,
    predecessor_entry_digest: Digest32,
    previous_release_digest: ReleaseDigest,
    deferred_descriptor: ReleaseDescriptorV1,
    authorizing_governance_revision: Revision,
    governance_nonce: U64,
}
ReleaseScheduleEntryV1::Cancel {
    entry_sequence: U64,
    predecessor_entry_digest: Digest32,
    pending_release_digest: ReleaseDigest,
    authorizing_governance_revision: Revision,
    governance_nonce: U64,
}
ReleaseScheduleEntryV1::EmergencyActivate {
    entry_sequence: U64,
    predecessor_entry_digest: Option<Digest32>,
    descriptor: ReleaseDescriptorV1,
    authorizing_governance_revision: Revision,
    governance_nonce: U64,
}

ActivationInstallerV1::WriteSettlementAnchor { input: SettlementAnchorInstallV1 }
ActivationInstallerV1::RotateGovernanceSet { governance_set: GovernanceSetV1 }
ActivationInstallerV1::ReserveValidatorKey { identity_key: ValidatorPublicKey }
ActivationInstallerV1::QualifiedHandler {
    handler_id: Digest32,
    input_schema_id: SchemaId,
    input_bytes: BoundedBytes<max_installer_input_bytes>,
}

ValidatorParticipantV1 { identity_key: ValidatorPublicKey }
ValidatorPopV1 { identity_key: ValidatorPublicKey, signature: FixedBytes64 }
ValidatorScheduleRecordV1::Schedule {
    revision: Revision,
    predecessor_entry_id: Option<Digest32>,
    target_epoch: Epoch,
    participants: SortedVec<ValidatorParticipantV1,64,max_validator_participants_bytes>,
    directory: NativeValidatorDirectory,
}
ValidatorScheduleRecordV1::Cancel {
    predecessor_entry_id: Digest32,
    cancelled_entry_id: Digest32,
    cancel_sequence: U64,
}
ValidatorScheduleRecordV1::KeyReservation {
    identity_key: ValidatorPublicKey,
    first_entry_id: Option<Digest32>,
}
ValidatorScheduleRecordV1::ScheduleState {
    revision: Revision,
    tail_entry_id: Option<Digest32>,
    pending_entry_id: Option<Digest32>,
}

Only the first release schedule entry may use predecessor_entry_digest = None; every later entry, including activate, defer, cancel, and emergency activate, names the exact current tail. Only the first validator schedule record may omit its predecessor, and every later schedule or cancel record names that family's exact current tail. A cancel record never rewrites the cancelled record.

Every installer variant maps to one closed handler in the activated ruleset. A qualified handler ID is registered in that release's manifest with its exact input schema, maximum reads/writes, and permitted destination families before scheduling. Revision 1 forbids every installer from creating or changing AccountLedger, credited, spent, locked, deposited user records, or replay marks. It also forbids an arbitrary family/value write. A new-family initializer may be introduced only as a new reviewed handler in a versioned-state release. The activation marker and all handler writes commit in one batch.

Validator schedule and cancel records are immutable. Cancel appends its own record and clears the singleton pending pointer; it does not mutate or delete the Schedule record or key reservation. Effective status derives from the append-only entries plus ScheduleState.

A defer command's executor derives deferred_descriptor from the current pending descriptor by changing only activation_height, then recomputes its release digest. Every other field must be byte-equal. The schedule state points at that new release digest and the immutable defer-entry digest.

The validator identity is the Commonware participant identity key. A participant entry never embeds a per-validator 96-byte threshold key; the DKG public polynomial and local share machinery own that material. Each newly added identity supplies Ed25519 proof of possession over BLAKE3("makechain.validator-possession" || chain_id || U64(target_epoch) || identity_key). The proofs vector contains exactly the lexicographically sorted added identities, which is at most one at launch. Existing participants supply no repeated proof.

ReleaseSurface tags are: 00 CommandValidity, 01 Execution, 02 Pricing, 03 Encoding, 04 ProofFormat, 05 SigningNamespace, 06 HandshakeNamespace, 07 RandomVersion, 08 TermLength, 09 ConsensusTimeouts, 0a DkgSharingMode, 0b DkgRevealMode, 0c DkgTranscriptVersion, 0d CommitteeBounds, 0e MarshalVariant, and 0f P2pWireCodec. The list is strictly increasing. Tags 05..=0f require consensus_critical = true; the first five do so only when the declared change also names a critical surface.

ParameterDeltaV1 is a tagged key/value union. Tags and value domains are:

Tag Parameter Value and validation
00 PriceSchedule Complete PriceScheduleV1; ID must match its digest
01 WorkTable Complete WorkTableV1; ID must match its digest
02 Limits Complete LimitsV1; aggregate relationships validate
03 AdmissionRates AdmissionRatesV1; every count/byte rate is nonzero
04 MinActivationDelayBlocks nonzero U64
05 MinEmergencyDelayBlocks nonzero U64, no greater than ordinary delay
06 RootWebAuthnOrigins sorted nonempty bounded origin vector
07 ImportsPaused Bool
08 SettlementTrustingPeriodMs nonzero U64
09 SettlementMaxSourceClockSkewMs U64
0a SettlementEvidenceLimits complete SettlementEvidenceLimitsV1; aggregates validate
0b SettlementProfile complete SettlementSourceProfileV1; changed only with the anchor installer
0c MaxBlockAdvanceMs nonzero U64
0d MinEmptyBlockIntervalMs nonzero U64, no greater than max block advance
0e ConsensusTimeouts complete ConsensusTimeoutsV1; all nonzero
0f GovernanceSet complete GovernanceSetV1; revision is current plus one
10 UsernameClaimsPaused Bool
11 ReclaimsPaused Bool
12 ExporterRoster complete sorted roster vector; every entry distinct by identity key and by address
13 MultisigRecoveryFactory nonzero AccountId; valid only while the effective value is None

No delta carries an arbitrary string key or untyped bytes. A descriptor contains at most one delta of each tag, in increasing tag order. Tags 08, 09, and 0a are field-specific updates to the effective settlement profile; they are mutually exclusive with the complete-profile tag 0b. Either form derives and validates the new profile ID and requires WriteSettlementAnchor in the same descriptor. A descriptor cannot change profile bytes while leaving the anchor's profile ID behind.

Tag 13 is the set-once path of specification R-7.6: a descriptor carrying it is invalid unless the parent snapshot's multisig_recovery_factory is None, and the delta value is never the zero address (R-7.7). Tag 12 replaces the complete roster; it changes transport membership only and no scheduled, ceremony, or signing set.

ReleaseDescriptorV1 has no separate settlement-profile field. The typed settlement deltas above are its only continuing-chain representations. ImportsPaused writes SettlementAnchor.imports_paused; the effective snapshot below carries only the singleton state-key reference, so there is one authoritative pause bit. Both deltas are invalid under DisabledDiagnostic; that branch is reset-only.

At an activation height, rule resolution reads the parent snapshot and referenced anchor, validates the descriptor, and applies its bounded deltas and installer to an in-memory overlay before any command in that block is checked. The activated pause values and replacement anchor therefore govern the first activation block. username_claims_paused rejects every USERNAME_CLAIM variant before its command logic, and reclaims_paused rejects RECLAIM; no other command family is affected by these two flags. If one descriptor contains both ImportsPaused and WriteSettlementAnchor, their Boolean values must agree; disagreement is invalid before the overlay is applied.

LimitsV1 {
    max_block_bytes: U32,
    max_block_work_units: U64,
    max_new_records: U32,
    max_command_bytes: U32,
    max_command_work_units: U64,
    max_command_new_records: U32,
    max_commands_per_block: U32,
    max_commitments_per_push: U32,
    max_ref_ops_per_push: U32,
    max_ref_name_bytes: U32,
    max_slug_bytes: U32,
    max_project_description_bytes: U32,
    max_username_bytes: U32,
    max_native_id_bytes: U32,
    max_state_value_bytes: U32,
    max_release_descriptor_bytes: U32,
    max_proof_response_bytes: U32,
    max_receipt_bytes: U32,
    max_historical_proof_work_units: U64,
    max_reconciliation_blocks: U64,
    max_preparation_response_bytes: U32,
    top_level: TopLevelLimitsV1,
    keys: KeyLimitsV1,
    project_push: ProjectPushLimitsV1,
    username: UsernameLimitsV1,
    state: StateLimitsV1,
    release: ReleaseLimitsV1,
    validator: ValidatorLimitsV1,
    schemes: SchemeLimitsV1,
    settlement: SettlementLimitsV1,
    proof_response: ProofResponseLimitsV1,
    events: EventLimitsV1,
}
TopLevelLimitsV1 {
    max_genesis_chainspec_bytes: U32,
    max_header_bytes: U32,
    max_body_bytes: U32,
    max_commands_bytes: U32,
    max_signed_command_bytes: U32,
    max_command_intent_bytes: U32,
    max_authorization_bytes: U32,
    max_unsigned_evidence_bytes: U32,
}
KeyLimitsV1 {
    max_public_key_bytes: U32,
    max_signature_container_bytes: U32,
    max_root_approvals: U32 = 8,
    max_root_node_levels: U32 = 1,
    max_nested_account_edges: U32 = 0,
    max_root_approvals_bytes: U32,
    max_owner_configuration_bytes: U32,
    max_governance_keys: U32,
    max_governance_keys_bytes: U32,
    max_governance_witness_bytes: U32,
    max_webauthn_credential_id_bytes: U32,
    max_webauthn_client_data_json_bytes: U32,
    max_webauthn_rp_id_bytes: U32,
    max_webauthn_origin_bytes: U32,
    max_webauthn_origins: U32,
    max_webauthn_origins_bytes: U32,
}
ProjectPushLimitsV1 {
    max_commitments_bytes: U32,
    max_ref_ops_bytes: U32,
    max_ref_witnesses: U32,
    max_push_evidence_bytes: U32,
    max_membership_proof_digests: U32,
}
UsernameLimitsV1 {
    min_username_bytes: U32,
    max_username_years_per_command: U32,
}
StateLimitsV1 {
    max_account_ledger_value_bytes: U32,
    max_account_root_value_bytes: U32,
    max_delegated_key_value_bytes: U32,
    max_project_value_bytes: U32,
    max_project_metadata_value_bytes: U32,
    max_slug_claim_value_bytes: U32,
    max_project_role_value_bytes: U32,
    max_transfer_offer_value_bytes: U32,
    max_ref_value_bytes: U32,
    max_username_lease_value_bytes: U32,
    max_account_username_value_bytes: U32,
    max_settlement_anchor_value_bytes: U32,
    max_protocol_release_entry_value_bytes: U32,
    max_protocol_schedule_value_bytes: U32,
    max_validator_schedule_entry_value_bytes: U32,
    max_effective_parameters_bytes: U32,
    max_genesis_state_writes: U32,
    max_genesis_state_bytes: U32,
    max_current_state_proof_digests: U32,
    state_bitmap_chunk_bytes: U32 = 64,
}
ReleaseLimitsV1 {
    max_release_name_bytes: U32,
    max_release_surfaces: U32,
    max_release_surfaces_bytes: U32,
    max_parameter_deltas: U32,
    max_parameter_delta_bytes: U32,
    max_release_lineage_entries: U32,
    max_release_lineage_bytes: U32,
    max_installer_input_bytes: U32,
    max_installer_writes: U32,
    max_family_amount_table_bytes: U32,
    max_command_work_table_bytes: U32,
    max_signature_work_table_bytes: U32,
    max_root_depth_work_bytes: U32,
}
ValidatorLimitsV1 {
    max_validator_participants: U32 = 64,
    max_validator_participants_bytes: U32,
    max_validator_directory_bytes: U32,
    max_validator_single_pop_bytes: U32,
    max_validator_pop_bytes: U32,
    max_genesis_committee_bytes: U32,
    max_genesis_directory_bytes: U32,
    max_namespace_suffix_bytes: U32,
    max_exporter_roster: U32,
    max_exporter_roster_bytes: U32,
}
SchemeLimitsV1 {
    max_schemes: U32,
    max_scheme_registry_bytes: U32,
    max_scheme_name_bytes: U32,
    max_scheme_kinds: U32,
    max_scheme_preimage_rules_bytes: U32,
}
SettlementLimitsV1 {
    max_tempo_group_key_bytes: U32,
}
ProofResponseLimitsV1 {
    max_finality_evidence_bytes: U32,
    max_history_proof_digests: U32,
    max_command_proof_bytes: U32,
}
EventLimitsV1 {
    max_events_per_block: U32,
    max_event_batch_bytes: U32,
    max_event_payload_bytes: U32,
    max_deposit_entries_per_event: U32,
    max_deposit_entries_bytes: U32,
    max_ref_changes_bytes: U32,
}
PriceScheduleV1 {
    price_schedule_id: PriceScheduleId,
    work_table_id: WorkTableId,
    byte_price: Amount256,
    work_unit_price: Amount256,
    permanent_creation_fees: SortedVec<FamilyAmountV1,9,max_family_amount_table_bytes>,
    deposited_creation_fees: SortedVec<FamilyAmountV1,6,max_family_amount_table_bytes>,
    deposited_locks: SortedVec<FamilyAmountV1,6,max_family_amount_table_bytes>,
    username_year_price: Amount256,
    minimum_initialized_balance: Amount256,
}
FamilyAmountV1 { family: StateFamilyTag, amount: Amount256 }
WorkTableV1 {
    work_table_id: WorkTableId,
    base_by_command: SortedVec<CommandWorkV1,17,max_command_work_table_bytes>,
    signature_by_family: SortedVec<SignatureWorkV1,4,max_signature_work_table_bytes>,
    per_commitment: U64,
    per_ref_op: U64,
    per_proof_digest: U64,
    per_mpt_node: U64,
    per_state_read: U64,
    per_state_write: U64,
    per_membership_append: U64,
    per_new_record: U64,
    root_depth_surcharge: BoundedVec<U64,3,max_root_depth_work_bytes>,
}
CommandWorkV1 { command_tag: U8, base_work_units: U64 }
SignatureWorkV1 { family: KeyFamily, verify_work_units: U64 }
ConsensusTimeoutsV1 {
    initial_ms: U64,
    minimum_ms: U64,
    maximum_ms: U64,
    growth_numerator: U32,
    growth_denominator: U32,
}
SettlementEvidenceLimitsV1 {
    max_certificate_bytes: U32,
    max_header_rlp_bytes: U32,
    max_account_proof_nodes: U32,
    max_storage_proof_nodes: U32,
    max_proof_node_bytes: U32,
    max_account_proof_bytes: U32,
    max_storage_proof_bytes: U32,
    max_credit_evidence_bytes: U32,
}
EffectiveParametersV1 {
    price_schedule: PriceScheduleV1,
    work_table: WorkTableV1,
    limits: LimitsV1,
    admission_rates: AdmissionRatesV1,
    min_activation_delay_blocks: U64,
    min_emergency_delay_blocks: U64,
    root_webauthn_origins: SortedVec<BoundedUtf8<max_webauthn_origin_bytes>,max_webauthn_origins,max_webauthn_origins_bytes>,
    username_claims_paused: Bool,
    reclaims_paused: Bool,
    settlement: EffectiveSettlementV1,
    max_block_advance_ms: U64,
    min_empty_block_interval_ms: U64,
    consensus_timeouts: ConsensusTimeoutsV1,
    exporter_roster: SortedVec<ExporterRosterEntryV1,max_exporter_roster,max_exporter_roster_bytes>,
    multisig_recovery_factory: Option<AccountId>,
}
EffectiveSettlementV1::DisabledDiagnostic {}
EffectiveSettlementV1::Enabled {
    profile: SettlementSourceProfileV1,
    anchor_state_key: FixedBytes32,
}

Each price table covers exactly its own storage class, and every family of that class appears in it exactly once, in increasing state-family tag order. The nine permanent families are AccountLedger, AccountRoot, Project, ProjectMetadata, AccountUsername, SettlementAnchor, ProtocolReleaseEntry, ProtocolScheduleState, and ValidatorScheduleEntry. The six deposited families are DelegatedKey, SlugClaim, ProjectRole, TransferOffer, Ref, and UsernameLease. A deposited family never appears in permanent_creation_fees, and a permanent family never appears in deposited_creation_fees or deposited_locks. Every deposited lock is nonzero.

LimitsV1 carries one typed field for every symbol of the bound registry in section 13, except the eight settlement evidence bounds that SettlementEvidenceLimitsV1 carries under their unprefixed names, and username_horizon_ms, which TimeParametersV1 carries. Its first twenty-one fields are the block, command, record, and response ceilings; the eleven sub-structs that follow carry the section 13 groups (the settlement evidence group lives in SettlementEvidenceLimitsV1, and username_horizon_ms in TimeParametersV1), and no bound appears in two places. max_validator_participants is the fixed collection cardinality of every validator vector and is distinct from the governed ConsensusParametersV1.max_participants, which selects the committee bound within it. Delta tag 02 replaces the complete structure, so a limits change is one reviewed value set rather than a patch. Work multiplication uses checked widened arithmetic and fits U64 before resource comparison. PriceScheduleV1.work_table_id must equal the ID of the effective WorkTableV1; a signed price-schedule ID therefore binds every work coefficient. The effective snapshot is bounded by max_effective_parameters_bytes, stored in ProtocolScheduleState, and rehashed after every activation. In its enabled branch, the settlement anchor key must equal the derived singleton key, the profile ID must equal the anchor's profile ID, and rules read the pause bit only from that authenticated anchor.

SettlementSourceProfileV1 {
    profile_id: ProfileId,
    tempo_chain_id: U64,
    certificate_codec_id: CodecId,
    header_codec_id: CodecId,
    settlement_contract: AccountId,
    settlement_runtime_code_hash: Digest32,
    contract_layout_id: LayoutId,
    cumulative_mapping_base_slot: FixedBytes32,
    cumulative_value_bits: U16 = 256,
    accepted_asset_address: AccountId,
    accepted_asset_currency: FixedAscii3,
    accepted_asset_decimals: U8,
    trusting_period_ms: U64,
    max_source_clock_skew_ms: U64,
    evidence_limits: SettlementEvidenceLimitsV1,
}
SettlementConfigurationV1::DisabledDiagnostic {}
SettlementConfigurationV1::Enabled {
    profile: SettlementSourceProfileV1,
    anchor: SettlementAnchorBootstrapV1,
}
TempoBlockDigestV1 = FixedBytes32
NativeTempoFinalization = commonware_consensus::simplex::types::Finalization<
    commonware_consensus::simplex::scheme::bls12381_threshold::vrf::Scheme<
        commonware_cryptography::ed25519::PublicKey,
        commonware_cryptography::bls12381::MinSig
    >,
    tempo_consensus::Digest
>
SettlementAnchorBootstrapV1 {
    group_key: BoundedBytes<max_tempo_group_key_bytes>,
    from_epoch: U64,
    tempo_height: U64,
    tempo_timestamp_ms: TimestampMs,
    accepted_at_makechain_timestamp_ms: TimestampMs,
    imports_paused: Bool,
}
SettlementAnchorInstallV1 {
    group_key: BoundedBytes<max_tempo_group_key_bytes>,
    from_epoch: U64,
    tempo_height: U64,
    tempo_timestamp_ms: TimestampMs,
    imports_paused: Bool,
}

Every devnet and testnet deployment profile selects Tempo Moderato. Mainnet selects Tempo mainnet. The profile carries real addresses, hashes, identities, and checkpoints through a reviewed deployment manifest; this document supplies no placeholder address or default checkpoint. Slot zero is encoded as 32 zero bytes. The accepted asset currency is ASCII USD, decimals is 6, and the cumulative is a full 256-bit value. from_epoch is explicit in the active anchor and rejects certificates from an earlier Tempo identity epoch. The profile assumes the pinned Tempo group key identifies only the selected source network for every accepted epoch at or after from_epoch; certificate namespace TEMPO does not provide network separation. A deployment review must verify that uniqueness and record the evidence with the anchor.

DisabledDiagnostic is valid only for an unfunded, nondeployed local diagnostic chain. It creates no settlement anchor, makes both import variants invalid, and supplies no other credit path. It cannot be enabled by a continuing-chain delta; doing so requires a reset. It is an explicit tag, never a zero address or partial source profile.

The genesis bootstrap's accepted_at_makechain_timestamp_ms must equal genesis_timestamp_ms. A release installer never accepts either installed_at_makechain_height or the accepted-at time from governance bytes: execution derives them from the activation height and activation block timestamp. It takes the profile ID from the activated effective settlement profile and the source checkpoint fields from SettlementAnchorInstallV1.

The external certificate candidate is pinned to Tempo commit 25b216a661c2cac72807174583371b582b75c286, crates/consensus/src/finalization_verifier/mod.rs:9-24,89-104: the native finalization above uses the Commonware 9.0 codec, namespace TEMPO, and a 32-byte Tempo consensus digest whose bytes equal the Keccak-256 header hash. Makechain decodes the native certificate and compares the authenticated payload to TempoBlockDigestV1; it does not reproduce that codec. This pin is a candidate until an authentic deployment certificate and header fixture qualify it.

9. Native Commonware contracts

Makechain wraps native types but does not reimplement their bytes. The format manifest must record the complete generic instantiation, decoder configuration, crate, path, tag, and commit. The verified source paths at v2026.9.0 are listed below. The proof path (specification section 6.2) instantiates these types with a Keccak256 hasher: the library's own cryptography::keccak256 is on its main branch after that tag, at commit v2026.9.0-69-gffe2cf2af2 and at ALPHA, so Makechain instantiates them with its own implementation of the pinned version's Hasher trait, whose vectors cross-check the upstream one. Every other instantiation is available at the tag. The paths are:

NativeStateTarget      = qmdb::sync::Target<mmr::Family, keccak256::Digest>
NativeMembershipTarget = qmdb::sync::Target<mmr::Family, keccak256::Digest>
NativeHistoryTarget    = qmdb::sync::CompactTarget<mmr::Family, keccak256::Digest>
NativeMembershipProof  = merkle::Proof<mmr::Family, keccak256::Digest>
NativeHistoryProof     = merkle::Proof<mmr::Family, keccak256::Digest>
NativeCurrentStateProof = qmdb::current::proof::OperationProof<mmr::Family, keccak256::Digest, 64>
NativeBmtProof         = bmt::Proof<keccak256::Digest>
NativeMmrLocation     = commonware_storage::merkle::Location<mmr::Family>
MembershipLocation    = NativeMmrLocation
HistoryLocation       = NativeMmrLocation
NativeSharingMode     = commonware_cryptography::bls12381::primitives::sharing::Mode
ValidatorPublicKey    = commonware_cryptography::ed25519::PublicKey
ValidatorIdentitySigner = commonware_cryptography::ed25519::PrivateKey
NativeValidatorAddress = commonware_p2p::Address
NativeValidatorDirectory = commonware_glue::dkg::network::Addresses<ValidatorPublicKey>
ThresholdVrfMinSig    = commonware_consensus::simplex::scheme::bls12381_threshold::vrf::Scheme<ValidatorPublicKey, MinSig>
NativeSimplexFinalization = simplex::types::Finalization<ThresholdVrfMinSig, keccak256::Digest>
NativeEpochInfo        = glue::dkg::types::EpochInfo<MinSig, ValidatorPublicKey, NativeValidatorDirectory>
NativeDkgOutput        = commonware_cryptography::bls12381::dkg::feldman_desmedt::Output<MinSig, ValidatorPublicKey>
NativeDealerLog        = commonware_cryptography::bls12381::dkg::feldman_desmedt::SignedDealerLog<MinSig, ValidatorIdentitySigner>

The displayed paths omit crate prefixes only for readability. The Rust aliases use the exact public paths recorded in the format manifest.

Role Native source contract Native field order at the pin
BMT command proof commonware_storage::bmt::Proof<D>; storage/src/bmt/mod.rs leaf_count: u32, counted siblings: Vec<D>; decode config is maximum proved items and derives at most items * MAX_LEVELS siblings
Merkle inclusion proof commonware_storage::merkle::Proof<F,D>; storage/src/merkle/proof.rs leaves: Location<F>, shortest-form UInt<u64> inactive_peaks, counted digests: Vec<D>
Current-state activeness proof commonware_storage::qmdb::current::proof::OperationProof<F,D,N>; storage/src/qmdb/current/proof/mod.rs loc: Location<F>, fixed bitmap chunk: [u8;N], native RangeProof<F,D>
Compact QMDB sync target commonware_storage::qmdb::sync::CompactTarget<F,D>; same path root: D, size: Location<F>
Simplex finalization commonware_consensus::simplex::types::Finalization<S,D>; consensus/src/simplex/types.rs Native proposal followed by native scheme certificate
Threshold VRF certificate commonware_consensus::simplex::scheme::bls12381_threshold::vrf::Certificate<V>; consensus/src/simplex/scheme/bls12381_threshold/vrf.rs Native fixed-size certificate for selected BLS variant
DKG epoch artifact commonware_glue::dkg::types::EpochInfo<V,P,D>; glue/src/dkg/types.rs outcome, epoch, native output, players, next_players, directory; decode config (max_participants, max_supported_mode)
DKG block payload commonware_glue::dkg::types::Payload<V,C,D>; same path tag 00 SignedDealerLog, tag 01 EpochInfo; Makechain stores the selected inner value in its separate body option

The application uses the native BMT proof directly in command receipts. It uses the native Merkle proof directly for membership and history inclusion. It uses the native current-state operation proof for value plus activeness. state_root remains the application-derived canonical current root, while the native QMDB target carries the operations root and range. These fields are never flattened into a generic root/siblings wrapper.

Revision 1 proposes the following complete storage instantiation:

Member Instantiation
Validity state qmdb::current::unordered::variable, merkle::mmr::Family, cryptography::keccak256::Digest, 32-byte keys, variable values, bitmap chunk N = 64 bytes
Membership log full qmdb::keyless::fixed, merkle::mmr::Family, cryptography::keccak256::Digest, fixed 32-byte leaves
Block history validator and exporter compact qmdb::keyless, full operation log in the proof service, merkle::mmr::Family, cryptography::keccak256::Digest, fixed 32-byte leaves
Command root storage::bmt::Builder<cryptography::keccak256::Keccak256> over the block's ordered 32-byte command-tree leaves, each Keccak256(canonical(SignedCommandV1))
Simplex simplex::scheme::bls12381_threshold::vrf::Scheme<ValidatorPublicKey, MinSig> and its Certificate<MinSig>
DKG public artifacts glue::dkg::types::EpochInfo<MinSig, ValidatorPublicKey, NativeValidatorDirectory>, its native Output<MinSig,ValidatorPublicKey>, and SignedDealerLog<MinSig,ValidatorIdentitySigner>

ValidatorPublicKey and ValidatorIdentitySigner are the pinned Commonware Ed25519 public- and private-key types. The latter fixes the public-key and signature associated types that determine SignedDealerLog wire bytes; custody implementations must expose those exact associated types and bytes. No private key is encoded in the log or any canonical public artifact. The launch directory is the stock commonware_glue::dkg::network::Addresses<ValidatorPublicKey>, a native ordered map from that key to commonware_p2p::Address; its decoder configuration is the exact peer-set cardinality derived by the pinned Directory trait. Genesis, validator scheduling, and NativeEpochInfo use these same bytes and type. MMR locations and sizes use the native commonware_storage::merkle::Location<mmr::Family> codec, which writes and reads the shortest-form commonware_codec::varint::UInt<u64> and enforces the family's maximum of 2^62 leaves. It is not a fixed-width u64. The bitmap choice follows the pinned glue any-adapter's ANY_BITMAP_CHUNK_BYTES = 64 value in glue/src/stateful/db/any.rs:49; the current adapter does not impose it, so revision 1 selects N = 64 explicitly because it affects the root. Glue exposes full qmdb::sync::Target<mmr::Family,keccak256::Digest> for the current state and full logs, and qmdb::sync::CompactTarget<mmr::Family,keccak256::Digest> for the compact history member; compile-time assertions and native vectors pin those exact target shapes. The BLS MinSig selection keeps 48-byte signatures and 96-byte public keys and aligns the separate Tempo certificate profile, while retaining distinct namespace and profile IDs. Dynamic DKG wrappers use these exact glue::dkg output, dealer-log, and epoch-info types. The final manifest records the compiler-resolved aliases and their source paths; it may not copy their fields into Makechain structs. Working HEAD types are irrelevant even if their names match.

Every revision-1 current, membership, and history commit passes native commit metadata None, including the current-state genesis seed. A block hash is never placed there because that would cycle through the header roots. This rule fixes the operation bytes and therefore the roots. It does not change inner native operation encodings: for example, pinned keyless fixed Commit encodes its floor using that operation's native big-endian u64 field even though standalone Location<mmr::Family> uses UInt<u64>.

Every history commit on validators and the exporter is the identical native fixed-keyless Commit(None, Location(0)); compact storage affects local retention, not the committed floor or root. Validators and the exporter prune witness material locally. The proof service retains it from zero.

The canonical header always carries the validator's compact history target. The proof service, which holds the published operation logs from location zero, constructs the full sync target as Target { root: header.history_target.root, range: [Location(0), header.history_target.size) } and requires a nonzero valid size and exact root equality. It does not use the native TryFrom<&CompactTarget> convenience conversion, which represents only the final-leaf range [size - 1,size). Proof-service ingestion and proof generation retain floor zero.

10. Proof and receipt wrappers

ProofContextV1 { header: HeaderV1, finality: FinalityEvidenceV1, release: ReleaseLineageV1 }
FinalityEvidenceV1::Direct { finalization: NativeSimplexFinalization }
FinalityEvidenceV1::DescendantAncestry {
    descendant_header: HeaderV1,
    descendant_finalization: NativeSimplexFinalization,
    ancestor_height: Height,
    history_location: HistoryLocation,
    history_proof: NativeHistoryProof,
}
ReleaseLineageV1 {
    schedule_value_bytes: BoundedBytes<max_protocol_schedule_value_bytes>,
    schedule_proof: NativeCurrentStateProof,
    entries: BoundedVec<ReleaseLineageRecordProofV1,max_release_lineage_entries,max_release_lineage_bytes>,
}
ReleaseLineageRecordProofV1 {
    identity: ProtocolReleaseIdentityV1,
    value_bytes: BoundedBytes<max_protocol_release_entry_value_bytes>,
    proof: NativeCurrentStateProof,
}
ProofEnvelopeV1 {
    schema_revision: U16 = 1,
    context: ProofContextV1,
    payload: ProofPayloadV1,
}
ProofPayloadV1::CurrentState {
    identity: StateIdentityV1,
    value_bytes: BoundedBytes<max_state_value_bytes>,
    proof: NativeCurrentStateProof,
}
ProofPayloadV1::Membership {
    project_id: ProjectId,
    commitment: CommitmentV1,
    location: MembershipLocation,
    proof: NativeMembershipProof,
}
ProofPayloadV1::Command {
    signed_command_bytes: BoundedBytes<max_signed_command_bytes>,
    command_index: CommandIndex,
    proof: NativeBmtProof,
}
ProofPayloadV1::Finality {}
ProofPayloadV1::ReleaseLineage {}
ProofPayloadV1::PublicationReceipt {
    signed_command_bytes: BoundedBytes<max_signed_command_bytes>,
    command_index: CommandIndex,
    proof: NativeBmtProof,
    commitment_index: CommitmentIndex,
}

This acyclic envelope carries one header, one finality object, one release-lineage object, and one kind-specific payload. ReleaseLineageV1.schedule_proof verifies the supplied schedule-state value under context.header.state_root; it does not contain a second proof context.

StateIdentityV1 is the 15-variant tagged union whose variant fields are exactly the identity fields in section 6. For a current-state proof, the verifier derives the 32-byte key from that typed identity, decodes value_bytes as the matching family value, requires its repeated identity to match, constructs the exact native unordered-current Operation::Update(key,value_bytes), and verifies that operation's activeness. A valid opaque key/value proof cannot be relabeled as another family. The release-lineage schedule key is the derivable ProtocolScheduleState singleton key and is not repeated.

Each release-lineage record similarly derives its family key from ProtocolReleaseIdentityV1, checks the decoded record's repeated identity, and verifies its native current-state proof. The records start at the active descriptor and follow every authenticated schedule predecessor needed to reach the proven schedule tail; activation markers are included where activation is claimed. Empty lineage is valid only when the proven schedule state names the genesis release with no tail.

Finality verification first verifies the native certificate under the epoch-selected scheme, then requires finalization.proposal.payload == block_hash(header) and finalization.proposal.round == header.consensus_context. The descendant form applies the same two checks to descendant_finalization and descendant_header, requires ancestor_height == header.height < descendant_header.height, derives the exact history location 2 * ancestor_height + 1, requires it equal history_location, and verifies the exact history leaf(header.height, block_hash(header)) against descendant_header.history_target. The proof target root and size must equal that header target; a certificate and unrelated header cannot be paired.

Receipt verification recomputes the command-tree leaf Keccak256(canonical(SignedCommandV1)) from signed_command_bytes, requires the BMT proof's native leaf count to equal header.command_count, checks the zero-based index, requires PUSH, and checks the commitment index. It derives every claim from authenticated bytes. A membership location, event, charge, or resulting ref revision needs separate evidence.

Membership and history proofs authenticate encoded fixed-keyless operations rather than bare digest leaves. The verifier constructs the exact native Operation::Append(membership_leaf) or Operation::Append(history_leaf), uses that operation's native encoding in range-inclusion verification, and requires proof.leaves to equal the authenticated target's end/size. History uses floor zero. Both proof kinds call proof.matches_canonical_inactive_peaks(target_end, target_floor) using the authenticated target range; for history the floor argument is zero. Verifying the raw 32-byte application leaf against the Merkle proof is invalid.

Every receipt proves exactly one command, so native bmt::Proof::Read::Cfg is 1; that parameter is the number of proved items, not a sibling limit. The library consequently permits at most its 32-level derived sibling count. Decode the enclosing proof slice under max_command_proof_bytes before native decoding and require full consumption.

11. Digest and signing preimages

The fixed digest registry is:

Name Preimage
chain_id BLAKE3("makechain.chainspec" || canonical(GenesisChainSpecV1))
intent_digest BLAKE3("makechain.command" || canonical(CommandIntentV1))
command_id BLAKE3("makechain.command-envelope" || canonical(SignedCommandV1))
block_hash Keccak256("makechain.block-header" || canonical(HeaderV1)) (proof path)
root node BLAKE3("makechain.root-authorization" || intent_digest || account || U64(config_version))
ref name BLAKE3("makechain.ref-name" || canonical(BoundedUtf8<max_ref_name_bytes>))
membership leaf Keccak256(project_id || scheme_id || native_id) (proof path), the commitment's own CommitmentV1 fields in that order, scheme_id fixed at 32 bytes and native_id running to the end of the preimage
command-tree leaf Keccak256(canonical(SignedCommandV1)) (proof path), one per command of a block, in execution order under command_root
history leaf Keccak256("makechain.block-history" || chain_id || U64(parent_height) || parent_hash) (proof path)
project ID BLAKE3("makechain.project" || chain_id || creator || client_salt)
key ID BLAKE3("makechain.key-id" || canonical(AccountScopedKeyV1))
root slot ID BLAKE3("makechain.root-slot" || account)
commitment ID BLAKE3("makechain.commitment" || canonical(CommitmentV1))
release digest BLAKE3("makechain.release" || canonical(ReleaseDescriptorV1))
schedule-entry digest BLAKE3("makechain.release-schedule-entry" || canonical(ReleaseScheduleEntryV1))
scheme ID BLAKE3("makechain.scheme" || canonical(SchemeDescriptorV1 without scheme_id))
price-schedule ID BLAKE3("makechain.price-schedule" || canonical(PriceScheduleV1 without price_schedule_id))
work-table ID BLAKE3("makechain.work-table" || canonical(WorkTableV1 without work_table_id))
settlement profile ID BLAKE3("makechain.settlement-profile" || canonical(SettlementSourceProfileV1 without profile_id))
scheme-registry digest BLAKE3("makechain.scheme-registry" || canonical(SchemeRegistryV1))
effective-parameters digest BLAKE3("makechain.effective-parameters" || canonical(EffectiveParametersV1))
validator schedule entry ID BLAKE3("makechain.validator-schedule-entry" || canonical(ValidatorScheduleRecordV1::Schedule))
validator cancel entry ID BLAKE3("makechain.validator-cancel-entry" || canonical(ValidatorScheduleRecordV1::Cancel))
validator proof of possession BLAKE3("makechain.validator-possession" || chain_id || U64(target_epoch) || identity_key)
governance digest BLAKE3("makechain.governance" || intent_digest)
event ID BLAKE3("makechain.event" || chain_id || block_hash || U32(command_index) || U32(event_index))
genesis epoch-info hash BLAKE3("makechain.genesis-epoch-info" || canonical(ProfileEpochInfoV1))
dealer-log hash BLAKE3("makechain.dealer-log" || canonical(NativeDealerLog))
DKG-outcome hash BLAKE3("makechain.dkg-outcome" || canonical(NativeEpochInfo))

Do not infer any other signature preimage from this table. Revision 4 fixes these family adapters:

  • Ed25519 delegated commands use SSHSIG namespace makechain.command:<chain_id_hex> over intent_digest.
  • P256 direct signs SHA-256("makechain.p256" || d), where d is intent_digest for a delegate and the root node digest for an owner.
  • WebAuthn challenge is intent_digest for a delegate and the root node digest for an owner.
  • A secp256k1 owner uses EIP-712 rebuilt from decoded intent/node fields. Makechain chain_id is the domain salt and the Tempo host chain ID is domain.chainId.

The canonical Rust registry emits the literal EIP-712 type strings selected by section 3.2, and the reviewed vectors preserve them. Vectors demonstrate these definitions; they never replace them.

12. System event payloads

Finalized events are the derived report of specification section 14. This section fixes the batch framing, the closed registry of kind tags, and the payload layout of every kind, so that two implementations produce byte-identical batches for a height and any consumer decodes one without private knowledge. Every table in this section is Proposed. An event enters no root, so these bytes are a delivery contract rather than a consensus input; they belong in spec/ because a consumer re-derives a batch byte for byte from certified inputs instead of trusting a supplied one (specification R-14.15), which is an output the boundary of README.md keeps normative.

12.1 Batch and event framing

EventBatchV1 = BoundedVec<EventV1,max_events_per_block,max_event_batch_bytes>
EventV1 {
    kind: U8,
    command_index: Option<CommandIndex>,
    event_index: U32,
    payload: BoundedBytes<max_event_payload_bytes>,
}

One batch covers one height and carries no height field, because delivery is one batch per height and the event identifier already binds the chain and the block. The identifier is the event ID row of section 11 and is not restated here. command_index is None for a block-level event, and that event's identifier preimage takes the block-level sentinel fixed by specification section 14. A wire Some(0xFFFF_FFFF) is a decode error, and section 13 bounds max_commands_per_block below that sentinel, so no command event and block-level event of one block can share an EventId. payload is the length-prefixed canonical encoding of the kind's layout in section 12.4, which is what lets a consumer skip an unknown kind; a decoder that knows the kind consumes the payload slice completely and rejects trailing bytes.

Batch order is ascending (command_index, event_index), comparing the section 14 sentinel value of a block-level event rather than the wire Option tag, so the block-level group orders last. For each applied command, in the flat block order of specification section 12, event_index zero is CommandFinalized and the command's domain events follow in the order its transition produces them: for PUSH, one CommitmentAdded per commitment in commitment order and then RefsChanged; for a rename, the source name before the destination name. event_index restarts at zero for the block-level group, whose events are ordered by ascending kind tag.

12.2 Kind registry

The registry is closed. A kind is emitted exactly as its tag and layout below, an unregistered tag is invalid, and a later release adds a kind only by appending the next unassigned tag.

Tag Kind Emitted for
0x01 CommandFinalized every applied command, at event_index zero
0x02 AccountInitialized INIT_ACCOUNT
0x03 RootConfigured ROOT_CONFIGURE
0x04 KeyAuthorized KEY_AUTHORIZE, both variants
0x05 KeyRevoked KEY_REVOKE.One
0x06 DelegationEpochAdvanced KEY_REVOKE.All
0x07 ProjectCreated PROJECT_CREATE
0x08 ProjectMetadataSet PROJECT_SET_METADATA
0x09 ProjectStatusSet PROJECT_SET_STATUS
0x0a RoleChanged ROLE_SET, both variants
0x0b TransferOfferChanged TRANSFER_OFFER, both variants
0x0c ProjectTransferred TRANSFER_ACCEPT
0x0d CommitmentAdded each commitment of an applied PUSH, in commitment order
0x0e RefsChanged an applied PUSH, once, covering every affected ref
0x0f RecordReclaimed RECLAIM
0x10 UsernameLeaseChanged USERNAME_CLAIM, and the destination name of a rename
0x11 UsernameReleased USERNAME_RELEASE, and the source name of a rename
0x12 CreditsImported IMPORT_CREDITS, both variants
0x13 ReleaseScheduled SCHEDULE_RELEASE, all four variants
0x14 ValidatorSetScheduled VALIDATOR_SET_SCHEDULE, both variants
0x15 ReleaseActivated block level, the activation of a release
0x16 ValidatorSetLockedIn block level, the lock-in of a scheduled validator-set entry
0x17 DkgCarryOver block level, a reshare carry-over

0x00 is invalid. 0x18..=0x7f are unassigned and available to a later release. 0x80..=0xff are reserved for a future format revision and are invalid in revision 1. A retired tag is never reassigned.

12.3 Shared payload shapes

ResourceVectorV1 { block_bytes: U32, work_units: U64, new_records: U32 }
ChargeV1 { fees_charged: Amount256, deposits_locked: Amount256, deposits_discharged: Amount256 }
DepositEntryV1 { record: DepositedRecordV1, deposit: DepositV1 }
DepositListV1 = SortedVec<DepositEntryV1,max_deposit_entries_per_event,max_deposit_entries_bytes>

DepositedRecordV1::DelegatedKey {
    account: AccountId, key_id: KeyId, authorization_generation: Generation,
}
DepositedRecordV1::SlugClaim {
    owner: AccountId, slug: BoundedUtf8<max_slug_bytes>, project_id: ProjectId,
    allocated_counter: Counter,
}
DepositedRecordV1::ProjectRole {
    project_id: ProjectId, grantee: AccountId, grant_generation: Generation,
}
DepositedRecordV1::TransferOffer { project_id: ProjectId, revision: Revision }
DepositedRecordV1::Ref { project_id: ProjectId, ref_name_digest: Digest32, revision: Revision }
DepositedRecordV1::UsernameLease {
    name: BoundedUtf8<max_username_bytes>, incarnation: IncarnationV1, revision: Revision,
}

RefChangeV1 {
    ref_name: BoundedUtf8<max_ref_name_bytes>,
    operation: RefOp,
    old_target: Option<CommitmentV1>,
    old_revision: Option<Revision>,
    new_target: Option<CommitmentV1>,
    new_revision: Option<Revision>,
}

DepositedRecordV1 uses the record's StateFamilyTag value from section 2 as its variant tag, so the six deposited families are the only valid tags here and the nine permanent families are invalid. Each variant carries its family's registered identity fields from section 6 followed by every value the family's compare-and-set binds (an incarnation, a revision, or both), and DepositV1 carries the stored payer and the stored amount unchanged.

A discharged entry names the record as it stood before the transition deleted or replaced it; a locked entry names the record the transition wrote. DepositListV1 is a SortedVec, so its entries increase by canonical encoding and no entry repeats, which is the canonical order specification section 14 requires. A payload below carries a list only for a family that can lock or discharge a deposit, and a transition that locks or discharges nothing carries the empty list.

ResourceVectorV1 holds the three dimensions of specification section 10.1 and ChargeV1 holds the command's totals, the fees added to spent and the deposits added to and released from locked under specification section 9.6. fees_charged is every non-refundable amount the command added to spent, summed across every ledger it touched: the canonical-byte charge, the reserved work charge, each record creation fee, the username lease price, and the first-import ledger creation fee that IMPORT_CREDITS charges to the proved beneficiary's own ledger rather than to a signer's. It never includes a deposit, which deposits_locked carries. Per-record deposit detail is in the domain event, never in ChargeV1.

In a RefChangeV1, old_target and old_revision are present for a SET or a DELETE and absent for a CREATE; new_target and new_revision are present for a CREATE or a SET and absent for a DELETE. A compare-and-set that allocates a fresh revision reports the new value, and a no-op SET repeats its revision and its target, which is how the outcome of each operation is read. Operations appear in the order the command applied them, so a repeated name appears once per operation.

12.4 Payload layouts

CommandFinalized, tag 0x01. The event exists only for an applied command, so it carries no status field; the command's outcome is its resource vector, its charge, and the domain events that follow it.

Field Type Width Meaning
command_id Digest32 32 the envelope identity of section 11
intent_digest Digest32 32 the intent identity of section 11
command_tag U8 1 the CommandPayload tag of section 2.1
actor Option<AccountId> variable the signing account of an account-context command; absent for a system command, whose charged party the paired domain event names
key_id Option<KeyId> variable the signing slot; absent for a system command
sequence Option<Sequence> variable the accepted replay mark; absent for a system command
resources ResourceVectorV1 16 the reserved resource vector
charge ChargeV1 96 fees charged, deposits locked, deposits discharged

AccountInitialized, tag 0x02.

Field Type Width Meaning
account AccountId 20 the initialized account
config_commitment Option<Digest32> variable present for a configured genesis account
root_sequence Sequence 8 the accepted positive sequence

RootConfigured, tag 0x03.

Field Type Width Meaning
account AccountId 20 the reconfigured account
config_commitment Digest32 32 the stored commitment of the new configuration
version U64 8 the new AccountRoot.version

KeyAuthorized, tag 0x04. A renewal repeats the preserved capability, scope, window, generation, and issuing epoch, and carries empty deposit lists.

Field Type Width Meaning
account AccountId 20 the keychain's account
key_id KeyId 32 the authorized slot
action KeyAuthorize 1 00 authorize, 01 renew
capability Capability 1 the key's class
project_scope Option<ProjectId> variable the key's scope
validity_window Option<ValidityWindowV1> variable the key's window
expires_at_ms TimestampMs 8 the resulting expiry
authorization_generation Generation 8 the resulting generation
issued_delegation_epoch Epoch 8 the epoch the key was issued in
keychain_revision Revision 8 the advanced keychain revision
deposits_locked DepositListV1 variable the new key's deposit
deposits_discharged DepositListV1 variable a replaced invalidated generation, if any

KeyRevoked, tag 0x05.

Field Type Width Meaning
account AccountId 20 the keychain's account
key_id KeyId 32 the deleted slot
authorization_generation Generation 8 the deleted record's generation
keychain_revision Revision 8 the advanced keychain revision
deposits_discharged DepositListV1 variable the deleted key's deposit

DelegationEpochAdvanced, tag 0x06. It carries no discharge list and no key count.

Field Type Width Meaning
account AccountId 20 the account whose epoch advanced
old_delegation_epoch Epoch 8 the epoch before the command
new_delegation_epoch Epoch 8 the epoch after the command
keychain_revision Revision 8 the keychain revision after the command (R-7.37)

ProjectCreated, tag 0x07.

Field Type Width Meaning
project_id ProjectId 32 the derived project identity
owner AccountId 20 the creating controller
scheme_id SchemeId 32 the project's fixed scheme
origin Option<CommitmentV1> variable the creator-asserted origin
slug BoundedUtf8<max_slug_bytes> variable the stored canonical slug
description BoundedUtf8<max_project_description_bytes> variable the stored description
deposits_locked DepositListV1 variable the initial slug claim's deposit

ProjectMetadataSet, tag 0x08.

Field Type Width Meaning
project_id ProjectId 32 the project
slug BoundedUtf8<max_slug_bytes> variable the stored canonical slug
description BoundedUtf8<max_project_description_bytes> variable the stored description
deposits_locked DepositListV1 variable the successor slug claim, when the slug changes
deposits_discharged DepositListV1 variable the replaced slug claim, when the slug changes

ProjectStatusSet, tag 0x09.

Field Type Width Meaning
project_id ProjectId 32 the project
status ProjectStatus 1 00 active, 01 frozen
lifecycle_revision Revision 8 the advanced lifecycle sub-revision

RoleChanged, tag 0x0a. The three optional fields are present for a grant and absent for a revoke, whose deleted record is named by its discharged entry.

Field Type Width Meaning
project_id ProjectId 32 the project
grantee AccountId 20 the grantee
action RoleSet 1 00 grant, 01 revoke
role Option<ProjectRoleKind> variable the granted role
grant_generation Option<Generation> variable the fresh grant generation
issued_control_generation Option<Generation> variable the project control generation it was issued under
deposits_locked DepositListV1 variable the new grant's deposit
deposits_discharged DepositListV1 variable a replaced or deleted grant's deposit

TransferOfferChanged, tag 0x0b. The optional fields are present for a set and absent for a clear.

Field Type Width Meaning
project_id ProjectId 32 the project
action TransferOfferAction 1 00 set, 01 clear
destination Option<AccountId> variable the named destination
revision Option<Revision> variable the offer's resulting revision
issued_control_generation Option<Generation> variable the control generation it was issued under
expires_at_ms Option<TimestampMs> variable the offer's expiry
deposits_locked DepositListV1 variable a new or replacement offer's deposit
deposits_discharged DepositListV1 variable a cleared or expired offer's deposit

ProjectTransferred, tag 0x0c. The event carries the rotated control generation and never a count of deactivated grants.

Field Type Width Meaning
project_id ProjectId 32 the project
previous_owner AccountId 20 the controller before acceptance
new_owner AccountId 20 the accepting destination
control_generation Generation 8 the rotated control generation
offer_revision Revision 8 the consumed offer's revision
destination_slug BoundedUtf8<max_slug_bytes> variable the slug claimed under the new owner
deposits_locked DepositListV1 variable the destination slug claim's deposit
deposits_discharged DepositListV1 variable the consumed offer and the old slug claim

CommitmentAdded, tag 0x0d.

Field Type Width Meaning
project_id ProjectId 32 the publishing project
commitment CommitmentV1 variable the published {scheme_id, native_id} pair
commitment_index CommitmentIndex 4 the zero-based index in the command's commitment list
location MembershipLocation variable the membership-log location the leaf took

RefsChanged, tag 0x0e.

Field Type Width Meaning
project_id ProjectId 32 the project
changes BoundedVec<RefChangeV1,max_ref_ops_per_push,max_ref_changes_bytes> variable one entry per applied ref operation, in operation order
deposits_locked DepositListV1 variable every ref deposit the command locked
deposits_discharged DepositListV1 variable every ref deposit the command discharged

RecordReclaimed, tag 0x0f.

Field Type Width Meaning
deposits_discharged DepositListV1 variable exactly one entry, naming the deleted record, its incarnation, its stored payer, and the stored amount

UsernameLeaseChanged, tag 0x10.

Field Type Width Meaning
name BoundedUtf8<max_username_bytes> variable the affected name
action UsernameClaim 1 00 claim, 01 extend, 02 restore, 03 rename
owner AccountId 20 the lease's owner
incarnation IncarnationV1 28 the lease's incarnation
revision Revision 8 the resulting lease revision
resolution_deadline_ms TimestampMs 8 the resulting resolution deadline
reservation_deadline_ms TimestampMs 8 the resulting reservation deadline
deposits_locked DepositListV1 variable a new incarnation's deposit
deposits_discharged DepositListV1 variable a replaced post-reservation lease's deposit

UsernameReleased, tag 0x11. A release and the source of a rename retain the deposit, so the payload carries no list.

Field Type Width Meaning
name BoundedUtf8<max_username_bytes> variable the released name
owner AccountId 20 the lease's owner
incarnation IncarnationV1 28 the lease's incarnation
revision Revision 8 the advanced lease revision
resolution_deadline_ms TimestampMs 8 the resolution deadline, ended at consensus time
reservation_deadline_ms TimestampMs 8 the grace reservation deadline

CreditsImported, tag 0x12. A refresh moves the checkpoint only, so its beneficiary and credited fields are absent.

Field Type Width Meaning
action ImportCredits 1 00 credit, 01 refresh
beneficiary Option<AccountId> variable the proved beneficiary
credited Option<Amount256> variable the resulting credited cumulative
settlement_profile_id ProfileId 32 the profile the evidence verified under
tempo_height U64 8 the accepted certified source height
tempo_timestamp_ms TimestampMs 8 the accepted certified source timestamp

ReleaseScheduled, tag 0x13. A cancellation carries no activation height.

Field Type Width Meaning
action ScheduleRelease 1 00 activate, 01 defer, 02 cancel, 03 emergency activate
release_digest ReleaseDigest 32 the release the entry names
entry_digest Digest32 32 the appended schedule entry's digest
activation_height Option<Height> variable the resulting activation height
governance_revision Revision 8 the key-set revision that authorized the entry
governance_nonce U64 8 the governance nonce the entry consumed

ValidatorSetScheduled, tag 0x14. The entry's participants and directory stay in the command body and in state.

Field Type Width Meaning
action ValidatorSetSchedule 1 00 append, 01 cancel
entry_id Digest32 32 the appended or cancelled entry's identity
target_epoch Epoch 8 the epoch the entry targets
schedule_revision Revision 8 the resulting validator-schedule revision

ReleaseActivated, tag 0x15. Block level.

Field Type Width Meaning
release_digest ReleaseDigest 32 the release now in force
ruleset_id RulesetId 8 the ruleset its manifest names

ValidatorSetLockedIn, tag 0x16. Block level.

Field Type Width Meaning
entry_id Digest32 32 the locked entry's identity
target_epoch Epoch 8 the epoch the entry takes effect in

DkgCarryOver, tag 0x17. Block level. The emitting block is the boundary block of epoch, the final block of previous_epoch, which is the block whose dkg_outcome body field carries an EpochInfo whose outcome is Failure.

Field Type Width Meaning
epoch Epoch 8 the epoch that opens under the carried output
previous_epoch Epoch 8 the epoch whose threshold output continues
dkg_outcome_hash Digest32 32 the emitting block's own dkg_outcome_hash header field, the DKG-outcome digest of section 11 taken over that block's dkg_outcome payload, and never the hash of an earlier successful EpochInfo

13. Bound registry

Every symbol below is a typed rules/profile field in the canonical Rust registry and is recorded by the format manifest. A profile supplies a reviewed numeric value and chain validation checks that all aggregate relationships hold. No implementation default is valid.

Every symbol has exactly one such field, and no field carries a bound that is absent here. Of the 119 symbols below, LimitsV1 and its eleven group sub-structs (section 8) carry 110; the eight settlement evidence bounds are the fields of SettlementEvidenceLimitsV1, whose names drop the tempo_ infix, so max_tempo_certificate_bytes is its max_certificate_bytes and likewise for the header, node-count, per-node, per-path, and total-evidence bounds; and username_horizon_ms is a field of TimeParametersV1. The struct carrying each group's symbols is the sub-struct named after that group in section 8, except the two cases named above.

Every max_*_bytes, max_*_items, max_*_nodes, max_*_digests, and collection-cardinality field is U32 unless its fixed value is stated in the table. Work bounds and block/history ranges are U64. Millisecond durations are U64. A value used as a pinned native decoder usize configuration is first checked to fit the supported target and then converted; the canonical field remains U32.

Group Required names
Top level max_genesis_chainspec_bytes, max_header_bytes, max_body_bytes, max_block_bytes, max_block_work_units, max_new_records, max_commands_per_block, max_commands_bytes, max_signed_command_bytes, max_command_bytes, max_command_intent_bytes, max_command_work_units, max_command_new_records, max_authorization_bytes, max_unsigned_evidence_bytes, max_reconciliation_blocks
Keys and signatures max_public_key_bytes, max_signature_container_bytes, max_root_approvals = 8, max_root_node_levels = 1, max_nested_account_edges = 0, max_root_approvals_bytes, max_owner_configuration_bytes, max_governance_keys, max_governance_keys_bytes, max_governance_witness_bytes, max_webauthn_credential_id_bytes, max_webauthn_client_data_json_bytes, max_webauthn_rp_id_bytes, max_webauthn_origin_bytes, max_webauthn_origins, max_webauthn_origins_bytes
Project and push max_slug_bytes, max_project_description_bytes, max_native_id_bytes, max_commitments_per_push, max_commitments_bytes, max_ref_ops_per_push, max_ref_ops_bytes, max_ref_name_bytes, max_ref_witnesses, max_push_evidence_bytes, max_membership_proof_digests
Username min_username_bytes, max_username_bytes, max_username_years_per_command, username_horizon_ms
State max_state_value_bytes, one max_<family>_value_bytes for all 15 families, max_protocol_schedule_value_bytes, max_protocol_release_entry_value_bytes, max_effective_parameters_bytes, max_genesis_state_writes, max_genesis_state_bytes, max_current_state_proof_digests, fixed state_bitmap_chunk_bytes = 64
Release max_release_name_bytes, max_release_descriptor_bytes, max_release_surfaces, max_release_surfaces_bytes, max_parameter_deltas, max_parameter_delta_bytes, max_release_lineage_entries, max_release_lineage_bytes, max_installer_input_bytes, max_installer_writes, max_family_amount_table_bytes, max_command_work_table_bytes, max_signature_work_table_bytes, max_root_depth_work_bytes
Validator and namespaces max_validator_participants = 64, max_validator_participants_bytes, max_validator_directory_bytes, max_validator_single_pop_bytes, max_validator_pop_bytes, max_genesis_committee_bytes, max_genesis_directory_bytes, max_namespace_suffix_bytes, max_exporter_roster, max_exporter_roster_bytes
Schemes max_schemes, max_scheme_registry_bytes, max_scheme_name_bytes, max_scheme_kinds, max_scheme_preimage_rules_bytes
Settlement max_tempo_group_key_bytes, max_tempo_certificate_bytes, max_tempo_header_rlp_bytes, max_tempo_account_proof_nodes, max_tempo_storage_proof_nodes, max_tempo_proof_node_bytes, max_tempo_account_proof_bytes, max_tempo_storage_proof_bytes, max_tempo_credit_evidence_bytes
Proof response max_finality_evidence_bytes, max_history_proof_digests, max_command_proof_bytes, max_proof_response_bytes, max_receipt_bytes, max_historical_proof_work_units, max_preparation_response_bytes
Events max_events_per_block, max_event_batch_bytes, max_event_payload_bytes, max_deposit_entries_per_event, max_deposit_entries_bytes, max_ref_changes_bytes

Chain validation rejects a profile unless each per-command maximum fits max_signed_command_bytes, each legal command vector fits max_body_bytes, native proof maxima fit their library decoder configurations, aggregate byte maxima cover their components without overflow, the p2p ceiling fits the largest legal block, max_commands_per_block is below the block-level sentinel 0xFFFF_FFFF of section 12.1, max_events_per_block is at least 3 + max_commands_per_block * (2 + max_commitments_per_push), max_event_batch_bytes is at least max_events_per_block * max_event_payload_bytes plus the batch and per-event framing, and max_deposit_entries_per_event is at least max(max_ref_ops_per_push, 2), because ProjectTransferred discharges two entries, the consumed offer and the old slug claim, and max_exporter_roster is at least two, because a deployment runs one active exporter and one standby (specification R-17.15). Numeric calibration is a release input. It is not permission to omit a bound.

The settlement interface's current candidates, 512-byte certificate, 4 KiB header, 65 nodes per path, 1 KiB per node, 64 KiB per path, and 136 KiB total evidence, remain calibration candidates. They are not defaults in this contract.

14. Expected-vector design

Each vector is reviewed semantic input plus literal expected canonical bytes. Hash vectors also carry the complete preimage bytes and expected digest. Verification vectors carry the expected typed result or stable error class. A missing byte field means unresolved schema, never empty bytes.

VectorCaseV1 {
    vector_id,
    contract_id,
    schema_revision,
    profile_id,
    ruleset_id,
    source_pins,
    semantic_input,
    expected_canonical_hex,
    expected_preimage_hex?,
    expected_digest_hex?,
    expected_result,
    mutation_of?,
}

The vector container is test metadata and is never hashed. Decimal strings represent Amount256 and JSON-unsafe integers. Hex is lowercase and even length.

The minimum accepted corpus contains:

  1. Zero, one, 127, 128, u32::MAX, and out-of-range collection counts; every primitive boundary; invalid bool, option, and enum tags; truncation and trailing bytes.
  2. One positive and one negative case for every command and every subvariant in section 4, including fixed-profile rejection of both validator variants and every consensus-critical release path.
  3. Every authorization wrapper, alternative sufficient root subsets, threshold crossing before the last approval, duplicate/out-of-order approvals, maximum recursion, and one-too-deep recursion.
  4. Every one of the 15 state keys and values, identity/key mismatch, maximum value, and deposited payer/amount preservation through replacement and discharge.
  5. Empty, one-command, odd-count, maximum-depth, wrong-count, wrong-index, reordered, missing, and surplus native BMT proof cases.
  6. Native membership, history, and current-state proofs at floor and size boundaries; wrong generic profile; inactive current operation; exact decode/re-encode bytes; and roots for metadata None versus rejected Some metadata.
  7. Both genesis profiles, missing/mismatched genesis epoch info, post-genesis presence, fixed-profile nonzero epoch, and every forbidden DKG optional including Some(empty).
  8. A release lineage with zero and multiple predecessors, defer/cancel/lock boundaries, installer marker replay, a parameter-only release retaining the ruleset ID, and each of the three pause families taking effect on the first activation block.
  9. Signer-generated secp256k1, SSHSIG, P256 direct, and WebAuthn cases on native and wasm. Negative cases mutate domain, chain, node digest, typed field, low-S form, origin, challenge, flags, and exact received JSON.
  10. Authentic Moderato and mainnet settlement fixtures per deployment manifest. Generated MPT/RLP vectors cover path structure and malformed inputs but never qualify a positive live import.
  11. Publication receipts with duplicate commitment leaves, alternative envelope evidence, wrong command count/index, ref-only PUSH, reset-chain replay, direct finality, and ancestry finality.

Generation is reproducible and refuses to overwrite a reviewed vector when bytes change unless the release explicitly authorizes a breaking format. Native and wasm use the same Rust codec and verifier; no hand-written JavaScript encoder or proof verifier is a conformance authority.

15. Remaining inputs and qualification gates

The widths, tags, field order, envelope split, state layouts, signing profiles, storage generics, chainspec catalog, release schemas, and proof framing above are design selections. Conformance has not run. The following finite inputs still prevent a byte-complete, launch-qualified claim.

ID Missing input or evidence Effect
OPEN-01 Fill every numeric calibration value named in section 13, every price/work entry, consensus timeout, admission rate, response cap, and historical-work bound. Run deterministic chainspec validation of every aggregate relationship. Safe decoder configs and maximum-size vectors cannot be emitted.
OPEN-02 Compile the selected Commonware instantiations against tag v2026.9.0, assert the exact Ed25519, Addresses<ValidatorPublicKey>, Output, EpochInfo, SignedDealerLog, target, and proof types, and capture native expected bytes. The design is source-backed but native genesis, DKG, target, proof, and certificate bytes are not yet demonstrated.
OPEN-03 Pin the external implementation/text revisions for EIP-712, OpenSSH PROTOCOL.sshsig, P256, WebAuthn, and native Git object hashing; emit every literal generated EIP-712 type string and signer-produced native/wasm vector. Signature and Git profile interoperability is designed but not qualified.
OPEN-04 Deploy the reviewed immutable settlement contract on Moderato and mainnet. Supply address, runtime bytes/hash, deployment checkpoint, active identity/from-epoch, and authentic certificate, header, account-proof, and storage-proof fixtures for the pinned certificate candidate. Positive credit import and deployed settlement profiles remain unavailable.

OPEN-05 is resolved. Section 12 fixes the event batch framing, the closed registry of kind tags, and the payload layout of every kind, so event consumers are wire-compatible and the derived history tables of docs/history-query.md are frozen against it. The expected vectors for those kinds are carried by SB-09 in docs/implementation-second-batch.md, and the numeric values of the event bounds are part of the section 13 calibration listed under OPEN-01.

Implementation may build independent selected contracts before these gates finish. It must not fill a missing input with a Rust default, dependency HEAD, local limit, guessed external literal, zero address, or synthetic settlement proof.

15.1 Specification coherence disposition

The revision-4 coherence pass applies these selected representations. This table records the completed alignment surface; it is not an additional approval gate:

Specification location Applied disposition
§6.1 and §3.4 The normative wire contract, executable Rust authority, and record-only format manifest roles are aligned.
§7.4 CommandIntent uses the tagged IntentAuthority union; permissionless import has no replay/charge sentinel and governance carries revision and nonce.
§§7.2–7.5 Root authorization uses the secp256k1-only single-key branch and configured branch with address-recomputable configuration.
§7.4 and §18.3 Governance uses the selected digest, sorted threshold rule, all-key emergency rule, and readable per-command EIP-712 projection.
§8 command sections Guard placement uses the §4 Expected<T> branches and conditional parent-counter shapes; derived execution values are not caller fields.
§9.2 and §18.5 Release descriptor, immutable schedule entry, activation marker, validator cancel, and singleton pending records are distinct.
§11 and §15 Native MMR/Keccak256/QMDB/BMT instantiations, the 64-byte current bitmap chunk, MinSig threshold VRF, and acyclic proof envelope are named.
§13.3 Validator proof of possession is chain-, epoch-, and identity-bound; participant records carry no per-validator threshold public key.
§18.5 and §18.6 Installers use the closed handler registry and cannot write monetary ledgers, deposited user records, or replay marks. Rule resolution composes the schedule snapshot with the referenced anchor.
§16 Amount256, slot-zero uint256, and SettlementAnchor.identity.from_epoch use the same contracts as §§1, 6, and 8.

16. Source ledger

Source Contract used here
spec/SPECIFICATION.md revision 14, sections 5–18 Identity, signing semantics, commands, state, blocks, releases, proofs, settlement, and fixed-profile behavior
spec/state-machine.md Variant guards, expected identities/revisions/counters, access shapes, and system-command split
docs/client-api.md Signed-byte handling, witness replacement, proof wrappers, receipt derivation, and reset partitioning
spec/settlement-interface.md Immutable slot-zero cumulative contract, proof fields, uint256 recommendation, external verification order, and candidate bounds
Commonware v2026.9.0 at d476a2361ce6840d2b9d0aa6fb30a924429046d4 commonware-codec framing and the exact native BMT, Merkle, QMDB, Simplex, and threshold certificate types named in section 9
Tempo 25b216a661c2cac72807174583371b582b75c286 TempoHeader RLP shape and timestamp split; Finalization<Scheme<ed25519::PublicKey,MinSig>, consensus::Digest>, namespace TEMPO, and Keccak header-payload binding; candidate codec evidence, not a settlement-contract deployment manifest or authentic qualification fixture
Tempo TIP-1107 cluster (TIP-1108, TIP-1110, TIP-1112, TIP-1113, TIP-1114) at e7ba69eb35349d49633e3637f751ce8a3558ebb9 Adopted configurable-account owner layout, address/configuration formulas, and single-key upgrade salt; reference semantics independent of live Tempo activation
mkit cace229e6ae464bdec25ed1b87f063db16e9d1a1 Launch object kinds, native IDs, position-bound Merkle object rules, and exact tree/chunked domain bytes