Skip to content

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 domain rev_log model plus the RevLogStore ports 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 the revCursor control 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 dispatches message.applyDiff with inverse(diff) to undo or diff to 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 acceptedconfirmed 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):

let receipt = runtime.forward_mutation(caller, request).await?;

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.