Protocol Specification v0.6

Cross-chain Protocol

A cross-domain operation retains one original event (opens in a new tab) decision through message retries, endpoint recovery and secondary settlement. A missing reply cannot turn an unresolved source debit into a refund. The source obligation and secondary propagation are separate state machines with separate evidence.

Secondary settlement

A published decision opens at most one job per key; retries keep the key and only an exact acknowledgement completes it.

A published decision opens at most one job per key; retries keep the key and only an exact acknowledgement completes it. The primary Publication of an origin holds the origin and its decision, and its key is the SettlementKey of the origin and the decision's commit position; the secondary queue keys its job by that same SettlementKey. enqueue opens one Waiting job at that key, and only for a published decision. dispatch adds an attempt and moves the job to InFlight, and from InFlight it is another attempt under the same key; fail returns an in-flight job to Waiting. acknowledge completes the job only when key, commitment and target match, at least one attempt was made and the signature verifies, from InFlight or from Waiting after a failed attempt. No secondary step reaches back to change the primary decision. Primary Publication origin, decision key() SettlementKey origin, commit 4 enqueue [published] 1 Secondary queue one job per SettlementKey Waiting InFlight Completed dispatch [attempts + 1] 2 fail dispatch acknowledge [key, commitment and target match] 3 acknowledge [same match, after an attempt]
  1. 1

    Published decision

    enqueue_secondary hands the queue's enqueue only the origin's primary Publication, so a committed decision that is not yet published opens no job. The key is the origin and the decision's commit position.

  2. 2

    One job, many attempts

    dispatch adds one attempt in any state except Completed, so from InFlight it is another attempt under the same key. fail returns an in-flight job to Waiting, and a second enqueue keeps the existing job.

  3. 3

    Exact acknowledgement

    acknowledge needs the job's key, commitment and target, at least one attempt and a verified signature. A wrong commitment under the right key leaves the job InFlight.

  4. 4

    Primary untouched

    A secondary failure or retry cannot change the primary decision, re-open pending source mass or issue a refund. fail leaves the job Waiting with the same Publication.

  • Start of the job
  • Normal path of one job
  • Another attempt under the same key
  • The key every attempt keeps
  • Final state
  • No step reaches back to the primary

Schematic: one origin and one target endpoint. Labels name the queue's own steps, which callers reach through enqueue_secondary, dispatch_secondary, fail_secondary and acknowledge_secondary.

Figure 1: The secondary settlement job for one published decision

Call identity and completion#

NormativeBytes, test vectors and proofs

  • An authority MUST bind a call identifier (opens in a new tab) to the authenticated caller namespace and the entire admitted request, including its operation and any lock credential. The transport message identifier and the original event key have separate meanings.
  • The authority MUST record the accepted binding, reply and effect in one durable commit. Authenticated calls that complete with rejection, busy or unavailable MUST record that outcome through the same authority.
  • Caller authentication, endpoint capability and complete signed-request verification are checked on every submission; their failure is refused without recording an outcome or consuming a fresh call identifier.
  • When the authority endpoint is up, an existing call identifier is handled through the retained-result path, including the caller's current result-access authority, before fresh-call checks.
  • For a fresh submission, pre-execution proof authentication, any required recovery endorsement, and consistency between the supplied expected cut and the signed request are checked before recording a new outcome; their failure leaves the call identifier unconsumed.
  • For a fresh admitted ordinary call, an unavailable authority source records Unavailable before staleness is considered. If that source is available, a signed cut (opens in a new tab) that no longer equals the current cut records Busy. A command-specific grant refusal reached by the authoritative ordinary consumer records rejection.
  • A recorded Unavailable outcome is distinct from a transport or storage error that leaves the authoritative outcome undetermined. A client that cannot reach the authority can wait and reconcile, since transport silence cannot establish a completed outcome.
  • A retry of the exact admitted call MUST return the existing outcome without executing another effect. Reusing the identifier with different content MUST be rejected. Collection of the existing result still requires the caller's current result-access authority.
  • Completion MUST follow the durable authoritative result, the primary publication when the operation requires one, and any release the operation requires. A debit that remains pending retains its obligation and its lock conditions.

