Skip to content

Scope pending attachments to committed chat sessions - #1546

Open
lihongguang-0014 wants to merge 21 commits into
mainfrom
fix/session-scoped-pending-attachments
Open

Scope pending attachments to committed chat sessions#1546
lihongguang-0014 wants to merge 21 commits into
mainfrom
fix/session-scoped-pending-attachments

Conversation

@lihongguang-0014

@lihongguang-0014 lihongguang-0014 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Scope

Target: main

  • Give pending WebUI attachments a local lifecycle generation.
  • Retire attachments only when an ordinary session navigation or fresh task actually commits.
  • Treat canonical-equivalent session aliases as the same logical session, including queue ownership, parked queues, durable WAL lookup, and the post-subscription recovery boundary.
  • Normalize legacy alias-owned pending WAL rows in memory and deduplicate them during hydration so canonical navigation can recover them.
  • Make attachment preparation ownership explicit: composer work retires with its session, while detached response-handoff recovery survives unrelated navigation.
  • Serialize composer-owned refresh across cloned concurrent send snapshots while keeping detached collections independent.
  • Ignore stale MIME sniff, FileReader, staged upload, and refresh callbacks after retirement.
  • Preserve next-turn attachments during same-key navigation, superseded or failed navigation, draft rebind, response handoff, and canonical draft/Goal materialization.
  • Bind asynchronous completion to attachment object identity so restored or duplicate local IDs cannot overwrite another item.
  • Restore failed response-handoff attachments by exact handoff ownership rather than the page-local attachment counter, including when hydration finishes after a reload-time upload reuses the same local ID.
  • Assign recovered response-handoff attachments collision-free negative UI IDs so recovered and newly selected attachments always have distinct Vue keys.
  • Re-key colliding attachment IDs at every queue-to-composer boundary, including rows independently persisted by multiple tabs.
  • Block pristine Meta draft recovery while attachment discovery/read/upload work is still in flight.
  • Fence durable queue-to-composer recovery by the captured canonical session, active queue lease, live composable, composer revision, and atomic WAL revision; restore multi-item batches in queue order, and retain the source item as a durable local-only draft only while its cancellation tombstone still owns the row.
  • Treat a failed earlier cancellation as an ordering barrier for batch composer recovery, and upgrade an in-flight retained cancellation to a chained destructive cancellation when Clear Queue wins the race.
  • Invalidate the captured edit/pop restore lease as soon as a queued destructive override is requested, before its serialized cancellation reaches the WAL.
  • Distinguish a deleted retain-CAS row from a live peer-owned conflict so concurrent tabs cannot hide an earlier durable draft and restore a later one first.
  • Acquire the initial cancellation tombstone with the same exact-identity/revision transaction so a delayed tab cannot recreate a row after peer deletion.
  • Accept canonical-equivalent legacy aliases during atomic WAL mutations and normalize successful writes to the canonical owner.
  • Fence attachment preparation, enqueue completion, error recovery, and server-list hydration with exact-identity WAL revision compare-and-swap so stale work cannot overwrite a peer cancellation tombstone.
  • Reconcile live WAL conflicts with replacement semantics, preserving destructive tombstones instead of inheriting a stale retained-cancellation flag.
  • Re-key rejected in-memory send attachments when a newer composer attachment has reused the same local ID.
  • Remove immediately restorable queue entries in place so mixed editable, durable, and server-owned attachment rows retain their exact relative order while cancellation is pending or fails.
  • Integrate the current session-read and session-lifecycle domain boundaries and update the attachment browser fixture to the current wire contract.

Root cause

