Commit
commit is the verb every other one is defined against. This chapter follows a
single call from the caller's .await to the receipt: what is true when it
resolves, how a retry avoids a second entry, and every point in the writer
pipeline where it can fail. You leave understanding why a watcher can see your
change before you do.
The Call
Section titled “The Call”let handle = runtime.journal.as_principal(alice());
let receipt = handle .commit(MoveSymbol { symbol: R1, to: Point::new(10, 45), }) .await?;When that future resolves Ok, three things are true: your transaction was
checked against the current state, applied to it, and sealed into
history at a specific position. The ? catches the fourth possibility: your
own check refused, and you got back your own typed error —
Error::Rejected(SchematicError::UnknownSymbol(R1)), not a framework code.
Rejection is a first-class answer, and after it, nothing happened at all.
handle is an ordinary Journal clone with one thing added: the principal the
writer will witness on every entry it sends. commit itself is on every
handle, scoped or not.
What comes back:
pub struct Receipt { /// Where the entry landed in the one total order. pub position: Position, /// The family the entry belongs to. pub correlation: CorrelationId,}position is the permanent serial number of your change in the one total
order — the same Position that identifies this entry forever, which is
chapter 2's naming lesson now cashed in.
correlation ties this entry to the family of entries it may cause
(chapter 5 uses it for tracing).
Records travel the same road minus the behavior:
journal.record(ExportCompleted { .. }).await? — no check, no apply, but a
real position and real metadata, because facts live in the same history as
changes.
Retries Without Duplicates: RequestKey
Section titled “Retries Without Duplicates: RequestKey”Here's a failure that will happen: the commit future gets dropped — a timeout, a UI hiccup, a network client giving up — but the writer had already applied the change. The caller doesn't know. It retries. Without protection, the schematic now has the symbol moved twice, or two entries where one belongs.
The fix is a caller-supplied idempotency key:
handle .commit_keyed(RequestKey::new("place-r1"), PlaceSymbol { .. }) .await?If the writer has seen that RequestKey before, it doesn't re-run anything — it
returns the original receipt. Retry as many times as you like; history
gets exactly one entry. On a desktop this matters at the margins; in a
server-shaped deployment, where commits arrive over HTTP, it's the difference
between "retry-safe API" and "double-charged invoice."
Under the Hood: The Writer Pipeline, Every Failure Point Named
Section titled “Under the Hood: The Writer Pipeline, Every Failure Point Named”Your commit call does very little locally: it packages the transaction with
a oneshot reply channel (a single-use return envelope) and sends it into the
bounded mpsc channel from chapter 3. Then it waits
for the envelope to come back. Everything else happens inside the writer task,
one request at a time:
send ─▶ dedupe ─▶ mint metadata ─▶ check ─▶ (inverse) ─▶ apply ─▶ seal ─▶ publish ─▶ reply A B C D- A — the channel is full. Not an error: your
.awaitsimply waits. This is backpressure — the system's honest way of saying "the writer is busy." Memory doesn't balloon; callers slow down instead. - A′ — the channel is closed. The writer is gone (shutdown, or poisoned — chapter 9). Commit fails with a runtime-down error. This is the only infrastructure failure a committer sees.
- B — dedupe. The
RequestKeyindex is consulted. Seen before → reply with the stored receipt, pipeline over. - Metadata minting. The writer stamps what it witnessed:
principal(from the handle's scope),correlation,causation,request,link, andat— read from the one runtime clock. You never constructMetadata; it is#[non_exhaustive]and cannot be built outside harmos — and that's a principle, not a convenience: metadata is what the runtime witnesses; the payload is what the application declares. A caller-supplied timestamp or principal would be a claim, not an observation, and history built on claims can't be trusted for undo, audit, or replay. - C —
check(&state). Refusal replies with your typed error. State untouched, no entry, no trace — a rejected transaction never existed. - Inverse capture — only for undoable transactions (chapter 7) — reads the pre-state.
- D —
apply(&mut state). Infallible by contract. A panic here is the one catastrophe: poison, evacuation, death, which chapter 9 gives its own chapter. What never happens: a half-applied state becoming visible. - Seal. The very value that just applied is sealed into an
Entryat the nextPosition— apply-then-seal is why a panicking transaction can never contaminate history. - Publish. The entry joins the tail, the
appliedwatch channel ticks, every observer's doorbell rings (chapter 5). - Reply. Your envelope comes back:
Ok(Receipt).
One subtlety worth writing down: publish happens before reply. A watcher
can see your change a microsecond before your own commit future resolves.
The receipt isn't "the moment it happened" — it's your copy of the
confirmation. History doesn't wait for you to read your mail.
And ordering? Determined entirely by arrival at the channel. Two tasks committing concurrently race to the Sender; whoever's request is accepted first gets the earlier position. There is no other ordering machinery — the queue is the order.
Test Your Knowledge
Section titled “Test Your Knowledge”
1. A network client commits PlaceSymbol with a RequestKey, times out, retries, and gets a receipt with position: 1041. Meanwhile a watcher saw the placement happen at position 1041 twenty seconds ago. Reconstruct exactly what happened, in order.
PlaceSymbol with a RequestKey, times out, retries, and gets a receipt with position: 1041. Meanwhile a watcher saw the placement happen at position 1041 twenty seconds ago. Reconstruct exactly what happened, in order.The first attempt arrived and ran the whole pipeline: dedupe found nothing,
metadata was minted, check passed, apply mutated state, the entry was
sealed at 1041, and publish rang the watcher's doorbell. Then the reply
envelope came back to a caller that had already timed out and dropped it.
The retry carried the same RequestKey. The writer's dedupe index
recognized it, ran nothing, appended nothing, and replied with the stored
original receipt for 1041.
Nothing ran twice. The twenty-second gap is just the retry delay. The watcher and the committer disagree about when only because a receipt is the caller's copy of the confirmation, not the event itself.
2. Make the case against letting a trusted client supply its own at timestamp in metadata. “Trusted” is doing work in that sentence, so the argument can't be “clients lie.”
at timestamp in metadata. “Trusted” is doing work in that sentence, so the argument can't be “clients lie.”Even a perfectly honest clock is a different clock. Client timestamps break the single witness, and the damage is structural rather than moral.
Two entries adjacent in the total order could carry timestamps that run
backwards: clock skew between machines, a timezone bug, a laptop waking
from sleep. at would no longer be monotone with Position, so every
consumer that sorts, windows, or bucketizes by time would silently
disagree with history's real order.
The runtime's clock isn't more accurate. It is one clock, read at the one place where order is already decided. Witness beats claim even when the claimant is honest.
3. Task A and task B both commit; A's future resolves with position 500, B's with 501. Your teammate concludes “A's commit was called before B's.” True, or not necessarily?
commit was called before B's.” True, or not necessarily?Not necessarily. Arrival at the channel decides order, and two tasks racing
the Sender can arrive in either order regardless of who called commit
first — task scheduling, await points, and channel capacity all interleave
between the call and the send.
The only sentence the receipts license: "A's request entered the channel before B's, therefore A applied first and observers saw A's change first."
Receipts order history, not call sites.