Submission order#

The authority endpoint authorizes the caller on every submission. While the endpoint is down it returns EndpointError::Unavailable and records nothing, and a known call identifier goes to its retained result. A fresh call then reaches the cut and proof checks, and for an ordinary call an unavailable authority source is recorded as Unavailable before a stale cut is recorded as Busy.

RustDurable submissionsrc/authority/identity.rsL746-L803Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
pub(super) fn execute_durable(    &mut self,    auth: &Credentials<'_, S>,    request: Request<'_>,    endorsement: Option<&Credentials<'_, S>>,) -> Result<ActionReply, EndpointError> {    let operation = request.action.claim().operation;    let identity = self.authorize_call(auth, &request, operation)?;    let actor = AuthenticatedCaller(identity.principal);    let authority = &self.reads.authority;    if !authority.up {        return Err(EndpointError::Unavailable);    }    let primary = &authority.state.primary;    if primary.calls.contains_key(&request.id) || primary.regulation.claimed(request.id) {        if !self.security.permit(            identity,            request.target,            Operation::Receipt,            Scope::Asset(request.action.claim().asset),            auth.proof.epoch,        ) {            return Err(EndpointError::Rejected);        }        return self.finish_retained(request);    }    let signed_cut = match request.action {        Action::Ordinary(payload) => payload.cut,        Action::Legal { payload, .. } => payload.cut,        Action::Spend { payload, .. } => payload.cut,    };    if request.expected != signed_cut {        return Err(EndpointError::Rejected);    }    self.verify_facts(identity, auth.proof, &request, operation, auth.signature)?;    if let Action::Ordinary(payload) = request.action {        self.endorse_recovery(endorsement, &request, payload)?;        let refusal = if !self.reads.available() {            Some(super::OutcomeReply::Unavailable)        } else if signed_cut != self.reads.current_cut() {            Some(super::OutcomeReply::Busy)        } else {            None        };        if let Some(refusal) = refusal {            self.reads.authority.state.primary.calls.insert(                request.id,                super::StoredCall {                    payload: payload.clone(),                    outcome: super::Outcome::plain(refusal),                },            );            return self.finish_retained(request);        }    }    self.execute_bound(actor, request)}

Exact retries#

A stored call matches a retry only when the whole payload is equal, and it is collected only when its effect is ready.

RustRetained call lookupsrc/authority.rsL686-L698Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn retained_completion(&self, id: CallId, payload: &CallPayload) -> (Collection, bool) {    let view = self.state.primary.calls.get(&id).map(|stored| {        let matches = stored.payload == *payload;        (matches, matches && self.state.primary.ready(stored), stored.outcome)    });    let (reply, complete) = retained_completion::retained_completion(view);    let reply = match reply {        retained_completion::Reply::Waiting => Collection::Waiting,        retained_completion::Reply::Rejected => Collection::Rejected,        retained_completion::Reply::Collected(outcome) => Collection::Collected(outcome),    };    (reply, complete)}

The retained decision is one function. When no call is stored, or the stored call matches but its effect is not ready, the reply is waiting; a different payload is rejected, and a ready match is collected.

RustRetained completionsrc/authority/retained_completion.rsL19-L30Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
pub(crate) fn retained_completion<T: Copy>(stored: Option<(bool, bool, T)>) -> (Reply<T>, bool) {    let Some((matches, ready, outcome)) = stored else {        return (Reply::Waiting, false);    };    if !matches {        (Reply::Rejected, false)    } else if ready {        (Reply::Collected(outcome), true)    } else {        (Reply::Waiting, false)    }}

The vectors retry one call after recovery, with another message, at another entry, and after Unavailable and Busy outcomes.

TextCall identity vectorstests/vectors/authoritative-outcomes.tsvL2-L7Source (opens the lines on GitHub in a new tab)

Columnsidoperationinputsexpectedevidence

first-authority-result	first-dispatch	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17	reply=source_dispatched;authority_effects=1	authority_stores_the_bound_source_dispatch_resultexact-retry-after-recovery	restart-retry	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17	reply=source_dispatched;authority_effects=1	exact_recovery_preserves_reply_and_execution_history,exact_retry_reads_the_committed_outcome,authoritative_call_retry_does_not_repeat_effectsame-id-different-message	conflicting-call	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;retry_message=2	reply=rejected;authority_effects=1	conflicting_payload_is_observably_rejected,authoritative_call_retry_does_not_repeat_effectdenial-survives-entry-change	denied-retry	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;first_route=administrative;retry_route=normal	reply=rejected;authority_effects=0	a_same_call_retry_at_an_allowed_entry_cannot_replace_the_denial,loss_of_the_stored_denial_allows_a_later_effectunavailable-result-survives-reconnect	unavailable-retry	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17	reply=unavailable;authority_effects=0	an_unavailable_unissued_call_records_an_outcome_without_execution,authoritative_call_retry_does_not_repeat_effectbusy-result-survives-retry	busy-retry	caller=7;namespace=7;sequence=1;message=1;session=1;epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;first_asset=17;conflicting_asset=30;conflicting_event=30	reply=busy;new_reservations=0	first_connected_call_records_its_public_result,opposite_order_pair_cannot_take_a_partial_lock,authoritative_call_keeps_older_results

Completion#

A final credit or refund is ready only when the primary publication exists and the asset's lock no longer belongs to its origin. A publication exists only after the decision is settled and published.

RustCompletion readinesssrc/authority.rsL379-L401Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn ready(&self, stored: &StoredCall) -> bool {    let final_effect = matches!(        stored.outcome.reply,        OutcomeReply::Accepted(Reply::Credited | Reply::Refunded)            | OutcomeReply::DuplicateSettlement(_)    );    if !final_effect {        return true;    }    let binding = stored.payload.binding;    self.publication(binding.origin).is_some()        && self.ledger.lock_owner(binding.asset) != Some(binding.origin)}fn publication(&self, origin: OriginKey) -> Option<Publication> {    let slot = self.origins.get(&origin)?;    slot.published?;    slot.settled?;    Some(Publication {        origin,        decision: slot.decision?,    })}

The completion vectors keep the credit reply waiting while the lock is held or the publication is missing.

TextCompletion vectorstests/vectors/authoritative-outcomes.tsvL8-L10Source (opens the lines on GitHub in a new tab)

Columnsidoperationinputsexpectedevidence

credit-reply-waits-for-lock-release	completion	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;published=true;released=false	reply=waiting	final_effect_cannot_complete_while_its_lock_is_heldcredit-reply-waits-for-publication	completion	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;published=false;released=true	reply=waiting	final_effect_cannot_complete_before_primary_publicationcredit-reply-after-publication-and-release	completion	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;published=true;released=true	reply=credited;completions=1	completed_result_has_a_committed_exact_binding,completion_is_idempotent

Original event consumption#

NormativeBytes, test vectors and proofs

  • Every terminal entry point MUST use one authoritative decision slot for the original event key, independent of the transport message or route. The slot binds the source event, asset, amount, destination, recipient and origin authority epoch (opens in a new tab).
  • An identical binding and decision MUST be recognized as a duplicate. A conflicting binding or decision MUST be rejected before any new financial effect. Updating admissible supporting evidence preserves the committed binding and decision.
  • An event has at most one settlement effect. A confirmed decision and a reversed decision for the same original event cannot both take effect.

One origin slot per original event key holds the binding, the origin epoch, the debit commit and at most one decision. A call matches the slot only with the same binding and origin epoch.

RustOrigin slotsrc/authority.rsL126-L144Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]struct OriginSlot {    binding: Binding,    epoch: u64,    debit: CommitRef,    decision: Option<OriginDecision>,    settled: Option<CommitRef>,    published: Option<CommitRef>,}impl OriginSlot {    fn matches(self, payload: &CallPayload) -> bool {        self.binding == payload.binding && self.epoch == payload.origin_epoch    }    fn matches_decision(self, payload: &CallPayload, kind: TerminalKind) -> bool {        self.matches(payload) && self.decision.is_some_and(|decision| decision.kind == kind)    }}

