Skip to main content

Swift Advanced Order Mutations

Remove one line without losing the total, enforce the allowed lifecycle of an Order, and handle a rejected stale save without overwriting the winner. Start with the Swift Order tutorial for workspace setup, Q/E result handling and the first audited edit.

The retained native SQLite recipes use published Swift 0.3.1. Existing generator/adapter qualifications remain scoped, and the other six runtimes’ lifecycle/soak workspace RELEASE PENDING marks remain unchanged. This console path does not certify Apple UI or HTTP lifecycle behavior.

Prepare three independent recipes​

Use the v1.0 model and original three-order fixture. The target is ORD-2026-1001: DRAFT (1001), total 3850 USD cents, Notebook quantity 2 at 1250, Pen quantity 3 at 450; Order and items start at version 1. Do not first run the introductory quantity edit: that changes the total to 5100 and invalidates these recipe starting values. The export, isolation and stock fixtures are different.

The complete native application contains startup, helpers and phase handlers. Code below is quoted from that application and assumes its trusted context, imports and validation helpers. These are function-body excerpts or complete functions, not replacement standalone programs. removeLine, transition, worker, status, report and their casing variants are application helpers where present; they are not extra generated API methods.

Each recipe needs a fresh disposable database initialized with bootstrap and seeded once. The retained built workspace/toolchain is required; this is not a clean-machine bootstrap. The original receipts keep each control on a separate seed copy, so an omission probe, removal and stale-removal probe do not silently alter each other's starting state.

1. Explicitly remove Pen and recompute the total​

  1. Load all Order scalars and its complete bounded item list. The selected list has a 101-item overflow sentinel; reject more than 100 and require a DRAFT Order, at least two items, and exactly one SKU-PEN.
  2. Validate each price, quantity and stored line total. Sum only the retained Notebook line: 2 × 1250 = 2500 cents. Do not calculate from a one-item preview.
  3. Mark Pen for deletion with the generated deletion API, update the Order total, and save both changes in one provider transaction.
  4. Read again through a normal query and a deleted-only query in a new process. Preserve tombstone identity; this is soft deletion, not physical row removal.
order.orderItemList[index].markForDeletion();order.updateTotalMinor(total);print("ORDER_REMOVAL_ATTEMPT")
_ = try await order.auditAs("Remove Order line and recompute total atomically").save(context)

Generated entities use value semantics. Mark order.orderItemList[index] inside the parent value that will be saved and await the audited Order graph save. Mutating a separate local child copy is insufficient. Keep the reporting-overflow arithmetic and complete-item checks from removeLine.

The snippet assumes the full function has already selected the unique target and validated total. A generated delete marker does not validate prices, quantities, Order state or authorization. The fixture's business-number query is not a production buyer/tenant policy.

Read the tombstone deliberately​

The existing complete-read handler uses this explicit deleted-only request:

let deleted=try await Q.orderItems().deletedRowsOnly().withSkuCodeIs("SKU-PEN").limit(10)
.comment("Read retained deleted Order lines").purpose("Verify explicit soft deletion after restart").executeForList(context)

This is a diagnostic query across the synthetic fixture, not an authorized cross-tenant endpoint. Use the separate isolation model and trusted scope for protected access. Ordinary relation loading excludes the tombstone.

Seed-based controlExpected durable resultFailure/recovery decision
remove-lineOrder total 2500, version 2; Notebook remains quantity 2/version 1; Pen remains in storage at version -2 and is absent from normal item reads. Other Orders stay unchanged.Treat the returned audited save/readback as the accepted removal; keep the tombstone ID for diagnosis.
omission-probe, on a separate fresh seedSaving the Notebook-only preview changes only the Order note/version. Total remains 3850 and both items stay live at version 1.Omission is not a delete command; explicitly mark the intended line.
remove-conflict, on a separate fresh seedA competing Pen label save advances Pen to version 2 with Pen (concurrent winner). The attempted total/deletion fails: Order stays total 3850, version 1, with original timestamp; both items remain live.Preserve the competing item. Reload the full Order/items and ask whether removal is still intended; do not publish a 2500 total after a failed transaction.

For the stale-removal control, the native log contains the Order UPDATE before the failing item UPDATE. The independent snapshot proves the earlier Order write rolled back. The winner's Pen update is intentionally retained; “rollback” does not mean erasing another transaction's commit. Do not catch a child failure inside the transaction and return a successful removal.

