fix: make queue admission reliable (#3663) (#3666)

This commit is contained in:
Carlos Florêncio
2026-09-14 19:19:09 +01:00
committed by GitHub
parent 2a0ec87da7
commit 8acb32c889
47 changed files with 3452 additions and 68 deletions
@@ -0,0 +1,56 @@
---
status: active
system: tasks
created: 2026-09-14
owners:
- kandev
---
# Queue Admission Requirements
## Overview
Users need a reliable result when submitting a prompt to a busy session.
The task system owns admission identity, persistence, and recovery. Composers expose this contract on desktop and phone.
An admission is one submission attempt, including its automatic retry. Queue editing, merging, and dispatch are later operations.
## Requirements
### REQ-TASKS-QUEUE-ADMISSION-001: Recoverable queue submission
**Intent:** Preserve user input and prevent duplicate admission when a response is lost.
#### Acceptance criteria
- **AC-TASKS-QUEUE-ADMISSION-001.1:** Every ordinary composer queue submission shall have a stable admission identity, including submissions without plan comments.
- **AC-TASKS-QUEUE-ADMISSION-001.2:** Repeating an accepted admission with the same identity and payload shall not append content, claim attachments, or consume comments again.
This applies after automatic merge, manual removal, queue clearing, reservation, dispatch, and backend restart.
- **AC-TASKS-QUEUE-ADMISSION-001.3:** Reusing an admission identity with a different payload shall fail without changing the accepted work.
An admission shall remain bound to its original task, session, and session incarnation.
- **AC-TASKS-QUEUE-ADMISSION-001.4:** A queue submission shall allow 10 seconds for a response, or 30 seconds when it includes attachments.
After uncertain transport failure, the client shall reconcile acceptance and retry at most once with the same identity and payload.
- **AC-TASKS-QUEUE-ADMISSION-001.5:** Confirmed acceptance shall clear only the submitted draft. A failed queue refresh after acceptance shall not report admission failure.
Rejected or unresolved submissions shall preserve the draft and attachments. A later unrelated draft shall remain unchanged.
- **AC-TASKS-QUEUE-ADMISSION-001.6:** Queue capacity and validation rejections shall show localized rejection feedback.
An unresolved timeout or connection loss shall show uncertain delivery feedback without claiming that the server rejected the message.
- **AC-TASKS-QUEUE-ADMISSION-001.7:** Reliable admission shall preserve Auto-merge, capacity, attachment ownership, Auto-run, and plan-comment consumption rules.
In particular, a compatible ordinary submission shall remain eligible for automatic merging.
- **AC-TASKS-QUEUE-ADMISSION-001.8:** Task chat and Quick Chat shall provide equivalent recovery and draft preservation on desktop and phone.
Phone feedback shall remain readable with the existing touch-accessible Send action.
## Compatibility and exclusions
Clients that omit admission identity retain their existing behavior without automatic replay guarantees.
Existing plan-comment identities remain valid. A second deliberate submission is a new admission, not a replay of an edited draft.
No provider-level exactly-once execution or offline outbox is promised. Browser reload does not restore an unacknowledged submission automatically.
## Related contracts
- [Resume prompt queue](resume-prompt-queue.md) owns startup eligibility and deferred dispatch.
- [Plan comments](plan-comments.md) owns comment selection and atomic consumption.
- [Automatic merge overrides](../../ui/requirements/message-queue-auto-merge-session-overrides.md) owns merge policy and capacity exceptions.
## Implementation plans
- [Queue admission reliability](../../../plans/queue-admission-reliability/plan.md)
@@ -0,0 +1,101 @@
---
status: current
system: tasks
requirements:
- REQ-TASKS-QUEUE-ADMISSION-001
---
# Queue Admission System Design
## Boundary and mapping
The task system owns durable prompt admission. This design extends the existing queue transport and identity guards.
| Criteria (AC-TASKS-QUEUE-ADMISSION-001) | Sections |
| --- | --- |
| .1, .2, .3, .7 | Server admission, Persistence |
| .4, .5 | Client recovery |
| .6, .8 | Composer feedback |
## Server admission
`QueueHandlers.wsQueueMessage` already accepts optional `client_queue_id`. Preserve this field rather than adding a parallel `client_message_id`.
Validate its length for every identified admission. Keep legacy unidentified callers on the existing path.
Plan-comment admissions retain their existing transaction and merge exclusion.
For ordinary identified requests, add a typed admission request to the messagequeue service.
Capture the immutable `QueueSessionIdentity`, normalized payload, and caller identity. Fingerprint content, model, plan mode, attachments, context, references, and admission options.
Exclude request IDs, timeouts, and transport timestamps. Use the existing canonical fingerprint utility.
Under the existing task/session admission guards, validate authorization and incarnation before reading any receipt.
An exact receipt replay returns acceptance without changing queue state. A mismatched fingerprint returns a typed conflict.
Replay lookup precedes capacity evaluation and attachment claim preparation that can reject already-claimed uploads.
Mutable reference validation applies to a first admission. Exact replay must not fail because an accepted reference later changed.
For a new admission, keep the existing insertion, full-capacity candidate fold, and staged attachment rules.
Write the receipt in the same transaction as the accepted insertion or fold and any attachment ownership changes.
A transaction failure commits neither content nor receipt. A database uniqueness constraint closes concurrent replay races.
Do not implement a separate receipt write after queue commit or a read-then-insert deduplication check outside the transaction.
Store ordinary contributing IDs in a provenance metadata key that is ignored by merge compatibility and unioned by automatic and manual folds. Ordinary entries must retain their current compatibility behavior, while their provenance remains available after a source row is folded or dispatched.
The earlier tail still survives automatic merging. A receipt identifies admission, not the continued existence of a visible source row.
Post-admission readiness checks retain current behavior and cannot convert committed acceptance into rejection.
## Persistence
Add a queue-owned `queue_admission_receipts` table through `repository_sqlite.go` initialization and a focused repository helper.
The shared SQL implementation must work with SQLite and PostgreSQL using established rebinding and lock conventions.
Use `(task_id, session_id, session_incarnation_id, client_queue_id)` as the receipt key.
Store the normalized request fingerprint, accepted queue ID, bounded response snapshot, and acceptance timestamp.
The response retains the current `QueuedMessage` shape. Omit inline attachment data from stored and replayed snapshots.
Clients already hold the submitted attachment payload and refresh authoritative queue state separately.
Retain receipts after manual edits, merges, removal, clearing, reservation, and dispatch. These operations do not reverse admission.
Session/task deletion purges associated receipts through existing lifecycle transactions. Reset invalidates old incarnation requests before replay lookup.
Session transfer must not retarget a receipt. Reject an obsolete original identity under current ownership guards.
Do not add a time-based expiry that allows a delayed duplicate within a retained session.
Existing rows need no backfill. Historical unidentified requests have no replay guarantee.
## Client recovery
`useMessageHandler` passes its existing `clientAdmissionId` for every queue submission, not only plan-comment submissions.
`useQueueAdmissionAction` preserves that ID and treats acknowledged admission as successful even if its terminal queue refresh fails.
`queueMessage` keeps an explicitly supplied ID. It does not generate a replacement ID inside retry handling.
Use a shared request closure with numeric timeout `10000`, or `30000` for nonempty attachments, for both attempts.
Classify timeout and connection closure using existing transport errors. Explicit server rejection is not a transport failure.
Keep existing queue/transcript reconciliation as an optimization. Bound ordinary reconciliation to one queue read and one newest transcript page per pass.
Each read has a 5-second timeout. Wait at most 3 seconds for reconnection before one replay-safe retry.
After retry failure, one bounded reconciliation pass can confirm acceptance. Otherwise propagate typed rejection or uncertain delivery.
The server receipt, not exhaustive transcript scanning, supplies deduplication when the original row has disappeared.
Preserve existing plan-comment recovery coverage while extracting any shared helper.
A returned replay snapshot must not insert a phantom pending row into the store.
Use the existing authoritative queue refresh and incarnation-aware mutation token.
After unresolved failure, preserve the draft. A later deliberate Send remains a new admission and can duplicate an earlier uncertain submission.
Feedback must not claim otherwise. Persistent draft retry identities are outside this package.
## Composer feedback
Keep typed queue error codes through the API boundary. Map capacity, validation, identity conflict, and unavailable-session errors to localized rejection copy.
Do not display raw server diagnostics as translated UI copy. Preserve existing plan-comment-specific normalization.
Only an unresolved transport outcome uses the existing uncertain-delivery message.
Use shared failure presentation for Task chat and Quick Chat. Audit the passthrough composer if it receives the same error types.
No new controls or layout are needed. Reuse the current toast, composer, and `chat-input-toolbar-mobile.tsx`.
The phone Task layout and Quick Chat retain their scroll ownership, safe-area spacing, and 44-pixel Send target.
The plan's UI-01 preview defines the affected error state. Add desktop and phone rendered regression coverage.
## Security and observability
Apply existing queue authorization before receipt lookup. Never disclose another task's receipt by a guessed key.
Fingerprinting and receipt handling must not log prompt content, attachment bytes, or credentials.
Use bounded structured fields for initial acceptance, replay, conflict, and uncertain client recovery.
No new metrics, high-cardinality labels, or global transport logging are required.
## Related decisions and implementation
- [Durable admission receipts](../../../decisions/2026-09-14-durable-queue-admission-receipts.md) records the accepted persistence choice.
- [Server-owned Auto-run](../../../decisions/2026-08-16-server-owned-queue-auto-run.md) remains authoritative for dispatch.
- [Implementation plan](../../../plans/queue-admission-reliability/plan.md)