Origin commands are checked against that slot. A Debit must match an existing slot or, without one, carry the current authority epoch; a terminal record must keep any recorded decision kind; a credit or reconciliation needs a confirmed decision and a refund a reversed one; and publication needs a settled slot. Other commands pass this check.

RustOrigin admission by commandsrc/authority.rsL403-L430Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn origin_allowed(&self, payload: &CallPayload) -> bool {    let slot = self.origins.get(&payload.binding.origin).copied();    match payload.command {        Command::Debit { .. } => slot            .map_or(payload.origin_epoch == payload.cut.authority_epoch, |old| {                old.matches(payload)            }),        Command::RecordTerminal(evidence) => {            evidence.binding == payload.binding                && slot.is_some_and(|old| {                    old.matches(payload)                        && old                            .decision                            .is_none_or(|decision| decision.kind == evidence.kind)                })        }        Command::Credit { .. } | Command::Reconcile { .. } => {            slot.is_some_and(|old| old.matches_decision(payload, TerminalKind::Confirmed))        }        Command::Refund { .. } => {            slot.is_some_and(|old| old.matches_decision(payload, TerminalKind::Reversed))        }        Command::PublishPrimary => {            slot.is_some_and(|old| old.matches(payload) && old.settled.is_some())        }        _ => true,    }}