Walk through the successful removal​

From the site root, using the retained built workspace. Stop if a native command fails; expected business rejections are reported as normal JSON, not process failures.

export ORDER_WORKSPACE="$PWD/artifacts/order/2026-10-27/swift/swift-app-console"
export ORDER_FIXTURE="$PWD/static/examples/order/fixture.json"
export ORDER_DATABASE="$(mktemp -d)/orders.sqlite"
cd "$ORDER_WORKSPACE"
for phase in bootstrap seed remove-line complete-read; do
./.build/debug/TeaQLConsole "$phase"
done

Inspect ORDER_CASE for remove-line and complete-read, not only the exit status. To inspect omission-probe or remove-conflict, start another fresh seed and substitute that phase for remove-line; never chain all three on the same edited database. Version -2 is the result for this version-1 fixture, not a constant to assign to arbitrary deleted entities.

2. Apply an allowed state transition​

The model declares DRAFT (1001), CONFIRMED (1002) and CANCELLED (1003). The application implements this policy before calling a generated setter:

Current statusTo DRAFTTo CONFIRMEDTo CANCELLED
DRAFTRejectCommitCommit
CONFIRMEDRejectRejectCommit
CANCELLEDRejectRejectReject

Read the complete current Order with its version; require one target. Reject unknown target 9999, repeated states and prohibited reversals without saving. A generated status setter expresses a value change; it does not itself enforce this transition matrix or your service's authorization.

func transition(_ context: UserContext) async throws -> [String:Any] {
let target=try required(ProcessInfo.processInfo.environment["ORDER_TARGET"])
let next=try required(Int64(required(ProcessInfo.processInfo.environment["ORDER_NEXT_STATUS"])))
let rows=try await Q.customerOrders().withOrderNumberIs(target).limit(2)
.comment("Load full Order before state transition").purpose("Apply the Order lifecycle policy with original version").executeForList(context)
try require(rows.count==1,"Expected one target");var order=rows[0]
let current=try required(E.customerOrder(order).orderStatusId().eval())
var result:[String:Any]=try ["target":target,"from":current,"to":next,"observedVersion":required(E.customerOrder(order).version().eval())]
guard (current==1001 && (next==1002 || next==1003)) || (current==1002 && next==1003) else {result["outcome"]="rejected";result["reason"]="Transition not allowed";return result}
if next==1002 {order.updateOrderStatusToConfirmed()} else {order.updateOrderStatusToCancelled()}
_ = try await order.auditAs("Apply an allowed Order status transition").save(context);result["outcome"]="committed";result["reason"]="";return result
}

ORDER_TARGET and ORDER_NEXT_STATUS are console inputs used to demonstrate the policy. A service should validate its request before passing the values to this operation. The returned observedVersion is the version read before save, not the version of the committed result; complete-read obtains current state.

On a separate fresh database, run the allowed path and rejection guards from the prepared application directory (the previous section established the same workspace and absolute ORDER_FIXTURE):

export ORDER_DATABASE="$(mktemp -d)/orders.sqlite"
run_order_phase() { ./.build/debug/TeaQLConsole "$1"; }
run_order_phase bootstrap
run_order_phase seed
export ORDER_TARGET=ORD-2026-1001
export ORDER_NEXT_STATUS=1002
run_order_phase transition
run_order_phase guard-edit
export ORDER_NEXT_STATUS=1003
run_order_phase transition
run_order_phase transition
run_order_phase complete-read
Operation in that sequenceExpected result
DRAFT → CONFIRMEDoutcome=committed, observed version 1; subsequent read shows state 1002/version 2.
guard-edit while CONFIRMEDrejected=true, reason Only DRAFT orders can be edited; no quantity, total or version write.
CONFIRMED → CANCELLEDCommit with observed version 2; subsequent read shows state 1003/version 3.
CANCELLED → CANCELLED againoutcome=rejected, Transition not allowed; state/version remain 1003/3.

These transitions preserve the 3850 total and all item values/versions. Setting CANCELLED does not delete items, release stock or deliver an external event: the v1.0 recipe has no inventory reservation workflow. The retained state controls also cover DRAFT → CANCELLED, all prohibited pairs, unknown state and item edits on a CANCELLED Order. A rejected policy decision is a normal business result, not proof of a provider failure. If save itself fails, preserve that native error and use the conflict/provider decision below; do not return committed in a finally block.

3. Handle a stale Order version​