The chat view reused one pending attachment collection across route-level session changes. Asynchronous sniff, read, upload, refresh, durable queue-cancellation, queue staging, and server-list hydration work could write back without proving that the originating composer session, queue lease, composable lifetime, composer contents, WAL identity, and WAL revision were still current. Ordinary committed navigation did not retire the old attachment collection, while detached response-handoff recovery needed to outlive that composer. Concurrent cancellation completion could reorder a multi-item restore or recreate a retained row after another tab deleted it. The initial cancellation tombstone used a blind write, so a delayed tab could recreate a row after peer deletion; staging continuations could similarly overwrite a cancellation tombstone after attachment preparation, enqueue, or hydration completed. Atomic mutations compared raw session-key bytes, rejecting legacy alias rows even when they had the same canonical owner. The later retain CAS also represented both a deleted row and a live revision conflict as null, so one tab could hide an earlier peer-retained draft and restore a later draft first. Partial object assignment could retain a stale retainAfterCancel flag over a peer's destructive tombstone. popAll rebuilt mixed survivors by category before cancellation settled, moving non-editable rows ahead of earlier drafts. A shared in-flight cancellation promise discarded the stronger destructive intent when Clear Queue followed edit/pop recovery, and the restore callback remained valid until that queued destructive operation reached the WAL, allowing a retained local-only record to reappear in the composer or after reload. Pending-queue ownership and WAL lookup also used raw session aliases, so a canonical-equivalent navigation could strand a durable draft or reject a local reorder. Failed send and response-handoff recovery deduplicated by page-local numeric IDs that can be reused, and generic WAL queue recovery could combine same-numbered attachment IDs independently allocated by multiple tabs; these paths produced duplicate Vue keys in the live composer. The Meta recovery pristine check ignored attachment work before its placeholder existed. Canonical aliases, restored local IDs, and independently cloned concurrent send snapshots exposed related identity, busy-window, and exclusion gaps.

Non-goals

  • No upload protocol or server ownership changes.
  • No database schema, persisted draft format, or cross-device attachment state changes.
  • No server-side owner proof or upload cancellation changes.
  • No change to send snapshot semantics.

Compatibility

No public RPC, upload schema, database schema, persisted configuration, or client contract changes. Existing installations require no migration. Existing pending WAL records stored under legacy default-session aliases remain readable and are atomically normalized to their canonical owner when reordered or mutated. The cancellation and staging compare-and-swap operations use existing WAL fields and require no IndexedDB version change. The implementation is integrated with the current session-read and session-lifecycle boundaries and remains limited to local WebUI composer and pending-queue lifecycle handling plus its browser fixture.

Platform impact

Platform-neutral WebUI behavior. The change uses existing browser FileReader and Promise lifecycle paths and adds no filesystem, subprocess, signal, permission, or OS-specific behavior.

Tests

  • Focused queue/WAL, send recovery, and rendered composer regressions: 4 files and 319 tests passed.
  • Full npm run test:unit: 427 files and 5276 tests passed.
  • npm run typecheck: architecture, RPC, security, style, localization, and Vue type checks passed.
  • Session lifecycle Python suites with the development extra: 28 tests passed.
  • npm run build:artifact: production artifact build and bundle guards passed; 395 files verified.
  • npm run verify:release-dist: release artifact verification passed.
  • Isolated Chromium regression for attachment-drag-upload.spec.ts, history-hydration.spec.ts, and session-switch-transport.spec.ts: 15 passed.
  • git diff --check: passed.

Safety and limitations

A retired HTTP upload may still finish server-side and expire through the existing temporary-file lifecycle; its stale browser callback is ignored. Pending-queue staging keeps its existing park-and-retry behavior, while browser-backed staging and hydration transitions now fail closed on an exact-identity or WAL-revision conflict. A queue-to-composer cancellation acquires and retains its tombstone only while the captured canonical session, queue lease, composable lifetime, composer revision, exact WAL identity, and WAL revision remain current. A failed predecessor or live peer-owned CAS conflict blocks later batch restoration, while an explicit destructive clear is serialized after any retained cancellation already in flight and immediately invalidates its restore callback. Mixed queue survivors stay in their original order throughout that wait. If navigation, user edits, attachment changes, intent changes, or teardown wins the race, the durable local-only record remains available for later hydration unless a destructive operation owns the record; if a peer explicitly deletes it, atomic revision ownership prevents resurrection. Queue and rejected-send restoration may replace a colliding page-local attachment ID, but do not alter its upload identity or persisted payload. Detached response-handoff recovery can still complete across navigation. Browser regression used a local isolated gateway with mocked chat transport. No live provider credentials, multi-device workflow, or manual cross-device validation was used.

Release note status

Not added: this is a focused WebUI correctness fix with no public interface or migration.

Linked issue

None

Third-party origin

None. The implementation and tests are original OpenSquilla work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant