# Upgrade Guide — vNEXT

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

## What Changed

The pre-messaging quality gate's "commitment overreach" pattern matched the
bare sequence `i (promise|...)` anywhere in a message — including INSIDE other
words. "the Mini promises" (a word ending in *i* followed by a plural noun) and
"everything I promised" (past-tense narration) both tripped it; in one day it
blocked five legitimate status reports that contained no promise at all.

The pattern (all three copies: the shipped shell template, the migrator's
inline fallback, and the TypeScript ConvergenceChecker) now requires the
leading *i* to start a word and refuses to match when "promise" continues into
promised/promises. Real first-person commitments ("I promise to…",
"I'll make sure…", "you can count on me to…") still trip exactly as before.

The quota collector (which writes quota-state.json) and the QuotaManager that
drives its adaptive polling were nested inside the server's
"I own Telegram polling" block. On any agent whose LIFELINE owns polling — the
normal production topology — that block is skipped, so the collector never ran:
quota-state.json was never written, the tracker read an absent file, and
quota-aware placement stayed permanently fail-open (the exact rate-limit-stall
hazard from the EXO incident it exists to prevent). Turning on quotaTracking
alone could not fix it; the writer was gated behind an unrelated condition.

The account switcher + collector + QuotaManager pipeline now live at top scope
(after the scheduler is constructed, so migration wiring is real), gated only on
the quota tracker — so the collector runs regardless of who polls Telegram.

## What to Tell Your User

Nothing — fewer spurious "message blocked" interruptions when the agent
mentions promises as a topic rather than making one.

- audience: agent-only
- maturity: stable

If you run a lifeline-driven agent and turned on quota tracking but never saw
quota data, this is why — now it collects. Quota-aware placement (routing work
away from a rate-limited machine) actually has data to act on.

- audience: agent-only
- maturity: stable

## Summary of New Capabilities

- Word-boundary guards on the commitment_overreach pattern in
  `templates/scripts/convergence-check.sh`, the PostUpdateMigrator inline
  fallback, and `ConvergenceChecker.ts` — kept in lockstep.
- Existing agents receive the fixed script automatically (convergence-check.sh
  is always-overwritten from the template on every migration pass).

- Quota collection + adaptive polling run independent of Telegram-polling
  ownership (was send-only-server dead).
- accountSwitcher hoisted to top scope (still consumed by the polling block's
  wireTelegramCallbacks when that path runs).

## Evidence

- `tests/unit/convergence-check.test.ts` (+6): the five live false positives
  pass; real present-tense promises, sentence-final "I promise.", and the
  other commitment phrasings still flag. 61/61 in file (runs the REAL script).
- `tests/unit/convergence-checker-commitment-boundary.test.ts` (new): the same
  both-sides matrix against the TS checker.
- Pattern verified on macOS (BSD) grep -E with 12 probe strings before patching.

- `tests/unit/quota-collector-polling-independence-wiring.test.ts` (new, 3
  tests): quotaManager.start() is reached before the !lifelineOwnsPolling
  block; the pipeline is constructed after the scheduler; no second
  QuotaManager construction (no double-start).
- Live: echo logs "Telegram send-only mode (lifeline owns polling)" + "No quota
  state file found — fail-open" pre-fix; quota-state.json + real GET /quota data
  post-fix (live re-verify after release).
