Pipeline (L2)
1. Undo/redo history¶
Undo/redo is authority-server-logged, client-navigated
(DESIGN-L2-undo-redo-synced-history, DESIGN-L2-undo-redo-revlog-contract).
Two owners, and the runtime tier is neither:
- The authority server durably owns the
RevLog— the authoritative record of applied invertible change-diffs (the domainrev_logmodel plus theRevLogStoreports and store implementation). A confirmed reversible forward mutation appends its rev-log step, and the log with its cursor is re-served as a synced view; cursor moves arrive as therevCursorcontrol mutation, which the authority server validates and applies. - The client owns the undo/redo stack as a projection reconciled against
the RevLog: it captures the invertible diff
{ keywords: { added, removed }, mailboxes: { added, removed } }, navigates a local cursor, and dispatchesmessage.applyDiffwithinverse(diff)to undo ordiffto redo. Non-invertible mutations (e.g.message.destroy) produce no history step.
The runtime tier owns nothing: no per-link undo/redo stack, no recorded
change-diffs, and no history frames (mutationHistory frames do not exist).
Undo and redo execute as ordinary message.applyDiff mutations through the
normal pending-set path — the same accept → execute → settle flow, replay guard, and
provider path as the forward mutation — so they are offline-capable, converge
identically, and idempotent retry of an undo/redo invocation is handled by the
normal mutation idempotency machinery rather than by stack navigation.
2. Settlement and reconciliation¶
2.1 Settlement states¶
The catalog uses the settlement states defined by the adapter (adapter L1 §5):
accepted, confirmed, failed — the typed vocabulary lists only states that
actually settle.
confirmed means the confirmation watermark has passed the mutation — an
authoritative assertion whose after reflects it has been applied to the
projection — at which point the pending overlay entry retires. It does not mean
only that a provider command returned. A mutation with no provider work may move
accepted → confirmed in one runtime turn; a mutation with provider work
stays accepted — its local effect folded and its provider work queued or
retrying — while offline. An entity-creating mutation (draft create, send)
reconciles its provisional id to the provider-assigned id and retires by identity
once a projection row with that id exists (replication L1 §5.4).
2.2 Reconciliation failures¶
If provider reconciliation fails transiently, the mutation stays pending and the
local effect stays folded; it retries on the next connectivity window. On
permanent rejection the runtime settles the mutation failed, drops the pending
overlay entry so the view reverts to authoritative state, and surfaces the
failure. A mutation accepted by the provider but never confirmed is retired after
a bounded number of convergence cycles and recorded for telemetry, so the pending
overlay cannot grow unbounded (replication L1 §9). The
UI renders settlement state and may offer user actions, but it does not own
conflict resolution.
3. The forward_mutation pattern¶
Link-scoped mutations use the shared MutationRequest/MutationReceipt
contract; the request carries the typed MailOperation
(posthaste-contract-core), parsed once at the wire, and is forwarded — one
up-channel verb across both seams (RFC D35b):
The authority server validates the link/account scope before accepting,
assigns a RuntimeMutationId, emits RuntimeFrame::MutationSettlement frames on
the same link stream, and records the latest settlement for collapsed reconnect.
It routes the message-action variants of MailOperation (setKeywords,
setReadState, setFlaggedState, setUserTags, moveToMailbox, moveToRole,
replaceMailboxes, destroy, applyDiff, snooze, unsnooze) through one
accept → execute → settle helper to the handle actions; dispatch is an exhaustive
match over the enum, not a string lookup, and role moves are resolved
runtime-side. Account mutation methods own their delete resource linkage
(internals L3 §4, account-mutation-contract-pattern);
drafts and send remain dedicated methods.
4. Validation¶
4.1 Provider-latency tests¶
Tests cover provider command delay/failure while local view state updates immediately according to the mutation contract.
4.2 Mutation catalog tests¶
Tests cover read/flag/tag, role move, destroy, draft save, send, settings/account,
and undo mutations at the runtime boundary. Send tests
cover crash/retry idempotency before provider submission so duplicate sends cannot
occur from a reused clientMutationId.
5. Assertions¶
| ID | Sev. | Assertion |
|---|---|---|
| retire-on-confirmation | MUST | A pending mutation's optimistic effect is retired when the confirmation watermark passes it (an authoritative assertion reflects it), not when its provider command returned. |
| entity-convergence-by-id | MUST | Entity-creating mutations reconcile a provisional id to the provider id and retire by identity once a projection row with that id exists. |
| provider-failure-settlement | MUST | Provider reconciliation failures settle through runtime state, not UI-owned conflict logic. |
| undo-history-server-logged-client-navigated | MUST | Undo/redo history is authority-server-logged and client-navigated: the authority server durably owns the RevLog of applied change-diffs, the client owns the undo/redo stack as a projection reconciled against it, and the runtime tier keeps no per-link stack, records no diffs, and emits no history frames. Undo/redo execute as ordinary message.applyDiff mutations through the normal pending-set path. |
| mutation-settlement-on-link-stream | MUST | forward_mutation assigns a RuntimeMutationId and emits settlement frames on the same link stream, recording the latest settlement for collapsed reconnect. |