# Upgrade Guide — vNEXT

<!-- assembled-by: assemble-next-md -->
<!-- bump: patch -->

## What Changed

Secret Drop's stored-submission lifetime was reworked so a submitted secret can no
longer be deleted out from under an active consumer.

- **Server (`SecretDrop.ts`)** — the post-submission store now uses a **sliding idle
  window** instead of a fixed 5-minute timer. The cleanup timer re-arms on every
  `peekReceived`, so a secret that is actively being retrieved never expires. The idle
  window is 15 minutes (was 5) and a new per-token `receivedDeadline` enforces a hard
  **30-minute absolute cap** (`armReceivedCleanup` clamps each re-arm with
  `Math.min(idle, deadline − now)`), so even a continuously-polled secret is purged.
  `consumeReceived`/`shutdown` clear the new deadline map; the stuck-consumer event's
  `minutesUntilCleanup` is derived from the deadline.
- **Consumer (`secret-drop-retrieve.mjs`)** — new `--run -- <cmd>` mode performs an
  **atomic use-and-consume**: it pipes the field value to the command's stdin and
  consumes the submission only if the command exits 0. A failed or launch-error
  handoff never destroys the secret, making consume-on-failure structurally impossible.
- **Awareness/migration** — the CLAUDE.md scaffold documents `--run` for new agents;
  an idempotent `PostUpdateMigrator` block adds the guidance to existing agents that
  already have the hardened helper. The helper script itself ships via the existing
  always-overwrite relay-script refresh.

## What to Tell Your User

- **Secret Drop is now sturdier**: "If you hand me a password or key through a secure
  link, it won't quietly expire while I'm in the middle of using it anymore — so you
  shouldn't have to send the same thing twice. And if a step fails, I keep what you
  sent so we can retry, instead of throwing it away." (Note: a secret still lives only
  in memory, so a server restart still clears anything not yet used — that's the
  security trade-off, unchanged.)

## Summary of New Capabilities

| Capability | How to Use |
|-----------|-----------|
| Sliding retrieval window + 30-min cap | automatic |
| Atomic use-and-consume | `secret-drop-retrieve.mjs TOKEN field --run -- <cmd>` |

## Evidence

**Reproduction (observed live, 2026-06-02):** during a multi-step credential handoff
(Bitwarden login, then a GitHub token) the operator had to resubmit the same secret
roughly six times. Each time, the submission sat in the store while a multi-step
retrieval ran; the fixed 5-minute timer deleted it mid-flow regardless of the active
retrievals, and a standalone consume fired after a step that had not yet succeeded —
destroying the secret with no recovery but resubmission.

**Before/after (deterministic, fake-timer unit tests in
`tests/unit/SecretDrop.test.ts`):**
- Before this change, a submission was unconditionally deleted 5 minutes after
  submit. After it: a submission peeked at 14 min and again at 28 min is still present
  (the window slid on each read); an untouched submission is still cleaned up after the
  idle window; and a submission polled every 10 min is nonetheless purged once past the
  30-min absolute cap.
- `--run` mode: consume is invoked only on child exit 0; on non-zero exit or a launch
  error the submission is left intact (verified by inspecting the spawnSync branch and
  the documented `gh auth login --with-token` example).

Suites: SecretDrop 37/37, migration 15/15 green; `tsc --noEmit` clean. An independent
second-pass reviewer concurred with the side-effects review.
