Files
kandev/docs/specs/ui/system-design/message-queue-edit.md
T
2026-09-10 14:32:06 +01:00

9.3 KiB

status, system, created, owners, requirements
status system created owners requirements
draft ui 2026-09-02
kandev
REQ-UI-MESSAGE-QUEUE-MANAGEMENT-002

Queued Message Editing System Design

Context and boundaries

The queue service owns pending-entry identity, FIFO admission, reservation, and mutation. The UI owns editor presentation and WebSocket request coordination. This design covers the cross-surface contract for editing a user-owned pending entry. It does not change agent, workflow, or server provenance rules, and it does not define the separate Send Now operation.

The queue's existing per-session admission boundary is the serialization point for queue mutations and automatic delivery. The edit hold is target-scoped: it must not become a session-wide Auto-run pause.

Components and responsibilities

  • internal/orchestrator/messagequeue.Service owns edit leases, target revisions, operation fencing, lease expiry, and the interaction between an edit hold and automatic head reservation.
  • internal/orchestrator/handlers.QueueHandlers authorizes the session before reading or mutating queue state, binds edit requests to the server-assigned WebSocket connection ID, translates typed queue conflicts to stable errors, and requests a post-save auto-run drain only after the edit lease is ended.
  • internal/orchestrator.Service owns promptability checks and exposes the policy-preserving queue drain used after a successful edit save. This drain may reserve the FIFO head only when Auto-run is already enabled; it never enables Auto-run as a side effect.
  • internal/gateway/websocket.Client places its connection ID in the dispatch context. The client never accepts a connection identity from a payload.
  • apps/web/lib/api/domains/queue-api.ts carries begin, renew, end, and fenced update requests. apps/web/hooks/use-queue-edit-protection.ts owns the editor lease lifecycle. queued-ghost-list.tsx and queued-ghost-message.tsx keep the editor attached to the selected row and identify successful saves separately from cancellations.
  • apps/web/hooks/domains/session/use-queue.ts reconciles mutation responses and queue status for the session captured by the action. Stale HTTP/WS responses must not overwrite a newer session snapshot.

Data model and contracts

A lease is keyed by (session_id, entry_id) and contains a server-generated lease ID, the entry's target revision at acquisition, an expiry timestamp, a monotonic lease generation, and the server-side connection binding. Lease connection identity is not exposed to the browser.

The queue edit end request may carry dispatch_if_auto_run: true only for a successful save. The backend releases the validated lease first, then invokes the policy-preserving drain for the same session. Cancel, failed save, stale lease cleanup, and disconnect cleanup omit the field or set it false. The response remains the existing session/entry acknowledgement; any dispatched successor is observed through the existing queue and session events.

The queue update contract includes lease_id, operation_id, and expected_target_revision when sent by a connected editor. The server returns the resulting target revision and operation ID. The operation ID is scoped to its live lease and its request content, attachments, and metadata. Repeating an identical operation returns the original revision without applying the update again. Reusing an operation ID with different content is rejected.

The ordinary compatibility update path remains available only for trusted non-WebSocket callers. Browser queue edits must acquire and retain a live target lease, pass its fencing fields on save, and refuse the save action when the lease is absent or has ended. The browser must never send an unfenced queue edit request.

Control flow and state transitions

  1. The editor requests message.queue.edit.begin for one visible user-owned row. The service checks the row under the session admission lock and creates one target lease without changing Auto-run.
  2. Automatic queue advancement continues for other entries. A policy-aware head reservation skips only while the current visible head is the leased target; it may reserve earlier entries normally.
  3. The editor renews the lease before expiry. Each renewal checks both lease ID and connection binding and advances the lease generation.
  4. Save validates the lease, connection, target revision, and operation ID in one admission-serialized operation. It claims any newly referenced staged attachments only after request preconditions that can reject the save have passed. The content, attachments, and entity-reference metadata replacement then commits atomically. Superseded attachment claims are released only after a successful replacement; rejected saves release only claims made by that request.
  5. After a successful save, the UI ends the lease with dispatch_if_auto_run: true. The backend validates and releases that lease before attempting the policy-preserving drain. The drain serializes against cancellation and other queue dispatches, rechecks promptability, and reserves the FIFO head only when Auto-run is already enabled.
  6. Cancel, failed save, lease loss, disconnect cleanup, and expiry end the lease without requesting a drain. A drain, remove, merge, session transfer, or replacement that wins first invalidates the target and causes the editor to refetch instead of applying stale state.
  7. Delayed cleanup from the old connection is fenced by lease ID, connection ID, and session/entry key, so it cannot release a newer lease or trigger a successor turn after a later edit.

Session transfer and queue snapshot replacement must participate in the same admission boundary as editing and invalidate leases for affected source and destination entries. This prevents a transfer or restore from racing a stale save and prevents a lease from surviving under an obsolete session identity.

Failure and recovery behavior

  • A second begin for the same target returns an edit conflict. A begin for a missing, reserved, or non-user row returns the non-enumerating not-found outcome.
  • A stale lease, foreign connection, expired lease, or mismatched revision returns a typed edit conflict. The UI clears local edit state and fetches the authoritative queue; it does not apply an optimistic replacement.
  • A duplicate operation with the same operation ID and request hash is successful and returns the original revision. A hash mismatch is rejected without a second mutation.
  • Validation, lease, reference, or attachment-claim failures before the queue mutation release only attachments newly claimed by that request. Existing retained attachments remain owned by the queue.
  • A requested post-save drain is never an implicit resume: Auto-run OFF, promptability guards, clarification, cancellation, or an in-flight dispatch leave the saved row pending for the next eligible trigger.
  • If lease release succeeds but the post-save drain cannot dispatch, the saved row remains durable and the existing queue status event is authoritative; a later agent-ready or explicit Auto-run trigger may deliver it.
  • Status events remain authoritative for every successful update. Reconciliation is scoped to the session and uses the existing request-generation guard so an older response cannot restore stale content after a newer event or session switch.

Permissions and security

Session authorization runs before queue reads for all edit actions. The server uses its connection-bound identity rather than payload data for lease fencing. The post-save dispatch flag is honored only when the live lease records a successful update for that target; a browser cannot turn a cancel or failed save into a delivery request. A browser may submit only session ID, entry ID, lease ID, operation ID, target revision, content, and replacement metadata. It cannot choose queued_by, task ownership, or another connection's lease.

Responsive behavior

The existing inline queue panel remains the mobile composition and its queue list remains the only internal scroll owner. Edit, save, and cancel controls remain discoverable without hover on coarse pointers, use at least 44 by 44 CSS pixel hit areas where the shared row/action pattern requires them, and do not introduce document-level horizontal overflow. Desktop keeps the compact row layout. Both surfaces use the same lease and reconciliation state.

Observability and tests

Queue edit conflicts, expired leases, rejected revisions, post-save drain deferrals, and attachment rollback failures should retain the existing structured queue-handler/service logging without logging message content or attachment data. Focused backend coverage belongs beside the message-queue service and handler tests and must cover save-after-turn-completion, Auto-run OFF, cancel, and failed-save paths. Frontend unit coverage covers lease lifecycle, operation fencing, and successful-save versus cancellation completion. Playwright coverage must exercise desktop and mobile-chrome edit/save behavior, including an Auto-run backlog and a session/view replacement scenario.

Requirement mapping

Requirement Design sections
REQ-UI-MESSAGE-QUEUE-MANAGEMENT-002 Data model and contracts; Control flow and state transitions; Failure and recovery behavior; Responsive behavior