Execution rejects a disallowed origin, and a Reserve, Dispatch or Debit that regulation blocks, before any effect. An identical retry returns its committed duplicate only when the caller's receipt credential matches.

RustRejection and duplicates before effectsrc/authority.rsL468-L503Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn execute(    &mut self,    route: Route,    actor: AuthenticatedCaller,    payload: &CallPayload,) -> Outcome {    let request = payload.request();    if !self.ledger.request_admitted(route, actor, request) {        return Outcome::plain(OutcomeReply::Rejected);    }    if matches!(payload.command, Command::PublishPrimary)        && self.regulation.contains_origin(payload.binding.origin)    {        return self.publish_legal(payload);    }    if !self.origin_allowed(payload)        || (matches!(            payload.command,            Command::Reserve { .. } | Command::Debit { .. }        ) && self.regulation.contains_origin(payload.binding.origin))        || (matches!(            payload.command,            Command::Dispatch { .. } | Command::Debit { .. }        ) && self            .regulation            .protected(payload.binding.origin.domain, payload.binding.asset))    {        return Outcome::plain(OutcomeReply::Rejected);    }    if let Some(duplicate) = self.duplicate(payload) {        return if self.ledger.receipt_credential_matches(actor, request) {            duplicate        } else {            Outcome::plain(OutcomeReply::Rejected)        };    }

The vectors change the origin epoch, the decision kind and the route of one event. The last row shows that two separate authorities would credit the same event twice.

TextOrigin vectorstests/vectors/authoritative-outcomes.tsvL11-L14Source (opens the lines on GitHub in a new tab)

Columnsidoperationinputsexpectedevidence

same-origin-different-epoch	conflicting-origin-epoch	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;retry_origin_epoch=10	reply=rejected;new_credits=0	same_event_with_a_different_origin_epoch_cannot_change_public_statesame-origin-opposite-decision	conflicting-terminal-kind	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;first_kind=confirmed;retry_kind=reversed	decision=confirmed;refunds=0	one_origin_slot_cannot_confirm_and_reverse,any_origin_entry_keeps_a_committed_decision,every_exact_product_trace_settles_each_origin_at_most_oncesame-origin-different-route-and-message	reroute-original-credit	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;first_route=normal;retry_route=adapter;first_message=1;retry_message=2	credits=1;destination=5	product_confirmed_consumer_credits_at_the_bound_entry,every_exact_product_trace_settles_each_origin_at_most_onceindependent-authority-removal	split-authority-control	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;first_route=normal;second_route=adapter	shared_new_credits=1;isolated_new_credits=2	splitting_the_authority_allows_two_new_credit_results,every_exact_product_trace_settles_each_origin_at_most_once

Primary publication and secondary settlement#

NormativeBytes, test vectors and proofs

  • A committed primary decision MUST remain immutable. Its publishable observation MUST refer to the exact committed decision and its completed financial effect.
  • Secondary settlement MUST use a separate queue, keyed by the same route-independent settlement identity on every retry. That identity is derived from the immutable primary commitment and its original event.
  • Acknowledgements MUST match the exact queue entry and primary commitment. A wrong acknowledgement cannot complete the entry. Secondary failure or retry cannot change the primary decision, re-open pending source mass, or issue a refund.

The settlement key is derived from the published decision, as its original event and its commit position.

RustPublication and settlement keysrc/authority.rsL146-L167Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]pub struct Publication {    pub origin: OriginKey,    pub decision: OriginDecision,}impl Publication {    pub fn key(self) -> SettlementKey {        SettlementKey {            origin: self.origin,            commit: self.decision.commit,        }    }}#[derive(    Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize,)]pub struct SettlementKey {    pub origin: OriginKey,    pub commit: CommitRef,}

