# Typed deploy readiness with monotonic deadlines — request

**Owner-visible evidence first:** the installed deploy log names `collector=ready`,
`controller=ready`, `botmaster=ready`, and `web=ready`, reports elapsed readiness time against a
300-second deadline, and final JSON reports collector HTTP 200. A deterministic fake-clock test
proves timeout occurs at the deadline rather than after a fixed number of attempts.

**Goal:** replace attempt-count deployment smoke with service-specific response validation under a
single monotonic deadline. An active unit or arbitrary HTTP response is not readiness.

**Files:**
- Create `packaging/lib/readiness.sh` — reusable monotonic deadline loop for deploy-local.
- Create `packaging/deploy-readiness.sh` — one typed installed-runtime probe pass.
- Modify `packaging/deploy-local.sh` — deadline configuration, gateway typed retry, and the
  multi-service readiness command used by web release activation/rollback.
- Modify `packaging/web-release.sh` — deadline-based verification instead of attempt count.
- Modify `packaging/test-deploy-local.sh` to preserve the real eval/deadline seam.
- Add `packaging/tests/typed-readiness.test.sh`.

**Contracts:**
1. Production deadline defaults to 300 seconds; interval defaults to 3 seconds. Test overrides are
   explicit. No attempt-count setting remains.
2. Deadline time comes from Linux monotonic uptime (`/proc/uptime`), never wall clock. The probe
   runs immediately. Between failures, sleep is capped to remaining deadline. A currently-running
   probe may finish at most its own bounded network timeout after the deadline.
3. One readiness pass validates:
   - collector: authenticated `GET 127.0.0.1:31338/health`, exact HTTP 200, JSON object with
     `ok === true`;
   - controller: authenticated `GET 127.0.0.1:8787/health`, exact HTTP 200, JSON object with
     `ok === true`;
   - botmaster: `GET 127.0.0.1:31340/api/bots?limit=1`, exact HTTP 200, JSON array;
   - web: the existing watchdog sweep, which requires exact HTTP 200 from every concrete route.
   The shared token is read from `~/.config/overdeck/token`; it is never printed or embedded in a
   command string.
4. Every failed pass emits one bounded named summary into the existing per-attempt smoke log. A
   successful pass emits the four ready names and elapsed/deadline evidence.
5. Web candidate and rollback verification use the same deadline loop. Keep the command
   subshell-wrapped and eval'd in-process; no bare exit outside that subshell.
6. Actions Gateway readiness retries its authenticated R0 smoke (not merely `systemctl is-active`)
   under the same monotonic deadline. Candidate rollback behavior remains unchanged.
7. Existing restart/kept accounting and durable queue acknowledgement ordering remain unchanged.

**Acceptance:**
- Fake-clock tests cover immediate success, transient typed failure then success, exact deadline,
  sleep capped to remaining time, wall-clock independence, wrong status/body/type rejection, and
  token redaction.
- Real-process fixture starts delayed collector/controller/botmaster endpoints and a web watchdog
  stub; the production scripts pass only after all four typed responses are correct.
- `bash -n` on changed shell files and `git diff --check`.
- Existing deploy queue, idle-work, collector gate, and web-release tests stay green.
- Installed deployment proof through `deploy-local.sh --now`; no synthetic outage on the owner
  machine.

## Installed proof

Landed and installed at `c8c66f660277bcf93d8a24f8305c034db6900c68`. The forced full deploy used the installed
entrypoint and converged in 79,560 ms under its 300,000 ms monotonic deadline after bounded,
named transient failures:

```text
readiness: collector=ready controller=ready botmaster=ready web=ready
{"component":"web-release","status":"ready","elapsed_ms":79560,"deadline_ms":300000}
{"stage":"deploy-local","status":"deployed","sha":"c8c66f66","collector_http":"200",...}
```

Evidence log: `~/.cache/overdeck/typed-readiness-forced-installed-1786885719.log`.
Actions Gateway remained intentionally dark because owner configuration is absent; its typed R0
retry path is fixture-proven but was not exercised live.
