Course outline

iOS / Swift / Accessible systems and target evidence / Lesson 17

Offline commands and reconciliation

Model pending intent separately from confirmed business state and reconcile uncertain completion with stable operation identity.

About 5 min readContent reviewed
On this page

Lesson goals

  • A local queue records intent, not confirmation
  • Persist stable operation identity
  • Represent uncertain completion honestly

A local queue records intent, not confirmation

An offline command can preserve what a user wants to do while the service is unavailable. It does not prove the service will accept the action later. A seat may be taken, permission may expire, or the account may change before evaluation. Keep queued, sending, confirmed, rejected, and uncertain states distinguishable. Explain the pending status and cancellation limits without promising an outcome the client cannot guarantee. Connectivity status is only a hint: an active network interface does not prove the target endpoint is reachable or authorized. Let the authoritative response determine business confirmation.

Persist stable operation identity

Each queued operation needs an identity that survives retries and restart. Generating a new identity on every attempt defeats an idempotency contract because the server sees apparently distinct commands. The first fragment defines a portable command record containing operation, account, target, and quantity. The identifier here is supplied by the caller so it can be created once and then persisted. Sensitive content and credentials do not belong in this plain teaching envelope. Schema versioning, validation, and retention still need an explicit storage design before an actual queue is used.

swift Illustrative Swift or SwiftUI fragment; requires the declared toolchain host.
struct QueuedReservation: Codable, Equatable, Sendable {
    let operationID: String
    let accountID: String
    let workshopID: String
    let seats: Int
}
let queued = QueuedReservation(operationID: "op-7", accountID: "lab-user",
                               workshopID: "w-7", seats: 2)
print(queued.operationID)
Expected output
op-7

The synthetic identifier is stable input, not a production uniqueness generator. No queue or remote operation is created.

Represent uncertain completion honestly

A rejected response establishes a different fact from a lost response after sending. The latter may mean the server accepted the command but the client did not receive confirmation. The second fragment distinguishes these transitions instead of marking every transport failure rejected. Under this teaching state machine, lost confirmation leads to uncertain, which requires reconciliation by operation identity. A real service’s idempotency and query contracts decide what recovery is safe. A local enum cannot invent exactly-once delivery or force the server to forget an already committed operation.

swift Illustrative Swift or SwiftUI fragment; requires the declared toolchain host.
enum Delivery: Equatable { case queued, sending, confirmed, rejected, uncertain }
enum DeliveryEvent { case begin, accepted, refused, responseLost }
func transition(_ state: Delivery, _ event: DeliveryEvent) -> Delivery {
    switch (state, event) {
    case (.queued, .begin): return .sending
    case (.sending, .accepted): return .confirmed
    case (.sending, .refused): return .rejected
    case (.sending, .responseLost): return .uncertain
    default: return state
    }
}
print(transition(.sending, .responseLost) == .uncertain)
Expected output
true

Unrecognized transitions preserve the state in this small model. A production command API should explicitly report invalid events where recovery requires it.

Define ordering and conflicts by domain meaning

Two queued commands may be independent, or one may depend on the other. A title edit followed by deletion is not equivalent to executing deletion first and retrying the edit later. Define per-record ordering, supersession, and conflict policy before introducing parallel workers. A server revision or other documented precondition can detect stale writes, while the UI must explain whether the user should retry, merge, or discard. Never prescribe last-write-wins for every domain. A conflict in a reading preference and a conflict in a reservation have different consequences and deserve different rules.

Keep account and cancellation boundaries visible

A command queued under one account must not be sent under another account merely because the app restarted or a token changed. Resolve current ownership and service authorization before delivery and decide what logout does to unsent private intent. Cancellation before send may remove local intent; cancellation after send can require a compensating command or may be impossible under the service contract. Do not erase an uncertain operation record just to make the pending badge disappear. Retain enough safe identity for reconciliation and apply a reviewed retention policy.

Test restart gaps with a synthetic transport

Use a fake store and transport to model restart after enqueue, after send but before confirmation, and after a server rejection. Verify retries reuse the same operation identity and do not cross account scopes. Exercise ordering dependencies and an unavailable record without a live service. A serialization test alone cannot prove queue durability, and a fake transport cannot prove the server’s idempotency implementation. Record those evidence limits. No persistent queue, real reservation, background worker, or device network operation was executed in this course authoring environment.

Common errors and fixes

Queued is shown as confirmed

Why it happens
Local intent was confused with authoritative acceptance.
How to fix it
Expose pending status until the service confirms.
Prevent it next time
Test delayed rejection after successful local enqueue.

Retries create new operation identifiers

Why it happens
Identity was generated per attempt instead of per command.
How to fix it
Persist the command identity and reuse it under the service contract.
Prevent it next time
Simulate a lost response followed by retry.

Unknown completion is treated as definitely rejected

Why it happens
Transport uncertainty erased possible server success.
How to fix it
Represent uncertainty and reconcile before unsafe repetition.
Prevent it next time
Separate rejected and response-lost fixtures.

Check your understanding

1. Why is a stable operation ID insufficient by itself to guarantee idempotency?

Show worked solution

The authoritative service must define and implement how it recognizes, scopes, retains, and responds to repeated IDs. The client preserves the identity so that contract can work. Without service support, the same string does not prevent duplicate side effects.

2. What should happen to an uncertain command when the user logs out?

Show worked solution

Preserve a safe reconciliation record under its original ownership according to policy, invalidate publication into new sessions, and do not resend under another account. Explain unresolved status where appropriate. Deleting evidence or relabelling it confirmed would conceal a real ambiguity.

Key takeaways

  • A local queue records intent, not confirmation
  • Test restart gaps with a synthetic transport

Official references