Only a published primary decision opens a secondary job.

RustSecondary enqueuesrc/authority.rsL821-L828Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
pub fn primary_publication(&self, origin: OriginKey) -> Option<Publication> {    self.state.primary.publication(origin)}pub fn enqueue_secondary(&mut self, origin: OriginKey) -> Option<SettlementKey> {    let publication = self.primary_publication(origin)?;    Some(self.state.secondary.enqueue(publication))}

The queue keeps one job per key. A dispatch counts an attempt, and a failure returns the job to waiting under the same key.

RustSecondary queuesrc/authority.rsL219-L249Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn enqueue(&mut self, commitment: Publication) -> SettlementKey {    let key = commitment.key();    self.jobs.entry(key).or_insert(SecondaryJob {        commitment,        target: self.target,        attempts: 0,        status: SettlementStatus::Waiting,    });    key}fn dispatch(&mut self, key: SettlementKey) -> Option<SecondaryJob> {    let job = self.jobs.get_mut(&key)?;    if job.status == SettlementStatus::Completed {        return None;    }    job.attempts = job.attempts.checked_add(1)?;    job.status = SettlementStatus::InFlight;    Some(*job)}fn fail(&mut self, key: SettlementKey) -> bool {    let Some(job) = self.jobs.get_mut(&key) else {        return false;    };    if job.status != SettlementStatus::InFlight {        return false;    }    job.status = SettlementStatus::Waiting;    true}

An acknowledgement completes the job only when its key, commitment and target all match a job that has been dispatched, and its signature verifies.

RustExact acknowledgementsrc/authority.rsL251-L270Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn acknowledge(    &mut self,    ack: Acknowledgement,    verify: impl FnOnce(&Acknowledgement) -> bool,) -> bool {    let Some(job) = self.jobs.get_mut(&ack.key) else {        return false;    };    if ack.key != job.commitment.key()        || ack.commitment != job.commitment        || ack.target != job.target        || ack.target != self.target        || job.attempts == 0        || !verify(&ack)    {        return false;    }    job.status = SettlementStatus::Completed;    true}

The queue vectors try an early enqueue, a wrong acknowledgement, a retry and a failed secondary step.

TextSecondary settlement vectorstests/vectors/authoritative-outcomes.tsvL15-L18Source (opens the lines on GitHub in a new tab)

Columnsidoperationinputsexpectedevidence

no-queue-before-primary-publish	early-enqueue	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;target=99;primary_position=0	jobs=0	a_committed_but_unpublished_terminal_cannot_enqueueright-key-wrong-commitment-ack	wrong-ack	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;target=99;primary_position=0;ack_position=1	status=in_flight;primary_unchanged=true	wrong_commitment_with_the_right_key_is_rejected,removing_publication_equality_accepts_the_wrong_commitmentmatching-ack-after-retry	retry-ack	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;target=99;primary_position=0;ack_position=0	attempts=2;status=completed;primary_unchanged=true	matching_ack_after_a_retry_completes_without_changing_primary,retry_keeps_the_exact_publication_under_the_same_keysecondary-failure-keeps-primary	secondary-fail	caller=7;session=1;authority_epoch=9;origin_epoch=9;source_domain=0;event=17;asset=17;destination_domain=2;recipient=3;initial=10;amount=5;target=99;primary_position=0	status=waiting;primary_unchanged=true;refunds=0	failed_secondary_step_cannot_change_pending_source_mass,every_product_continuation_keeps_the_same_primary_decision

