Compliance seal must end within its 30-min transcript cap
## Defect
The epic's quality bar (C4): "a seal that silently never happens (no row, no live workflow, no failed reason) is a FAIL — every done task must end in sealed or an explained failed/not_sealed", and AC1 (operator decision I1): sealed **within 30 minutes of done**, with any unsettled transcript recorded as `pending`.
On dev this does not hold for a task whose transcripts never settle.
**Reproduction (observed, read-only):**
- Dev task #4938 (`Add .DS Store line to .gitignore`, a MicroVM runner task with a merged PR) reached Done at 08:22:16 UTC on 2026-10-12.
- At 09:12 UTC (50 min later) its Details tab still reads "Compliance manifest — Waiting for 3 session transcripts (up to 30 min)." Seen by the finisher of Task 5926, and before that by Task 5925 at 09:10.
- Task 5925 also saw that Audit › Workflows shows `task-done-4938-0` SUCCESS (its `compliance_seal` action ran at 08:22:24). `compliance-seal-4938-0` was **PENDING** with `recovery_attempts: 14` and its last `dequeued_at` was 09:00:18. Dev was redeployed several times in that window.
**Cause:** `apps/api/app/durable/compliance_seal.py:422-430` (`compliance_seal`) limits the wait by counting `waited += POLL_SECONDS` per `DBOS.sleep`. Nothing ties the cap to wall-clock time. A run that is re-dequeued or recovered again and again (14 recovery attempts here) never reaches the cap. Also, once `recovery_attempts` passes DBOS's maximum, the run stops with no `compliance_manifests` row. The `compliance-seal-sweep` cron can't rescue it, because the deterministic workflow id `compliance-seal-<task>-<seq>` makes a re-enqueue a no-op. First confirm why this run keeps recovering, from its workflow steps/events in Audit › Workflows and the API logs. Then fix it, and say in the PR which of the two causes it was.
## Fix (no new surface)
1. Compute a wall-clock deadline once, inside a recorded `@DBOS.step`, so replay does not change it. Transcript wait: `tasks.done_at` (or the workflow's first start, for a Regenerate) plus `compliance_seal_transcript_wait_minutes`. Epic wait: plus `compliance_seal_epic_wait_minutes`. Each loop turn checks `now() < deadline` through a step, instead of the counted `waited`. A run recovered after the deadline seals straight away, with unsettled transcripts recorded honestly as `pending` (the existing at-cap path).
2. If root-causing shows the run crashes on each recovery (rather than only being restarted by redeploys), fix that crash at its source.
3. A seal past its deadline plus a grace period must end in a `failed` row with a plain reason, never a silent PENDING. Use the existing `record_failure` path, from the sweep: when a done task's current-seq seal is still live long after its deadline, record the failure and bump the seq so a fresh run can be offered.
## Out of scope
Do NOT change the payload/envelope/HTML (C3), signing/Terraform (C1), daemon PR capture (C2), the status/download/verify route contracts, or the web UI. Do NOT add auto-reseal after the cap (AS-A) or retro-seal historical tasks. Do NOT touch the transcript upload pipeline. Why #4938's three transcripts stay `uploading` is a separate question; name it in the PR if you find the cause.
## Done means
- Test: the seal workflow is restarted (simulated recovery) many times across the cap. It seals no later than `done_at + cap` (plus one poll), with the unsettled session recorded `pending`.
- Test: a seal whose current-seq run is still live well past its deadline is turned into a `failed` row with a readable reason by the sweep. The task's status route then reads `failed` (or `sealed` after the fresh run), never `waiting`.
- Existing compliance seal/route tests stay green; `./dc check full` green; the PR's `Jaah Deploy` run succeeds (fix and merge again if not).
- Live on `https://app.dev.jaah.ai` (credential `jaah/debug/main/operator-login`), after deploy:
- #4938's Details tab shows either "Sealed vN · <time>" or a failed state with its reason and Regenerate. Not "Waiting".
- A manifest generated for #4938 lists its sessions with model and transcript status. Its "Changes shipped" section shows the PR's merge commit and files. That is AC3's / C2's still-open live leg: the PR provenance of a task finished after #5132. Screenshot + downloaded JSON in the report.
Outcome
Seal cap is now wall-clock (deadline recorded in a step). The sweep fails a run still PENDING past its deadline. Cause: orphaned or restarted runs, not a crash. Full gate and deploy green. #4938 shows Sealed v2. C2 PR-provenance live leg still open: no dev task has merged a PR since C2.
Artifacts
Activity
- Ready
- Runner started
- Runner ended (done)