The existing stale-order control isolates the version decision from business math: two native processes change only the Order note. Both fully load the same existing version-1 Order before either is allowed to write.

StepWriter AWriter BAuthoritative database
LoadRetain original Order/version 1.Retain original Order/version 1.Still seed state; no writes before the readiness barrier.
Release BKeep waiting with version 1.Save Delivery note from writer B.B's note at version 2, independently checked before A is released.
Release AAttempt audited save with the original version 1; native rejection.Already finished.B's note, version and timestamp remain unchanged; amounts/items stay at seed values.
Read againReload authoritative state before any new business decision.—Fresh process sees B's committed version 2.

After its readiness/release barrier, this language's worker executes:

order.updateOrderNote(note);var outcome="committed",reason=""
do { _ = try await order.auditAs("Revise Order note with optimistic version").save(context) }
catch { outcome="conflict";reason=String(describing:error) }
result["outcome"]=outcome;result["conflictReason"]=reason
print("ORDER_WORKER "+String(decoding:try JSONSerialization.data(withJSONObject:result,options:[.sortedKeys]),as:UTF8.self))

The test worker collects thrown save errors under one label. The parent controller verifies the native error and winner; do not classify every thrown provider error as a conflict.

Classify the error, then decide​

The native stale-run diagnostic in the existing swift receipt is:

optimisticLock(entity: "CustomerOrder", id: TeaQLCore.TeaQLValue.int(1), expectedVersion: 1)

Preserve the associated entity and expected version in the native error. Await the save before returning or ending the provider scope; a cancelled or failed Task is not a committed transition.

The numeric entity ID in the recorded diagnostic belongs to that database; use the scoped business identity rather than copying it as a portable Order key.

OutcomeApplication action
Confirmed optimistic version rejectionReload the complete authoritative Order and needed items in the same trusted scope. Revalidate current state and amounts. Reject the stale command or reconcile the user's intent before creating a new audited attempt.
Policy says Transition not allowed / non-DRAFT editReturn that business rejection without a write. Do not retry it as a transient database problem.
Busy/locked, timeout, provider statement or COMMIT failurePreserve the native cause and diagnose transaction/provider cleanup. These errors do not count as version conflicts.
Missing Order after reloadHandle not-found in the same scope. Do not fabricate a successful edit or borrow an ID from another fixture.

Do not overwrite a losing object's version with the winner's version and resubmit its old fields. Do not put every failure into an unbounded retry loop. A reloaded Order may now be CONFIRMED/CANCELLED, or the Pen line may have changed; repeat the business checks before deciding whether to retry. A newly accepted edit needs its own audit reason and one complete transaction.

The worker files use a 45-second readiness/release protocol and are coordinated by the existing P1 runner. Running the bare worker phase without its controller will wait for release; use the stored round trace to understand the timeline. The same-Order control releases both writers together and permits one winner; the distinct-Order control expects two commits. Use the recorded ready/result snapshots to distinguish the one-target and two-target outcomes.

The retained stale race changes notes. It does not supply a complete service-level retry/merge policy for two concurrent state-transition commands. Keep the save failure boundary explicit and design that business decision at the service layer.

Existing evidence and next steps​

TopicWhere to inspect the retained result
Exact snippets and runtime/package qualificationCurrent core receipt: runtimes.swift.verification.cases and the source-bound application above.
Explicit removal, omission, stale child and state matrixP1 completion receipt, with its original sources; the current core receipt reruns those cases with the quoted source.
Ordered stale writer and same/different Order racesP1 concurrency receipt, plus cases.stale-order in the current core receipt.
Failures and database invariantsNative errors, provider UPDATE/audit logs, before/after/fresh-read snapshots, and the removal/state expectations.

Use the existing receipt's controls for the three removal branches, steps for state decisions and rounds for writer ordering. A positive process exit from a negative-test reporter is not a successful business mutation. Do not relabel observedVersion as a returned committed version.

Continue with the Swift troubleshooting table, API usage guide and documentation coverage matrix. The first-edit tutorial and School regression baseline remain separate paths.

Continue with Order queries​

Use Swift Order Queries for continuous export, retained-ID page jumps and provider summaries. The query recipes use separate nine-/ten-order datasets; keep the fixture and context/store lifetime with the operation you are learning.

Continue with buyer and tenant isolation​

Use Swift Order Isolation to upgrade the model to v1.1 and build root/buyer scope from an application-owned trusted session.