# Deploy queue — coalescing, visible, no timeout deaths

audience: AI coding agents first.

status: DONE
task IDs: #1 Complete deploy queue handoff; #2 Restore live deploy queue behavior; #4 Fix deployment safety-check hang
source request: 2026-08-12 session `ses_00bc3eb05ffe7YKXeLedypuXR2` (lost to opencode crash, recovered from opencode.db): "many jobs waiting for the shared deploy lock — how can I see the queue?" → "industry standard? how does ours differ?" → "foss tool to adopt?" → "wire it up, transparent to the agents? nobody needs further instructions? anything changes in the skill file deploy?" → "go".

## Outcome

`packaging/deploy-local.sh` callers get queue semantics with ZERO caller/instruction changes. Requests persist as files, coalesce (N pending = converge to `origin/main@HEAD`, supersede intermediates), are visible via `ls`, and never die at a silent 1800s `flock` timeout while a deploy is succeeding.

## Design (owner-approved in lost session)

1. **In-script transparent queueing.** Script tries `flock -n` first:
   - Lock free → holder: snapshot queued requests, deploy, then on SUCCESS delete exactly that snapshot.
   - Lock held → requester: create `$QUEUE/req-<pid>-*`, poll for successful deletion, or acquire lock and become holder. Timeout fails with request path; request PERSISTS for next drainer.
2. **systemd user pair as drain backstop** (no caller alive → requests still drain):
   - `packaging/overdeck-deploy.path`: `DirectoryNotEmpty=%h/.local/share/overdeck/deploy-queue`.
   - `packaging/overdeck-deploy.service`: `Type=oneshot`, `TimeoutStartSec=0`, runs deploy-clone `deploy-local.sh` as a normal caller.
   - Installed + `enable --now` by deploy-local.sh itself.
3. **flock stays** as the mutex (dpkg/apt pattern); queue adds persistence+visibility+coalescing, NOT a second lock.
4. Queue dir default: `${OVERDECK_DEPLOY_QUEUE_DIR:-${DEPLOY}-queue}` = `~/.local/share/overdeck/deploy-queue`.

## Acceptance criteria

- [ ] Two concurrent script runs: one holder, one requester; requester exits 0 after holder's successful drain; queue dir empty.
- [ ] Requester file survives a FAILED deploy (fail-closed persistence); next successful run drains it.
- [ ] Holder drains pre-existing queue files on success.
- [ ] `.path`/`.service` units installed+enabled by deploy; service REQUEST_ONLY mode enqueues and exits 0 under contention.
- [ ] DRY_RUN output lists queue install/request steps.
- [ ] `bash packaging/test-deploy-local.sh` green incl. new queue cases.
- [ ] od-deploy SKILL.md: queue section (see queue = `ls` dir; coalescing; no caller change). AGENTS.md Landing: one line if contract text changes.

## Preserved WIP

None — lost session wrote nothing (session_diff empty; died at plan-entry step).

## Constraints

- Fail-closed everywhere; never eat a request silently. Deletion of request files ONLY after a fully successful iteration whose fetch postdates their snapshot.
- Whole-script exclusive section unchanged; flock mutex unchanged.
- No new daemons beyond the systemd user pair (already the OS).
- KISS: `ls $QUEUE` IS the queue UI. No status subcommand.

## Execution steps

1. This plan + INDEX row.
2. TDD: new `packaging/test-deploy-queue.sh` (faked env per test-deploy-local.sh pattern).
3. Implement queue block in deploy-local.sh + two unit files + install step + DRY_RUN lines.
4. Tests green; od-deploy skill + AGENTS.md doc deltas.
5. Land via ship.sh; deploy (self-hosting: this deploy installs the units); live proof: contended run coalesces.

## Current receipt

2026-08-12 — DONE. Queue implementation and both safety-check repairs landed and deployed. Final deployment reported `status=deployed` at `cb586d27`; the web release was reused because application code was unchanged. Installed proof: `overdeck-deploy.path` is active, queued request count is zero, and the installed safety checker completes in 19 seconds rather than hanging. Its exit 3 reports pre-existing uncommitted safety-tool copies in other worktrees; those belong to other active work and were not modified. Focused safety-check suite: `PASS=16 FAIL=0`.

## Next executable action

None.
