9. State and record lifecycle

Draft protocolSource at 169cbd2 ↗Excerpt SHA-256 af56543…4fa

Consensus keeps three authenticated databases and nothing else. This section defines what each one holds, which header field commits it, and the life of a record from the fee and deposit charged at its creation to the transition that deletes it and returns that deposit. It also fixes how expiry is evaluated on read, how a deleted record's identity is prevented from being reused, and the bound on how fast one block may grow the state. Commands are in section 8, header fields in section 11, block execution in section 12, and prices in section 10.

9.1 The three authenticated databases

Every block header commits all three databases below. Their native instantiations are fixed by encoding section 9.

Database Holds Header commitment
Validity state (section 9.2) every record execution reads or writes state_root and state_target {ops_root, floor, size}
Membership log (section 9.3) one permanent leaf per commitment published to a project membership_target {root, floor, size}
Block-history accumulator (section 9.4) one leaf per block history_target {root, size}

R-9.1. All three databases are merkleized with Keccak256, because their roots and their leaves lie on the proof path of section 6.

R-9.2. Under format revision 1, commit metadata is absent (None) in all three members, including genesis initialization, and no operator and no handler inserts local context or a block hash into commit metadata.

INV-STATE-11. Every command observes earlier successful commands in its block; a failed candidate contributes no writes, charges, membership appends, or events to a proposed block.

The block-local overlay through which a command observes its predecessors, and the treatment of a failed candidate, are specified in section 12.

9.2 Validity state

The validity state is one unordered qmdb::current database, the authenticated key-value store that every validity rule reads and every transition writes. A record is one value in it, belonging to one family, identified by that family's identity fields and keyed by the 32-byte digest below. Each family's domain string, complete identity fields, and value layout are fixed by encoding section 6.

state_key = BLAKE3(family_domain || identity)

R-9.3. A state value carries the identity its key is derived from, and every write asserts that the value's identity matches the key it is written under.

R-9.4. The native commit floor is part of the database's canonical operation encoding.

R-9.5. The families below are the families of the genesis ruleset, and a family is added only by a versioned-state release under section 18.

Family Identity Class Rules in
AccountLedger account permanent section 9.6, funded by section 16
AccountRoot account permanent section 7
DelegatedKey account and key_id deposited section 7
Project project_id permanent section 8
ProjectMetadata project_id permanent section 8
SlugClaim owner and canonical slug deposited section 8
ProjectRole project_id and grantee deposited section 8
TransferOffer project_id deposited section 8
Ref project_id and ref_name_digest deposited section 8
UsernameLease name deposited section 8
AccountUsername account permanent section 8
SettlementAnchor singleton permanent section 16
ProtocolReleaseEntry record kind and entry digest permanent section 18
ProtocolScheduleState singleton permanent section 18
ValidatorScheduleEntry schedule identity permanent section 13

ProtocolReleaseEntry holds separate descriptor, immutable schedule-entry, and activation-marker subrecords, keyed by a subtype and the corresponding digest, and the descriptor and schedule-entry digest domains are distinct. The activation marker that guards a release installer, written release_applied/{release_digest} in section 18, is one of those subrecords and not a sixteenth family: encoding section 6 fixes its identity as the ActivationMarker variant of ProtocolReleaseIdentityV1, carrying the installed release_digest as the entry digest, and gives it a record-kind tag distinct from the descriptor subrecord, which carries the same digest. Its state key is therefore the key formula above over the ProtocolReleaseEntry family domain and that identity, and no separate family domain or marker-specific digest exists. ValidatorScheduleEntry holds separate immutable schedule and cancel records, permanent key reservations, and singleton schedule metadata.

R-9.6. A deferral never overwrites historical bytes.

Every record's initial values are fixed by the initial-value table of state machine section 3.1, which covers every root, key, project, username, and schedule base. These are Makechain state conventions; they import no Tempo storage layout and depend on the existence of no Tempo account.

R-9.7. Initialization stores (a) a configured account's verified genesis configuration commitment and its accepted root sequence, (b) root keychain revision zero, and (c) project control generation one and lifecycle generation one, with the initial slug consuming mutation counter one and leaving two.