Pending source obligation#

NormativeBytes, test vectors and proofs

A source debit moves the bound amount to pending mass. Exact confirmed source evidence may credit the destination once; exact reversal evidence may return the source once. A deadline expiry, missing acknowledgement or driver failure cannot supply reversal evidence or release pending mass. State Management gives the lock and evidence conditions.

An acknowledgement binds the complete original event, exact primary commitment and queue entry. A changed target, commit or settlement identity cannot close that entry. A publication retry retains the already committed primary outcome; it cannot reopen source mass.

The settlement acknowledgement carries the whole original event and the published decision. Its target must be the sender of the frame.

JSONSettlementAcknowledgement entryspec/operations.jsonL750-L775Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
{  "family": 5, "opcode": 1, "name": "SettlementAcknowledgement", "title": "exact settlement acknowledgement",  "summary": "Exact settlement acknowledgement from the secondary target.",  "reference_accepts": true,  "words": [    {"kind": "word", "name": "origin_domain", "type": "domain_id"},    {"kind": "word", "name": "origin_event", "type": "event_id"},    {"kind": "word", "name": "commit_position", "type": "commit_position", "description": "Commit position of the published decision."},    {"kind": "word", "name": "target", "type": "domain_id", "description": "Secondary target that acknowledges the settlement."},    {"kind": "word", "name": "asset", "type": "asset_id"},    {"kind": "word", "name": "owner", "type": "principal_id"},    {"kind": "word", "name": "destination", "type": "domain_id"},    {"kind": "word", "name": "recipient", "type": "principal_id"},    {"kind": "word", "name": "origin_epoch", "type": "epoch"},    {"kind": "word", "name": "decision_kind", "type": "code", "enum": "decision_kind", "accepted": "all"}  ],  "blobs": [    {"name": "amount", "type": "amount_u256"}  ],  "deadline": "absent",  "checks": [    {"when": "consumer", "rule": "target equals the Core source_domain.", "error": "INVALID_SOURCE"}  ],  "call": false,  "reference_note": "The reference accepts it only from the configured acknowledger principal of the remote domain."},

A retried decision, settlement or publication returns the outcome already committed in the origin slot, at its original commit position.

RustCommitted duplicatessrc/authority.rsL432-L466Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn duplicate(&self, payload: &CallPayload) -> Option<Outcome> {    let slot = self.origins.get(&payload.binding.origin)?;    match payload.command {        Command::RecordTerminal(evidence)            if self.ledger.evidence(evidence.reference) == Some(evidence) =>        {            Some(Outcome::committed(                OutcomeReply::DuplicateDecision,                slot.decision?.commit,            ))        }        Command::Credit { reference } | Command::Refund { reference, .. } => {            let kind = slot.decision?.kind;            let commit = slot.settled?;            if self.ledger.evidence(reference)                != Some(TerminalEvidence {                    binding: payload.binding,                    kind,                    reference,                })            {                return None;            }            Some(Outcome::committed(                OutcomeReply::DuplicateSettlement(kind),                commit,            ))        }        Command::PublishPrimary => Some(Outcome::committed(            OutcomeReply::Accepted(Reply::Published),            slot.published?,        )),        _ => None,    }}

Ordered acknowledgement and read batches#

NormativeBytes, test vectors and proofs

The ordered schedule accepts canonical v0.6 settlement acknowledgements and current reads up to the host's configured occurrence and swap limits. Each occurrence supplies its own signed frame, negotiated channel and credentials. Labels identify replies. Settlement slots, observed queries and transport UUID keys come from the decoded input.

An unsupported shape or invalid occurrence produces a refusal in its original label without blocking other valid occurrences in the batch.

A proposed exchange requires successful acknowledgements or current observations, independent settlement slots or observations, and distinct transport keys. Shared slots and shared UUID keys are evaluated in their original relative order.

