9.5 KiB
spec, decision, created, status
| spec | decision | created | status |
|---|---|---|---|
| docs/specs/ui/requirements/message-queue-management.md | docs/decisions/2026-08-03-separate-message-queue-provenance-cancellation-and-capacity.md | 2026-08-03 | implemented |
Fix Plan: Message Queue Management
Overview
Make the queue panel's Clear all promise literal, add removal for every visible pending message, and expose the per-session queue limit under Settings > General > Message Queue. Preserve durable in-flight delivery, session isolation, message provenance, and already accepted retries.
Confirmed Root Cause
The reported Bitbucket Integration task has ten persisted queue rows. Every row
has queued_by='agent'. The frontend sends message.queue.cancel, whose
handler calls messagequeue.Service.CancelAll; the SQLite and memory
repositories then delete only rows whose queued_by is not agent,
workflow, or server. The response therefore says removed: 0. The hook's
optimistic empty state is replaced by the next authoritative status event, so
the full queue immediately reappears.
Individual removal has the same repository guard. The React row action also
uses one canEdit flag for both Edit and Remove, hiding removal on
agent/workflow rows. This behavior implemented ADR 0051's prior ownership
boundary, so the repair requires an explicit contract change rather than only
a frontend patch.
The live installation was inspected read-only. No queued row, task, session, or setting was mutated during diagnosis.
Behavioral Contract
- An authorized session owner may remove any pending entry returned by queue status and may clear all such entries, regardless of provenance.
queued_bycontinues to control editing and merge compatibility.- The visible queue uses compact one-based ordinals after remove, merge, or drain; durable FIFO position keys remain unchanged.
- A durable lifecycle row already marked in flight remains hidden and non-cancellable. Cancellation and reservation must be race-safe.
- Every user-facing queue WebSocket handler authorizes its session. Queue add
and append additionally validate that
task_idbelongs tosession_id. - Capacity resolves as valid environment value, then persisted install
setting, then default
10.0means unlimited. - Saving capacity applies live, does not prune existing rows, and limits only new admissions. Restore/retry of already accepted work bypasses the cap.
Backend Design
Pending-row cancellation
- Change the
Repository.DeleteByIDandDeleteAllBySessioncontracts from user-origin deletion to visible-pending deletion. - In both SQLite/Postgres-compatible and memory repositories, serialize with
the existing per-session mutation lock and skip
lifecycle_reserved_in_flightrows. - Use persisted compare guards and affected-row counts so an out-of-process reserve or update that wins cannot be deleted or counted afterward.
- Preserve the mandatory
(session_id, entry_id)scope and the privilegedPurgeTask/AcknowledgeByIDpaths. - Update service logging and error text to remove the obsolete "not owned by caller" implication.
Queue action authorization
- Add a task-service authorization method for a
(task_id, session_id)pair: authorize both resources, load the session, and fail closed when the pair is inconsistent. - Inject a queue access authorizer into
QueueHandlersfrom backend composition. - Guard get, add, append, update, remove, merge, clear, and drain before any queue read or mutation. Add/append use pair authorization; session-only actions use session authorization.
- Return the existing non-enumerating error shape and test that unauthorized calls neither reveal status nor publish a mutation event.
Runtime capacity setting
- Add
internal/system/queuesettingswith typed settings, generic-settings adapter, environment resolver, live target interface, service, and HTTP handlers. - Persist
{"max_per_session": N}under install-wide keymessage_queue. - Resolve the setting before the orchestrator queue starts. Extend system
wiring with the live
messagequeue.Serviceso GET/PATCH use the same target and register under/api/v1/system/message-queue/settings. - Register GET on the signed-in system group and PATCH on its admin subgroup.
PATCH validates
N >= 0, returns409while a valid environment override is active, persists first, then live-applies the response value. - Replace the queue service's plain integer with a concurrency-safe runtime
value and add
SetMaxPerSession. Each admission snapshots it once for the repository call, log, andqueue_fullresponse. - Normalize non-positive environment values to effective
0; ignore and warn on malformed values. PreserveKANDEV_QUEUE_MAX_PER_SESSIONcompatibility. - Make ordinary restore and durable lifecycle retry bypass the current admission cap. Initial user/agent/workflow/server insertions still obey it.
Frontend Design
Queue panel
- Split
canEditfromcanRemoveinQueuedGhostMessageandRowActions. Every visible row gets remove; editing remains user-only and merge rules stay unchanged. - Keep optimistic behavior only when the hook can reconcile it. On removal or clear failure/race, refetch authoritative status and show localized feedback.
- Replace touched queue-header literals with
chatnamespace keys. - Keep compact hover controls on pointer devices. On coarse-pointer viewports, make remove, clear, close, expand, merge, and run-next actions at least 44px high/wide and always visible.
General settings page
- Add API types and fetch/update helpers for the configured/effective response.
- Add a client settings card with a numeric
min=0input, explicit unlimited help, source badge/help, loading/error states, environment lock state, and member read-only state. - Register the draft with
useSettingsSaveContributor; do not add local Save/Cancel controls. - Add
/settings/general/message-queueto the Vite route registry, app-page composition, General sidebar, breadcrumb label map, and sidebar-navigation tests. Redirect the former/settings/system/message-queueURL to preserve existing bookmarks. - Add all new copy to translation catalogs and regenerate/check pseudo locale.
Avoid module-scope
t()by storing label keys and translating during render.
Responsive and Mobile Contract
The nearest shipped queue exemplar is
apps/web/e2e/tests/chat/mobile-message-queue-management.spec.ts: retain its inline
panel and single queue scroll region so the composer stays visible. The new
controls must not depend on hover and must meet the 44px coarse-pointer target.
The settings page uses the existing left-side mobile Settings sheet and shared
settings-scroll-container. Render one column with no nested page scroller;
keep the numeric field at least 44px high and above the existing save bar plus
safe-area padding. Desktop and mobile expose identical values and permissions.
TDD and Verification Strategy
- Change existing backend tests to expect agent/workflow/server pending rows to be removable and add in-flight/race/security regressions. Run them RED against unchanged production code.
- Implement cancellation and handler authorization; run memory, SQLite, and env-gated Postgres package tests GREEN.
- Add capacity resolver/store/service/handler tests, including live apply, environment precedence, lower-than-current behavior, and retry restoration. Run RED, then implement and rerun GREEN.
- Add frontend component/hook/settings tests first, including split edit/remove permissions and environment/member locks. Implement minimal UI changes, then run typecheck, lint, and i18n gates.
- Add desktop and mobile Playwright scenarios for mixed/agent provenance and settings navigation/save. Preserve and restore the install-wide cap in test teardown so parallel specs cannot inherit modified state.
- Update public queue/configuration/operations docs and run their validators.
Implementation Waves
- Task 01: Make pending queue cancellation authoritative — wave 1
- Task 02: Add live install-wide queue capacity settings — wave 2, after Task 01
- Task 03: Expose removal for every visible queue row — wave 3, after Task 01
- Task 04: Add the Message Queue General settings page — wave 3, after Task 02
- Task 05: Prove end-to-end behavior and update public docs — wave 4, after Tasks 03 and 04
Tasks 03 and 04 are file-disjoint enough to run in parallel after their backend dependencies, but this package does not authorize subagents. Implementation remains in the user-controlled primary session unless the user later delegates.
Environment Prerequisite
If apps/node_modules is absent, run pnpm install --frozen-lockfile from
apps/ before frontend RED tests. Do not change the lockfile.
Risks and Boundaries
- A clear/remove request may lose to reservation; that message can still be delivered. The UI must reconcile rather than claim it cancelled in-flight work.
- Dynamic lowering must not make restore or lifecycle retry lossy.
- Settings tests mutate install-wide state and must restore their baseline even after failure.
- Queue contents from the diagnosed live Bitbucket task remain untouched until this package is implemented and deployed.
- Editing backend-origin content, changing merge rules, queue reordering, per-workspace limits, and automatic pruning remain out of scope.