R-9.8. Every counter starts at its specified base, with allocation counters and epochs at 1 and a single-key account's genesis authority version at 0, every increment is checked, and u64::MAX is rejected.

R-9.9. A logical deletion never restores an old revision.

R-9.10. No handler performs a state-size-dependent scan, and the bounded state-derived expansions of a command's read and write set are enumerated in section 12.

R-9.11. Consensus offers no current-state exclusion proof.

A "not found" answer from a derived view is therefore a service claim and never an authenticated absence (section 15).

R-9.12. Replacing the unordered current variant with the ordered variant is a reset-class change under section 18.

INV-STATE-02. Every counter starts at its base, increments checked, rejects u64::MAX; logical deletion never restores an old revision.

INV-STATE-06. No handler performs a state-size-dependent scan; every read and write set is bounded and enumerated by the specification.

INV-STATE-10. Every project has exactly one matching slug claim, and every slug claim names a project whose owner and canonical slug match the claim's key.

The commands that create, move, and delete a slug claim are specified in section 8.

INV-MEMBER-05. Consensus offers no current-state exclusion proof at launch; "not found" is a service claim.

9.3 The membership log

The membership log is the permanent authenticated record of publication: one qmdb::keyless database whose root covers the whole history forever, windowed on validators and on the exporter and complete in the store. A location is a leaf's position in that log and is the stable witness a client cites. The window is the suffix of the log a validator retains readable. A leaf is the 32-byte digest below, taken over the project and the published commitment's own registered identifier pair, so that a verifier recomputes it from the preimage without a BLAKE3 hasher (section 6); the commitment itself travels in the command body and in the finalized events, and its commitment_id remains the BLAKE3 identity the scheme registry defines.

membership_leaf = Keccak256(project_id || scheme_id || native_id)

R-9.13. At each commit the ruleset sets the inactivity floor as below, and that floor is part of the committed operation bytes and therefore of the root, so every role commits the identical floor.

new_floor = max(parent_floor, max(0, new_size − membership_window))

The floor is therefore nondecreasing, and a later release that widens the window stops the floor advancing until size catches up.

R-9.14. A validator prunes bytes below the floor, and an exporter prunes below the same floor only after the block's operation-log slices are durably queued for publication (section 17).

R-9.15. A location is eligible when it lies in the canonical [floor, size) range of the executing parent, which a validator resolves by point read, and eligibility is defined by that range rather than by what a node happens to retain, so a slow-pruning validator and a freshly bootstrapped one agree.

A location below that range is proven instead by an inclusion proof generated by the proof service, which can prove any location against the root at any published size.

Sizing, informative. These figures are derived at the pinned library version rather than measured. RAM is O(1) at any size. Each operation occupies 42 bytes plus about 64 bytes of persisted MMR nodes, about 106 bytes per operation, and a per-block commit operation occupies a leaf too. A window of 2^26 operations is about 2.8 GB on the wire and 7 GB on disk, independent of chain age, because a window is a traffic window and not a number of days; the store grows to about 106 GB at 10^9 operations. The window value, the maximum-rate window turnover, and the edge-crossing retry behaviour are calibration items of section 19.

INV-MEMBER-01. An accepted membership leaf is never erased from the canonical log; validator pruning below the ruleset floor is not erasure.

INV-MEMBER-06. Every role commits the identical inactivity floor for every log; retention differs, roots never do.

INV-MEMBER-02. Membership locations are pre-assigned in flat command order before any lane runs.

Flat execution order and the assignment of locations within a block are specified in section 12.

INV-MEMBER-03. A non-delete signed ref op names its target commitment; its witness binds only through a same-command index, a location in the parent's canonical [floor, size), or a valid inclusion proof anchored at a commit boundary strictly above the floor whose root the validator derives from its own prefix; a wrong witness is invalidity, an unavailable eligible source is transient, and neither is ever nonmembership.

INV-MEMBER-07. Replacing a command's evidence never changes its signed intent or exceeds its signed max_charge.

Witness forms, witness resolution, and evidence replacement are specified with the PUSH command in section 8.