For each accepted exchange, both orders MUST produce the same retained image and the same response at each original label. Supported acknowledgements and reads preserve the signed product cut.

All successful outcomes are rechecked against current identity, wire signature and product authorization after preparation. A changing batch publishes one flushed record with one exact authority CAS. A batch with no image change reads back the exact observed authority and head.

A change to the authority before confirmation withholds the prepared results. Ordered delivery TTL (opens in a new tab) limits admission (opens in a new tab) and does not become a second clock-based condition at publication.

Shapes and classes#

An occurrence has a shape only when it decodes to a settlement acknowledgement or a current read. Its class is the settlement slot or the observed query, and its transport key comes from the decoded Core.

RustOccurrence shapesrc/authority/recovery/wire/schedule.rsL72-L98Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn shape<S: WireSecurity>(    occurrence: &WireOccurrence<'_, S>,    security: &S,) -> Result<Candidate, WireError> {    if occurrence.channel.profile().version != 6 {        return Err(WireError::UnsupportedVersion);    }    let frame_limit = security.frame_limit(occurrence.channel);    check_declared_version(occurrence.bytes, frame_limit)?;    let packet = decode_packet(occurrence.bytes, frame_limit)?;    let core = packet.core();    if !security.accepts_operation(occurrence.channel, core) {        return Err(WireError::UnsupportedVersion);    }    let class = match decode(core, 1)? {        Operands::Ack(ack) => slot(ack.key),        Operands::Read(read @ ReadOperand::Current { .. }) => {            let epoch = core.authority_epoch.ok_or(WireError::InvalidFormat)?;            Class::observation(query_origin(read_at(read, epoch)))        }        _ => return Err(WireError::InvalidFormat),    };    Ok(Candidate {        class,        transport: TransportKey::for_core(core)?,    })}

Two classes can be exchanged only when neither writes what the other reads or writes.

RustFootprints and compatibilitysrc/authority/recovery/schedule.rsL150-L183Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
fn footprint(self) -> (BTreeSet<Dependency>, BTreeSet<Dependency>) {    let common = [        Dependency::Authority,        Dependency::Revision,        Dependency::CompletionHistory,        Dependency::ReadProvider,    ];    match self {        Self::Observation {            origin,            publication,        } => {            let mut reads = BTreeSet::from(common);            reads.extend(origin.map(Dependency::Origin));            reads.extend(publication.map(Dependency::Publication));            (reads, BTreeSet::new())        }        Self::Slot { key, publication } => {            let slot = Dependency::Settlement(key);            let mut reads = BTreeSet::from([Dependency::Authority, slot]);            reads.extend(publication.map(Dependency::Publication));            (reads, BTreeSet::from([slot]))        }        Self::Serial => (BTreeSet::from(common), BTreeSet::from(common)),    }}pub(super) fn compatible(self, other: Self) -> bool {    let (a_read, a_write) = self.footprint();    let (b_read, b_write) = other.footprint();    a_write.is_disjoint(&b_read)        && b_write.is_disjoint(&a_read)        && a_write.is_disjoint(&b_write)}

These vectors exercise the service-action scheduler, which uses the same class footprints. Three independent pairs are exchanged, and three pairs on one settlement slot stay in input order.

TextService scheduler vectorstests/vectors/parallel-composition.tsvL2-L7Source (opens the lines on GitHub in a new tab)

Columnsidoperationinputsexpectedevidence

independent-actual-dispatches	pair	operation_a=dispatch;operation_b=dispatch;origin_a=17;origin_b=22;commit_a=0;commit_b=1;attempt_a=0;attempt_b=0;request_exchange=true	schedule=exchange;reply_a_attempt=1;reply_b_attempt=1;retained_state_equal=true	different_actual_settlement_slots_exchange,compatible_actual_product_actions_exchange,different_exact_jobs_can_exchange_under_the_same_authoritypublication-derived-enqueue-and-other-dispatch	pair	operation_a=enqueue;operation_b=dispatch;origin_a=17;origin_b=22;commit_a=0;commit_b=1;publication_a=committed;job_a=absent;job_b=waiting;request_exchange=true	schedule=exchange;enqueue_key_a=17;reply_b_attempt=1;retained_state_equal=true	enqueue_reads_publication_even_when_no_job_exists,exact_queue_effect_is_the_existing_endpoint_effect,compatible_actual_product_actions_exchangeactual-credit-read-and-queue-write	pair	operation_a=raw_read;operation_b=dispatch;origin_b=17;epoch=9;domain=2;asset=17;recipient=3;amount=5;request_exchange=true	schedule=exchange;reply_a=5;reply_b_attempt=1;retained_state_equal=true	authorized_raw_observation_of_the_published_credit_is_five,exact_parallel_job_dispatch_is_nonvacuous,compatible_actual_product_actions_exchangesame-slot-enqueue-dispatch-conflict	pair	operation_a=enqueue;operation_b=dispatch;origin_a=17;origin_b=17;commit_a=0;commit_b=0;job_a=absent;request_exchange=true	schedule=serial;reply_b_attempt=1;unsafe_reverse_reply_b=none	same_slot_dispatch_and_enqueue_change_the_reply_when_reordered,conflicting_pair_is_revalidated_in_input_ordersame-slot-labeled-attempts	pair	operation_a=dispatch;operation_b=dispatch;origin_a=17;origin_b=17;commit_a=0;commit_b=0;attempt_a=0;request_exchange=true	schedule=serial;reply_a_attempt=1;reply_b_attempt=2;unsafe_reverse_changes_replies=true	two_dispatches_of_one_job_return_different_attempts_to_the_occurrences,compatible_settlement_writes_use_distinct_exact_slotssame-slot-failure-conflict	pair	operation_a=dispatch;operation_b=fail;origin_a=17;origin_b=17;commit_a=0;commit_b=0;request_exchange=true	schedule=serial	same_slot_actions_cannot_exchange_even_with_unchanged_authority,conflicting_pair_is_revalidated_in_input_order

Exchanges#

Each proposed swap reruns the batch from the initial image. A different reply or a different image holds the whole batch.

RustAdjacent exchangesrc/authority/recovery/wire/schedule.rsL225-L268Highlighted lines are discussed in the textSource (opens the lines on GitHub in a new tab)
let initial = RetainedImage::capture(product);let mut order: Vec<_> = (0..occurrences.len()).collect();let mut selected = run_order(    product,    occurrences,    &order,    &candidates,    profile.deployment,    input,)?;let baseline_image = RetainedImage::capture(product);let mut exchanges = vec![Exchange::Serialized; adjacent.len()];for (attempt, &index) in adjacent.iter().enumerate() {    let (left, right) = (order[index], order[index + 1]);    let (Ok(a), Ok(b)) = (candidates[left], candidates[right]) else {        continue;    };    if !exchangeable(selected.replies[left])        || !exchangeable(selected.replies[right])        || a.transport == b.transport        || !a.class.compatible(b.class)    {        continue;    }    order.swap(index, index + 1);    let mut alternate =        initial.clone().open(profile, security.clone(), connected);    let exchanged = run_order(        &mut alternate,        occurrences,        &order,        &candidates,        profile.deployment,        input,    )?;    if selected.replies != exchanged.replies        || baseline_image != RetainedImage::capture(&alternate)    {        return Err(hold());    }    *product = alternate;    selected = exchanged;    exchanges[attempt] = Exchange::Exchanged;}

Coordination design context#

DesignCarried from v0.5, without bytes or test vectors in this version

The PREPARE, COMMIT and FINALIZE vocabulary in v0.5 describes architecture-level coordination. This reference edition exposes concrete lock, debit, terminal evidence, publication, receipt and acknowledgement operations. It does not serialize that entire design vocabulary as a new network coordination service. Read the actual family (opens in a new tab)/opcode layouts when building an interoperable participant.

An uncertain acknowledgement#

After primary publication, the secondary sender can retry the stable settlement key. If its response is lost, it dispatches the same entry again rather than allocating another key. Even if the secondary service stays unavailable, the immutable primary remains committed and the original debit cannot be refunded from that uncertainty alone.


Edition history#

  • 0.6.0: Added this edition. The v0.5 specification remains available at its original addresses.