Skip to content

iOS fire-and-forget session sync can overwrite newer transcripts or resurrect deleted sessions #310

Description

@yxlyx

Summary

Account-session persistence is fire-and-forget and unsequenced. Multiple saves for the same session, and save/delete pairs, run as independent detached tasks with no revision or ordering token.

An older PUT can therefore complete after a newer PUT and roll the synced transcript backward. A delayed PUT can also race a DELETE and recreate a session the user removed, unless the server supplies an undocumented conflict guarantee.

Root cause

AppSessionSync.save() (apps/ios/Graff/Sources/GraffAccount.swift:293-304) snapshots the session and starts an independent Task.detached PUT. Saves are triggered from turn completion and new-session handoff. Deletion starts a separate detached task in SessionsListView.

There is no:

  • per-session client serialization;
  • cancellation/coalescing of obsolete saves;
  • monotonic client revision;
  • conditional request/version in Gateway.putAppSession;
  • ordering between PUT and DELETE.

Reproduction

  1. Delay the PUT produced after turn 1.
  2. Complete turn 2 and allow its newer PUT to finish.
  3. Release the delayed turn-1 PUT.
  4. Reload history and observe whether the older transcript won.

For the delete variant, delay a PUT, delete the row, then release the PUT and reload history.

Expected behavior

  • Define and enforce a per-session ordering/version invariant on both client and gateway.
  • Coalesce obsolete saves or reject stale revisions server-side.
  • Ensure DELETE tombstones cannot be undone by an older in-flight PUT.
  • Add deterministic delayed-response tests for save/save and save/delete ordering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions