# Upgrade Guide — vNEXT

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

## What Changed

Added the Durable Inbound Message Queue + Hold-for-Stability policy (CMT-1118,
spec `docs/specs/durable-inbound-message-queue.md`, 10-round convergence). When
the multi-machine router cannot deliver an inbound message right now (its
conversation is mid-move, or the owning machine is briefly unhealthy), the
message now goes into a crash-proof on-disk queue (`PendingInboundStore`,
SQLite `synchronous=FULL`, 0600) instead of being injected into a possibly-stale
local session or relying on platform resend — and a suspect-but-alive machine
gets up to 90 seconds to recover before its conversation is moved (fewer
machine swaps on 5-second blips). The drain engine carries the full reviewed
safety set: receipt-before-inject at-most-once acting, transactional
emergency-stop fences, queued-rows-only pause with cumulative caps, tenure
clamps (acquisition-generation, never the renewal epoch), six boot-validated
config invariants (violation = queue stays OFF, loudly), P19 brakes with an
Eternal-Sentinel declared tick, and every loss window reported — never silent.
New Bearer route `GET /pool/queue` (503 while dark); capacity heartbeats gain
queue depth/tenure/top-K; guard manifest + dark-gate registrations included.
**Ships DARK**: `multiMachine.sessionPool.inboundQueue` defaults
`enabled:false, dryRun:true`; hold-for-stability trails one rollout stage.

## What to Tell Your User

Nothing changes today — this ships off by default. Once enabled (staged rollout,
dry-run first): messages that arrive while your agent's conversation is moving
between machines can no longer be lost or delivered to the wrong place — they
wait in a small crash-proof queue and deliver in order when the move finishes.
And a machine having a brief network blip gets ~90 seconds to recover before
its conversations are moved off it, so you'll see far fewer pointless machine
swaps. If anything ever can't be delivered, you get one plain notice naming
what to resend — never silence.

## Summary of New Capabilities

- `GET /pool/queue` — queue counts (queued/claimed/held/frozen, delivered24h
  which excludes possibly-not-injected), durable counters (incl. dry-run
  `wouldEnqueue`/`wouldHold` promotion evidence, `possiblyNotInjected`,
  `holdBypassedByAttemptsCap`), hold/flap state, tenure. 503 while dark.
- Capacity heartbeats: `inboundQueue` block (depth, oldest, tenure, top-K) —
  feeds the survivor loss-suspected item + capped session recovery when a
  machine dies holding messages.
- `DeliverAck` gains the typed `sender-rejected` NACK (receive-side authz
  re-validation of carried sender envelopes).
- Guard posture: `multiMachine.sessionPool.inboundQueue.enabled` +
  `multiMachine.sessionPool.holdForStability.enabled` rows in `/guards`
  (hold's getter reports the EFFECTIVE state — orphaned config derives
  off-runtime-divergent).

## Evidence

- Unit: `pending-inbound-store.test.ts` 32/32 (tri-state, bounds + carve-out
  boundary, AUTOINCREMENT across prunes, claim CAS, eviction-skips-claimed,
  payload hygiene + locator retention, conditional receipt fence, tenure
  derivation incl. A→B→A, cumulative pause accounting across restart, handle
  encapsulation, 0600); `queue-drain-loop.test.ts` 35/35 (dispositions incl.
  delivered-unconfirmed, stop-fence both interleavings, pause scope round-9,
  hold budget across episodes, herd cap, maxHeldTotal degrade, maxAttempts
  forced re-place both arms, tenure clamps, wake clamps, Eternal-Sentinel
  episode latch, mirror reconciliation, P19 sustained-failure hour);
  `inbound-queue-config.test.ts` 10/10 (all six invariants, defaults parity
  with ConfigDefaults); `inbound-queue-boot-sweep.test.ts` 13/13 (gate-expiry
  ×4 reasons, crash-table rows 1–3, stop-scoped PIS veto, pause-aware
  recovery, quarantine + expiry).
- Integration: `inbound-queue-route.test.ts` 3/3 (ships-dark 503, feature-alive
  200 over real HTTP + real engine + real SQLite, delivered24h exclusion).
- E2E: `inbound-queue-lifecycle.test.ts` 2/2 (production-init order: sweep →
  invariants → engine adopts swept store → drain → route truth; and the
  production-default disabled boot: gate-expiry + 503).
- Lints: `lint-dev-agent-dark-gate` clean (both flags registered);
  `lint-guard-manifest` clean (components listed).