INV-MEMBER-04. Every finalized height has an agreeing membership_target after processing or recovery.

The header target and its verification are specified in section 11.

9.4 The block-history accumulator

The block-history accumulator is the third database member, compact on validators and on the exporter and full in the store: one leaf per block, linking a parent height to a parent hash, so that finality of any height is provable from one later certificate.

history_leaf = Keccak256("makechain.block-history" || chain_id || u64(parent_height) || parent_hash)

location of the leaf of height h = 2*h + 1        # first appended by child h + 1
history target size after block H = 2*H + 1       # native bootstrap commit at location zero

R-9.16. Each block appends the leaf of its parent height, so the genesis block adds no parent leaf and no header references its own hash.

R-9.17. Every location and size calculation above is checked against the native family bound.

R-9.18. The accumulator's inactivity floor is zero forever on every role.

R-9.19. The proof service maps the header's CompactTarget(root, size) to Target(root, 0..size) from the published operation logs, with exact root equality and a nonzero valid size, and the native compact-to-full convenience conversion, which retains only the last operation, MUST NOT be used.

R-9.20. A validator prunes the compact variant's per-batch witness journal to the recovery horizon, and that pruning is local.

The library does not prune that journal automatically. The proof service serves an ancestry proof anchored at any directly certified descendant (section 15).

9.5 What is not in state

R-9.21. The validity state holds no commit bodies, no clear ref names, no verifications, no social graphs, no message-hash indexes, and no query-convenience structure.

A ref name enters state only as ref_name_digest; the clear name travels in the block body.

9.6 Record lifecycle

Memory model, informative. The current database keeps an in-memory index entry for every physically undeleted key, together with the grafted tree and the activity bitmap over the retained operation range. Pruning trims the operation log below the inactivity floor and never shrinks the index, so validator resident memory is approximately the sum below, where a live record is one that has not been physically deleted, expired and deactivated records included. The membership log and block bodies have bounded retention; resident validity state grows with funded usage and governed records.

resident ≈ allocated_index(live_records) + bitmap + grafted_tree + bounded caches
           + speculative and rebuild headroom

Record classes. Every family is permanent or deposited.

Class Families Rule
Permanent AccountLedger, AccountRoot, Project, ProjectMetadata, AccountUsername, SettlementAnchor, ProtocolReleaseEntry, ProtocolScheduleState, ValidatorScheduleEntry records are never deleted; metadata and pointers may update in place; account-created records pay the family's creation fee, a ledger created by a first settlement import pays under R-9.29, and governed system records are uncharged under section 10; creation is always included in resource accounting
Deposited DelegatedKey, ProjectRole, Ref, TransferOffer, UsernameLease, SlugClaim creation charges the family's creation fee and locks the family's deposit; the record is deleted or replaced only by an explicit transition, which discharges the deposit exactly once

Ledger accounting. AccountLedger holds two monotonic cumulatives, credited (the imported settlement frontier of section 16) and spent (non-refundable charges), plus a locked balance (deposits currently held by resident records).

available      = credited − spent − locked
spent + locked ≤ credited

R-9.22. Available credit is derived and never stored.

R-9.23. A creation adds its fee to spent and its deposit to locked in the same state batch that writes the record, which stores {deposit_payer, deposit_paid} immutably.

R-9.24. A discharge subtracts exactly deposit_paid from the stored payer's locked, and credited and spent never change on discharge, so a discharge is an internal unlock, never a settlement refund and never a transferable credit.

R-9.25. The amount discharged is the stored amount, whatever the price table says at the time.

R-9.26. Every charged account intent signs max_charge, the ceiling defined in section 8, and a discharge inside the same command never reduces the running total that ceiling bounds.

R-9.27. Execution aggregates ledger deltas per account, including when the signer is also a refund payer.

R-9.28. A command is invalid if any resulting ledger does not satisfy spent + locked ≤ credited, and an atomic discharge may fund a new lock in the same account but never a lock in another account.

R-9.29. The first import into an unseen address records the full proved cumulative as credited and charges the ledger creation fee as spent, and is invalid if the proved amount cannot cover the fee.

proved cumulative 100, ledger creation fee 10
  credited = 100, spent = 10, locked = 0, available = 90

R-9.30. The credited cumulative is never reduced, so re-importing the same proof never appears to advance it.

R-9.31. Locked deposits survive settlement anchor replacement and settlement contract replacement.

INV-STATE-01. Spent cumulative plus locked deposits never exceed credited cumulative; available credit is derived, never stored.

Discharge closure.

R-9.32. Only a transition named in the table below discharges a deposit.

R-9.33. A discharging transition (a) reads the raw record, (b) checks its project or owner binding, (c) checks any required expected incarnation or revision, and (d) deletes or replaces the record in the same batch as the discharge.

Record Discharging transitions Transitions that retain its deposit
Ref PUSH DELETE, including delete-then-create in one push SET, including a no-op SET
ProjectRole ROLE_SET revoke or re-grant; RECLAIM after control-generation invalidation transfer invalidates authority without deleting the record
DelegatedKey KEY_REVOKE(One); KEY_AUTHORIZE(Authorize) replacing an invalidated resident generation; RECLAIM Renew; KEY_REVOKE(All) invalidates without deletion
TransferOffer clear; replacement after expiry; consumption by TRANSFER_ACCEPT; RECLAIM update of a still-live offer
UsernameLease claim replacing a post-reservation record, including the destination of rename; RECLAIM extension, restoration during grace, release, and the released source of rename
SlugClaim slug change in PROJECT_SET_METADATA; owner or slug change in TRANSFER_ACCEPT description-only metadata update

R-9.34. A replacement pays the current creation fee and locks the current deposit, and an update that keeps the record keeps its deposit metadata.

A delete and recreate cycle therefore loses fees and never earns credit.

R-9.35. No transition deletes or replaces a deposited record without discharging its deposit, so at every height the sum of locked over all ledgers equals the sum of deposit_paid over all resident deposited records.

The deposit-conservation test that exercises that identity is described in the validation document.

INV-STATE-05. Every deposit is discharged exactly once, to its stored payer, by the transition that physically deletes or replaces its record; a discharge never raises the credited cumulative, never lowers the spent cumulative, and never replenishes a block's resource reservation.

Expiry.

R-9.36. Expiry is lazy and is a pure predicate on consensus block time.

R-9.37. Deadline intervals are half-open: a record is expired when block_timestamp_ms >= expires_at_ms, a name resolves only before its resolution deadline, and a name is reserved only before its reservation deadline.

Deadline How it is set
DelegatedKey expiry authorization sets expires_at_ms = block_timestamp_ms + key_term_ms; renewal follows section 7
DelegatedKey optional validity window starts inclusively and ends exclusively, so the key is unauthorized before its start and from its end onward; the window never extends the key's expiry
TransferOffer expiry set from consensus time under the term fixed in section 19
UsernameLease resolution and reservation deadlines set by the username commands of section 8

R-9.38. All timestamp arithmetic is checked.

R-9.39. An effective read applies the family predicate below, while a raw read returns the physically resident record for CAS, replacement, and deposit accounting.

Family Effective read treats the record as unusable when Effect of the predicate
DelegatedKey it is at or beyond its expiry, at or beyond its validity-window end, before its validity-window start, or its issued_delegation_epoch differs from the account's current delegation epoch the key is unauthorized
ProjectRole its issued_control_generation differs from the project's current control_generation the grant is unauthorized
TransferOffer it is at or beyond its expiry the offer is unacceptably stale
UsernameLease it is at or beyond its resolution deadline the name does not resolve, and the owner reservation is retained until the reservation deadline

R-9.40. A read never mutates state and never discharges a deposit.

R-9.41. Reauthorization allocates a new key generation, and neither renewal nor physical cleanup restores an old one.

INV-STATE-04. An expired or generation-invalidated record grants no authority; a username in grace does not resolve but retains its owner reservation. Raw reads still expose the record's incarnation and deposit until an explicit deletion, replacement, or reclaim removes it.

RECLAIM. RECLAIM is the permissionless deletion of one objectively stale record, discharging its deposit. Its place in the command catalog is section 8.

R-9.42. RECLAIM names exactly one record key and the incarnation it expects, and for a username lease it also binds the lease revision.

R-9.43. RECLAIM is valid only if, at execution, the named record is objectively stale by the predicate for its family below.

Family Stale when
DelegatedKey it is at or beyond its expiry, at or beyond its optional validity-window end, or its issued_delegation_epoch differs from its account's current delegation epoch; a future validity-window start alone never makes a key reclaimable
TransferOffer it is at or beyond its expiry
UsernameLease it is at or beyond its reservation deadline
ProjectRole its issued_control_generation differs from the project's current control_generation
Ref, SlugClaim never

R-9.44. RECLAIM is signed by the caller's root authority or by an unscoped active key of class WRITE or above, and the caller needs no authority over the target account or project.

R-9.45. A valid RECLAIM deletes the record, discharges its deposit to the stored payer, and emits RecordReclaimed naming that record and its discharged deposit (section 14).

R-9.46. The caller pays the command's work and receives no bounty.

R-9.47. A RECLAIM on a record that is not stale, or whose expected incarnation has moved, is invalid.

The protocol keeps no queue, sweep, or job for reclaimable records.

Incarnations and replay after deletion.

R-9.48. Every recreatable child's incarnation incorporates a monotonic counter from its surviving parent, as below.

Child Counter it draws from
DelegatedKey generation the account's key_allocation_counter
Ref revision the project's mutation counter
ProjectRole grant generation the project's mutation counter
TransferOffer revision the project's mutation counter
UsernameLease incarnation the owner's permanent AccountUsername allocation counter, paired with that owner
SlugClaim incarnation the project's mutation counter, paired with the permanent project_id

R-9.49. A metadata command retains last-writer-wins semantics for the requested metadata but resolves the old slug claim from the executing project's state and never from an unchecked caller-supplied key, and a transfer binds its offer revision and its authority context (section 7).

R-9.50. A command that expects absence also binds the parent's mutation counter, so a create, delete, and create cycle is detected.

R-9.51. No tombstones are kept.

A recreated ref therefore never reuses a revision.

INV-STATE-08. Every recreatable child's incarnation incorporates a monotonic counter from a surviving parent, so deletion never reuses an incarnation; revisions within it advance checked.

INV-STATE-03. A delayed signed command cannot regain validity after its bound revision, generation, or lifecycle sub-revision changes.

INV-STATE-09. A project-authorized command binds its control generation and, when authorized through a grant, its grant generation, and is invalid when either has moved.

The authority context an intent binds, and the guards that carry it, are specified in section 7.

Growth rate.

R-9.52. max_new_records is a chainspec bound on the gross record creations one block may contain, counting implicit records, including a ledger created by a first import, a uniqueness record, and a username pointer, and never netting deletions against creations.

It bounds how fast funded usage can grow the state, not how large the state may become.

R-9.53. State growth is priced and rate-limited and never capped, and no valid block is rejected because of a node's memory or disk.

R-9.54. The chainspec also fixes maximum encoded record sizes and per-command item limits, so every command's write set is bounded before execution.

INV-STATE-07. State growth is priced and rate-limited, never capped: a block reserves at most max_new_records gross record creations, counting implicit records and never netting deletions, and no valid block is ever rejected because of a node's local memory or disk.

9.7 Rationale

This subsection is informative.

Client-supplied witnesses replace an authenticated negative-lookup index: a push cites the position of the membership it builds on, the validator point-reads it inside its window, and anything older arrives as a proof-service inclusion proof, so publication creates no per-commit validity-state entry. The unordered current-state variant is chosen because no validity rule needs an exclusion proof and the ordered variant costs a next-key link per active key. State keys are 32-byte digests so that structured inputs spread across translated prefixes; finite-prefix collisions remain and full keys are always compared. Growth is priced and rate-limited rather than capped or swept, which keeps block execution independent of expiry cohorts; a deposit rewards physical deletion while a creation fee charges for churn and permanent state. Committing the floor in the root is what lets roles differ only in retained bytes. The accumulator exists because Simplex finalizes ancestors transitively and bounds neither how many heights that covers nor a header-chain fallback.