@llblab/pi-kit 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
  4. package/node_modules/@llblab/pi-telegram/BACKLOG.md +13 -32
  5. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
  6. package/node_modules/@llblab/pi-telegram/README.md +4 -3
  7. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -2
  8. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +3 -5
  9. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +1 -1
  10. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +93 -9
  11. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +9 -3
  12. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +231 -71
  13. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +4 -0
  14. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +34 -7
  15. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +63 -2
  16. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +3 -0
  17. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +13 -0
  18. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -0
  19. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +25 -4
  20. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +1 -0
  21. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +32 -12
  22. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +10 -0
  23. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +22 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +2 -2
  25. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +1 -1
  26. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -2
  27. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +9 -0
  28. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +41 -1
  29. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +9 -1
  30. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +23 -3
  31. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +7 -2
  32. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +2 -0
  33. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +68 -37
  34. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +6 -4
  35. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +134 -61
  36. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +9 -0
  37. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +49 -3
  38. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +77 -6
  39. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +384 -8
  40. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +3 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +6 -0
  42. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  43. package/node_modules/@llblab/pi-telegram/docs/architecture.md +21 -14
  44. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +31 -10
  45. package/node_modules/@llblab/pi-telegram/docs/updates.md +2 -0
  46. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +5 -7
  47. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +69 -12
  48. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +236 -94
  49. package/node_modules/@llblab/pi-telegram/lib/bus.ts +39 -9
  50. package/node_modules/@llblab/pi-telegram/lib/extension.ts +69 -2
  51. package/node_modules/@llblab/pi-telegram/lib/journal.ts +14 -0
  52. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +23 -4
  53. package/node_modules/@llblab/pi-telegram/lib/locks.ts +32 -13
  54. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -1
  55. package/node_modules/@llblab/pi-telegram/lib/polling.ts +2 -2
  56. package/node_modules/@llblab/pi-telegram/lib/queue.ts +2 -3
  57. package/node_modules/@llblab/pi-telegram/lib/sync.ts +45 -1
  58. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +30 -2
  59. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +11 -3
  60. package/node_modules/@llblab/pi-telegram/lib/threads.ts +73 -36
  61. package/node_modules/@llblab/pi-telegram/lib/updates.ts +145 -78
  62. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +67 -4
  63. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +465 -7
  64. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +7 -0
  65. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  66. package/package.json +2 -2
@@ -64,17 +64,17 @@ The repository uses a **Flat Domain DAG**:
64
64
  - `lib/extension.ts`: composition root for live ports, domain runtime construction, cross-domain port wiring, and lifecycle registration. It exposes wiring but owns no process identity, journal-binding selection, mutable late-binding state, admission lifecycle selection, reusable policy, or low-level adapter mechanics; those belong to `bus`, `journal`, `prompts`, `activity-verbosity`, `updates`, and other named domains.
65
65
  - `api`: Bot API helpers, retries, uploads/downloads, temp cleanup, byte limits, chat actions, lazy token clients, and API error recording. Its optional Workspace-admission client classifies valid threaded requests exactly, chat-only requests chat-wide, malformed declared targets profile-wide, and targetless methods as outside the fence; JSON and multipart leases span internal retries through final settlement. Admission blocks prevent issuance, while release errors remain protective diagnostics and never convert an already-settled non-idempotent request into replay. Production composition resolves this adapter from the current bot/profile admission runtime.
66
66
  - `config` / `setup`: `telegram.json`, bot token setup, named bot/session profiles, first-user pairing, authorization, env fallback, atomic persistence, effective config views, and live config accessors.
67
- - `locks` / `polling`: extension-local transport owner storage, exact-owner epoch exposure, process-global reload generations, owner-aware polling lifecycle/takeover/follower registration, and classic-vs-Threaded capability orchestration. Polling owns long-poll state, worker-before-poller startup, strict batch validation, journal-before-offset admission, one offset commit per response, and non-awaited worker signaling.
67
+ - `locks` / `polling`: extension-local transport owner storage, exact-owner epoch exposure, process-global reload generations, completed-generation suspension evidence, owner-aware polling lifecycle/takeover/follower registration, and classic-vs-Threaded capability orchestration. Polling owns long-poll state, worker-before-poller startup, strict batch validation, journal-before-offset admission, one offset commit per response, and non-awaited worker signaling.
68
68
  - `journal`: private profile/bot-scoped raw-update authority with strict v1 identity/schema validation, exact deduplication, bounded transaction-serialized `0600` publication, process/session/acquisition-bound prompt/control receipts, owner-fenced completion, durable retry state, and generic removal that rejects queued or legacy failed authority. Runtime/recovery identity separates token rotation from future proof-gated queue-owner recovery. Read-only follower-journal discovery enumerates canonical profile-exact snapshots and segment roots and reports incomplete evidence for unexpected matching paths. Its optional Workspace-admission adapter conservatively classifies each append batch as exact-target, chat-wide, or profile-wide, holds all leases through publication, and releases acquired partial batches on rejection; production journal bindings resolve that adapter for leader, follower, recipient, and historical-path stores.
69
- - `bus` / `bus-api` / `bus-leader` / `bus-follower` / `ownership` / `target`: Threaded Mode multi-instance bus contracts, profile-scoped process/endpoint identity, local leader/follower IPC, leader-only orchestration, follower-side manual registration/session runtime, follower-routed Bot API calls, live message ownership, and `{ chatId, threadId? }` target identity. `bus` owns protocol v1 independently from package build, canonical capabilities, compatibility, process identity, profile-aware endpoints, and IPC primitives. Registration rejects missing/mismatched protocol before provisioning while preserving compatible package skew; negotiated identity reaches status/state. Leader runtime, leader envelope handling, follower assembly, and follower registration construction require explicit protocol identity, preventing identity-less composition at both production and low-level runtime boundaries. `bus-leader` owns leader envelope handling and polling/server/prune orchestration; its Workspace-admission assembly holds a chat-wide lease across leader provisioning and profile-wide leases across follower provisioning/registration, disconnect/dead cleanup, leader/follower rename, display preference/reconciliation, and detached post-provision cleanup through its delayed API/store settlement. It consumes the profile operation runtime shared with Sync and routing; admission precedes that process-local mutation gate, fence rejection occurs before mutation, and release follows complete settlement. `bus-follower` owns registration/ack negotiation, active leader-auth/election state, heartbeat, authenticated clients, forwarded receiving, and recovery. Its production assembly holds profile-wide admission across follower-to-leader promotion and acknowledged target replacement, so a retained fence rejects both before leadership acquisition or store mutation. Production leader composition resolves the admission assembly at each operation boundary.
70
- - `sync`: demand-driven Telegram reconciliation, mutable sync-slice state, nested provisioning activity, and local assumption policy. It does not own a complete Telegram bot read-model; Bot API lacks a complete topic/thread listing surface. It owns sync slices, invalidation triggers, config-persist invalidation sequencing, exact-target admitted stale-topic API recovery, observation intake, status/debug freshness, paired manual-disconnect/session-restart cleanup assembly, and reconciliation scheduling across bot identity, pairing assumptions, live target bindings, reservations, and transport health after meaningful observable signals. Production topic lifecycle and disconnect/restart cleanup enter profile-wide admission before the shared Workspace operation gate. Lifecycle admission spans observation-driven store settlement; cleanup admission spans intent publication, Telegram cleanup, binding mutation, durable settlement, and transport release. It should call narrower domain primitives rather than letting `index.ts`, `threads`, or `status` accumulate cross-cutting reconciliation policy.
69
+ - `bus` / `bus-api` / `bus-leader` / `bus-follower` / `ownership` / `target`: Threaded Mode multi-instance bus contracts, profile-scoped process/endpoint identity, local leader/follower IPC, leader-only orchestration, follower-side manual registration/session runtime, follower-routed Bot API calls, live message ownership, and `{ chatId, threadId? }` target identity. `bus` owns protocol v1 independently from package build, canonical capabilities, compatibility, process identity, profile-aware endpoints, IPC primitives, and replacement-invalidated non-routing registry observations. Registration rejects missing/mismatched protocol before provisioning while preserving compatible package skew; negotiated identity reaches status/state. Leader runtime, leader envelope handling, follower assembly, and follower registration construction require explicit protocol identity, preventing identity-less composition at both production and low-level runtime boundaries. `bus-leader` owns leader envelope handling and polling/server/prune orchestration; its Workspace-admission assembly holds a chat-wide lease across leader provisioning and profile-wide leases across follower provisioning/registration, disconnect/dead cleanup, leader/follower rename, display preference/reconciliation, and detached post-provision cleanup through its delayed API/store settlement. It consumes the profile operation runtime shared with Sync and routing; admission precedes that process-local mutation gate, fence rejection occurs before mutation, and release follows complete settlement. `bus-follower` owns registration/ack negotiation, active leader-auth/election state, heartbeat, authenticated clients, forwarded receiving, and recovery. Its production assembly holds profile-wide admission across follower-to-leader promotion and acknowledged target replacement, so a retained fence rejects both before leadership acquisition or store mutation. Production leader composition resolves the admission assembly at each operation boundary.
70
+ - `sync`: demand-driven Telegram reconciliation, mutable sync-slice state, nested provisioning activity, and local assumption policy. It does not own a complete Telegram bot read-model; Bot API lacks a complete topic/thread listing surface. It owns sync slices, invalidation triggers, config-persist invalidation sequencing, exact-target admitted stale-topic API recovery, observation intake, status/debug freshness, paired manual-disconnect/session-restart cleanup assembly, prepared quiescent leader-quit detachment, and reconciliation scheduling across bot identity, pairing assumptions, live target bindings, reservations, and transport health after meaningful observable signals. Production topic lifecycle and disconnect/restart cleanup enter profile-wide admission before the shared Workspace operation gate. Lifecycle admission spans observation-driven store settlement; cleanup admission spans intent publication, Telegram cleanup, binding mutation, durable settlement, and transport release. It should call narrower domain primitives rather than letting `index.ts`, `threads`, or `status` accumulate cross-cutting reconciliation policy.
71
71
  - `thread-reconciler`: Threaded Mode control-plane planning for Telegram thread/tab lifecycle. It owns the reconciliation state machine (`stable`, `provisioning`, `sync-required`, `cleanup-required`), pure plans, proof-before-delete rules, pending-provision protection, fresh-creation grace windows, leader-epoch checks, and the single policy authority for destructive thread cleanup actions. It excludes live Telegram API calls, inbound routing, menu rendering, and direct persistence.
72
72
  - `thread-cleanup-manager`: Disconnected proof-only admission planning for manual inactive-Workspace cleanup. It emits exact profile/binding/target snapshots only when durable inactivity exists, all live-owner/accepted-work/delivery evidence is `clear`, identities are unique, and no reservation, provision, or cleanup competes. Missing, malformed, duplicate, or `unknown` evidence returns no candidates; age and ordering never create authority. Its disconnected bounded profile/token-scoped work store atomically persists exact candidate snapshots, records one exact Workspace-issued deletion permit as outcome-unknown, rejects mismatched permits, and confirms deleted state idempotently; strict reads reject malformed/private-file ambiguity. A disconnected executor requires an injected exclusive Workspace deletion boundary across fresh planner evidence, exact retained-snapshot comparison, permit acquisition/recording, delete callback, and confirmation. The future fence owner must acquire and recheck in admission-ledger order; a retirement fence cannot be nested inside an active ordinary admission lease. Regressions prove drift stops before permit, unavailable/already-issued state cannot fabricate authority, and ambiguous delete remains outcome-unknown without replay. A production-shaped adapter preserves full Workspace records through external-protection resolution, then snapshots exact cleanup fields, reservations, provisions, and cleanup intents; protection exceptions become `unknown`, while source failures propagate fail-closed. Production Settings exposes **Review inactive tabs** only: profile-wide admission surrounds fresh evidence capture, one canonical 128-bit-digest work-set is retained, and a separate summary reports proven count, explicit no-deletion state, and Back navigation. The exact confirmation callback fits Telegram's 64-byte bound and accepts only canonical work-set IDs. **Clean inactive tabs** renders only when composition supplies a destructive port; production intentionally omits it, so malformed/stale callbacks fail closed and review remains non-destructive. Permit composition must not call `acquireRetirementFence()` because retirement remains pressure-only. The one admission-ledger fence now carries discriminated `pressure-retirement | manual-thread-cleanup` authority, treats legacy missing kind as pressure, exposes `acquireThreadCleanupFence()`, includes kind in exact comparison/permits, and remains profile-singleton. Cleanup-specific adopt/issue/absence/release/complete APIs preserve existing retirement callers and reject cross-kind use. Review admission releases before cleanup-fence acquisition; that fence then spans exact full-record/protection revalidation, sole permit issuance, work-set recording, one delete attempt, absence confirmation, binding/work-set commit, and fence completion. The v1-compatible schema and kind-specific acquire/adopt/issue/absence/release/complete methods are implemented; pressure methods reject manual fences and cleanup methods reject pressure fences. A disconnected permit runtime acquires the cleanup fence, revalidates under it, releases drifted unissued fences, refuses already-issued replay, and retains `commit-ready` until an injected durable commit succeeds. `threads.commitInactiveWorkspaceCleanup()` now removes only an exact full inactive binding after rechecking local records, claims, reservations, provisions, cleanups, and retirement intents; the retained cleanup candidate carries sufficient exact cwd/workspace/instance/global-slot/binding/target/inactivity/update commit identity, and under the retained fence exact absence closes commit-unknown retry without reconstructing the deleted full binding. Commit composition removes the binding before confirming the work-set, and failure retains `commit-ready`. A hidden coordinator validates canonical review identity, resolves the full binding, records the sole permit before one injected delete call, then commits binding, work-set, and fence. A `commit-ready` retry finishes without another delete; ambiguous deletion remains `deletion-issued` and is never replayed. A typed Settings-port adapter exposes this coordinator only when explicitly supplied and reports deleted, outcome-unknown, and blocked counts. Fake-port tests exercise the callback lifecycle and successor recovery: takeover requires injected proof, exact fence identity is preserved, live/unverifiable predecessors fail closed, and `commit-ready` resumes without deletion replay. Cross-process workers prove stale `prepared` contenders call fake transport once: deletion requires successful work-set permit CAS, and redundant fences over deleted entries settle without replay. Work-set pre-rename failure and lost post-rename acknowledgement retain recoverable fence truth; durable `deleted` completes that fence without another transport call. Binding-snapshot pre-rename ownership loss reloads and restores the exact binding, while post-rename acknowledgement loss reloads durable absence as successful commit. Cleanup runtime composition additionally requires candidate/work-set/runtime/fence profile equality before mutation and captures one stable successor owner snapshot for adoption. The optional Settings port reports only redacted exact-authority recovery classes: commit pending, deletion outcome unknown with no retry, or unavailable authority. It exposes no target, path, token, or transport details. Production still omits `cleanInactiveThreads`, so Bot API activation remains separate.
73
73
  - `thread-display`: Shared Letters/Names/Directories projection, Settings-mode/manual-override coordination, initial-create title selection, and serialized leader-owned title application. Owns bounded path disambiguation, ambiguous-label rejection, and profile/mode/epoch plus captured live-binding fences. Acknowledged `displayTitle` remains distinct from stable `threadName`; the caller owns triggers and live-owner discovery. Leader provisioning applies the configured projection before creation; registration ACKs deliver the acknowledged title before initial follower status, and heartbeat ACKs carry later changes. Stable runtime names remain restoration identity rather than presentation. Current-thread/TUI display identity is separate from restoration identity. Live bot choosers, notices, prompt labels, and cross-instance agent-target resolution use acknowledged display titles while captured numeric targets and live registrations remain the routing authority. Settings routes direct-owner changes locally and follower changes through an authenticated capability-gated envelope. The leader serializes preference writes and application.
74
74
  - `workspace-slots`: Pure bounded global-letter selection and pressure-reclamation proposals. It consumes explicit protection/inactivity evidence and never discovers owners, persists state, or performs deletion.
75
- - `workspace-admission`: Durable profile-scoped reader/writer ledger for cross-process exact-target, chat-wide, and profile-wide admission leases plus one destructive retirement fence. It owns atomic lease/fence transactions, proven-dead process-birth recovery, conservative malformed/ambiguous-state handling, exact successor adoption, retained-slot projection, and the durable `fenced` → `deletion-issued` → `commit-ready` phases that emit at most one deletion permit. Its runtime binding resolves `workspace-admission[.<profile>].json`, stores only the token SHA-256 profile authority, preserves separate named-profile identities across switching, permits changed-token rebind only when the prior ledger is provably empty, and fails closed while foreign leases or a fence remain. Issued fences cannot be released before confirmed absence and durable retirement commit; callers own journal/API/provisioning operations and retirement policy. Production composition supplies admission to journals, JSON/multipart API, leader/follower mutations, topic lifecycle, reroute restoration/reclamation, manual disconnect/session-restart cleanup, exact stale-target recovery, and Thread-store slot reservations; journal-evidence pruning also requires caller-supplied admission. Common async runners and the API adapter reject concurrent reuse of a live operation ID before a second caller can share or release its lease; once the first invocation exits, retry-stable recovery remains available. A 2/2 same-model independent post-fix quorum verified complete production-mutation composition at 0.96 confidence per reviewer. Destructive retirement remains disconnected by release scope; this verification does not authorize live deletion or replace disposable operator smoke.
76
- - `workspace-retirement`: Profile/leader-fenced pressure preparation over the store snapshot. It counts standalone reservations, selects one candidate only at full slot capacity, rechecks protection, and persists/resumes an exact durable intent. Workspace bindings accumulate their historical follower-journal routing keys and distinguish complete fresh metadata from incomplete legacy evidence. Its read-only accepted-work policy resolves those binding-specific journals plus the shared leader journal and combines them with local exact targets, failing closed when source coverage or target decoding is incomplete. It can prune a known empty follower-journal key only from complete readable evidence plus explicit writer quiescence under exact binding/profile/epoch fences and an exact-target admission lease held through durable publication. Incomplete legacy bindings consume discovered hashed journals as target-scoped evidence but remain incomplete so every later retirement repeats discovery. The shared Workspace operation runtime serializes topic lifecycle, reroute restoration/reclamation, provisioning, delayed post-provision reconciliation, follower/manual cleanup, rename, and display mutation through one exposed gate. Detached mutation work must reacquire fresh admission rather than inherit a lease already released by its caller. A successor may durably adopt one exact stale-epoch intent after profile/binding/protection revalidation; direct old-epoch execution remains blocked. The isolated executor consumes that gate and requires the durable admission ledger. It acquires or exactly adopts the matching fence, rechecks protection after admissions close, advances to `deletion-issued` before invoking an executor-only `deleteForumTopic` port with the sole permit, and never reissues from that phase. Success or exact absence advances to `commit-ready`; store commit failure retains the fence, and exact completion follows durable binding+intent removal. A successor resolves an issued unknown outcome only through a separate exact-absence probe. Leader composition exposes exact registry, active/queued work, known journals, and profile-exact legacy discovery as protection evidence. Missing queue targets and incomplete reads remain unknown. The common direct Bot API client counts exact JSON/multipart targets until settlement; message-scoped edits/deletes without a thread conservatively protect every binding in their chat. Known historical follower owner keys decode to process-birth identity before liveness checks. Durable intents block matching claims and binding mutations. Preparation/adoption/execution remains disconnected from leader runtime by release scope. Independent review cleared the admission-composition blocker; live deletion and operator acceptance remain separate gates.
77
- - `threads`: Telegram UI thread/tab binding state mapped to Bot API `message_thread_id` / `ForumTopic` transport. Owns exact-`cwd` Workspace bindings and transient claims, first-proven inactivity metadata, fail-closed retirement occupancy snapshots, and exact durable retirement intents, leader/current-instance identity state, active-turn → follower → leader target preference, matching status projection assembly, profile-bound same-process handoff, exact-claim global-slot allocation and conservative missing/duplicate legacy migration, collision-safe compact thread-name selection, Workspace-aware rename persistence, and primitive provision helpers. Its optional external-slot source makes every generic allocation, Workspace claim, and occupancy snapshot reserve retained admission-fence slots; malformed, unreadable, or non-uppercase evidence fails allocation closed. Its synchronous provision-commit helper transfers exact targeted creation-title evidence into the claim-committed Workspace binding and consumes matching pending evidence; callers retain admission, epoch checks, and durable publication. It should not turn dormant bindings into routing authority, own destructive cleanup policy, or grow into the general Telegram synchronization domain.
75
+ - `workspace-admission`: Durable profile-scoped reader/writer ledger for cross-process exact-target, chat-wide, and profile-wide admission leases plus one destructive retirement fence. It owns atomic lease/fence transactions, proven-dead process-birth recovery, conservative malformed/ambiguous-state handling, exact successor adoption, retained-slot projection, and the durable `fenced` → `deletion-issued` → `commit-ready` or `deletion-rejected` outcomes; one fence emits at most one deletion permit. Its runtime binding resolves `workspace-admission[.<profile>].json`, stores only the token SHA-256 profile authority, preserves separate named-profile identities across switching, permits changed-token rebind only when the prior ledger is provably empty, and fails closed while foreign leases or a fence remain. Issued fences cannot be released before confirmed absence and durable retirement commit; callers own journal/API/provisioning operations and retirement policy. Production composition supplies admission to journals, JSON/multipart API, leader/follower mutations, topic lifecycle, reroute restoration/reclamation, manual disconnect/session-restart cleanup, exact stale-target recovery, and Thread-store slot reservations; journal-evidence pruning also requires caller-supplied admission. Common async runners and the API adapter reject concurrent reuse of a live operation ID before a second caller can share or release its lease; once the first invocation exits, retry-stable recovery remains available. A 2/2 same-model independent post-fix quorum verified complete production-mutation composition at 0.96 confidence per reviewer. Demand-driven retirement is now operator-authorized and wired to fresh allocation; disposable operator smoke remains distinct from local validation.
76
+ - `workspace-retirement`: Profile/leader-fenced pressure preparation over the store snapshot. It counts standalone reservations, selects one candidate only at full slot capacity, rechecks protection, and persists/resumes an exact durable intent. Workspace bindings accumulate their historical follower-journal routing keys and distinguish complete fresh metadata from incomplete legacy evidence. Its read-only accepted-work policy resolves those binding-specific journals plus the shared leader journal and combines them with local exact targets, failing closed when source coverage or target decoding is incomplete. It can prune a known empty follower-journal key only from complete readable evidence plus explicit writer quiescence under exact binding/profile/epoch fences and an exact-target admission lease held through durable publication. Incomplete legacy bindings consume discovered hashed journals as target-scoped evidence but remain incomplete so every later retirement repeats discovery. The shared Workspace operation runtime serializes topic lifecycle, reroute restoration/reclamation, provisioning, delayed post-provision reconciliation, follower/manual cleanup, rename, and display mutation through one exposed gate. Detached mutation work must reacquire fresh admission rather than inherit a lease already released by its caller. A successor may durably adopt one exact stale-epoch intent after profile/binding/protection revalidation; direct old-epoch execution remains blocked. The isolated executor consumes that gate and requires the durable admission ledger. It acquires or exactly adopts the matching fence, rechecks protection after admissions close, advances to `deletion-issued` before invoking an executor-only `deleteForumTopic` port with the sole permit, and never reissues from that phase. Success or exact absence advances to `commit-ready`; store commit failure retains the fence, and exact completion follows durable binding+intent removal. A successor resolves an issued unknown outcome only through a separate exact-absence probe. Leader composition exposes exact registry, active/queued work, known journals, and profile-exact legacy discovery as protection evidence. Missing queue targets and incomplete reads remain unknown. The common direct Bot API client counts exact JSON/multipart targets until settlement; message-scoped edits/deletes without a thread conservatively protect every binding in their chat. Known historical follower owner keys decode to process-birth identity before liveness checks. Durable intents block matching claims and binding mutations. Fresh leader/follower allocation now uses a serialized capacity wrapper: try ordinary allocation, release its leases on a typed capacity failure, then prepare/adopt/execute retirement under the shared mutation gate and retry once. Restore-only follower startup never evicts. The executor-only direct-client deletion port validates the exact permit and disables both retries and transport fallback. Strict journal protection reads cannot recover/reset evidence. A confirmed `commit-ready` fence can finish after binding removal without another deletion; unknown outcomes remain fenced. See [demand-driven rotation](./multi-instance-bus.md#demand-driven-slot-rotation) for history loss and recovery boundaries.
77
+ - `threads`: Telegram UI thread/tab binding state mapped to Bot API `message_thread_id` / `ForumTopic` transport. Owns exact-`cwd` Workspace bindings and transient claims, first-proven inactivity metadata, fenced non-destructive owner detachment with retained Workspace identity/slot, fail-closed retirement occupancy snapshots, and exact durable retirement intents, leader/current-instance identity state, active-turn → follower → leader target preference, matching status projection assembly, profile-bound same-process handoff, exact-claim global-slot allocation and conservative missing/duplicate legacy migration, collision-safe compact thread-name selection, Workspace-aware rename persistence, and primitive provision helpers. Its optional external-slot source makes every generic allocation, Workspace claim, and occupancy snapshot reserve retained admission-fence slots; malformed, unreadable, or non-uppercase evidence fails allocation closed. Its synchronous provision-commit helper transfers exact targeted creation-title evidence into the claim-committed Workspace binding and consumes matching pending evidence; callers retain admission, epoch checks, and durable publication. It should not turn dormant bindings into routing authority, own destructive cleanup policy, or grow into the general Telegram synchronization domain.
78
78
  - `updates` / `routing`: update classification, authorization, callbacks, edits, reactions, forwarding, and inbound composition. `updates` owns production journal workers, leader/follower admission lifecycle construction, binding and settlement selection, queue-handoff projection across recipient journals/admission/IPC/live queue state, process/session queue-owner projection, post-public source binding, exact-signal late settlement, durable receipt readiness, same-process claim reconstruction, and structural worker state. `routing` converts message, callback, guest, section, reroute, and control admissions into exact receipts; its complete unbound-target and reroute restore/reclaim handlers run under the shared profile-wide Workspace operation boundary before store access.
79
79
  - `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound extraction, rich reply plaintext, grouped debounce, split-text coalescing, optional time context, handlers, and prompt assembly/editing, including the `[guest]` Guest Mode speed note appended to guest turn text. Group replay replaces stale generation-local message/report bindings without duplicating content.
80
80
  - `queue`: queue contracts, transport stamps, lanes, readiness, mutations, dispatch, enqueueing, and lifecycle sequencing. Durable admission uses deterministic receipts, canonical source sets, replay dedupe, multiple folded-history receipts, append-before-dispatch reporting, exact handoff/control/discard settlement, and a readiness gate. Receipt-bearing inactive-profile work is preserved after current-profile work rather than dropped.
@@ -232,11 +232,16 @@ Required witnesses before accepting this policy: out-of-order first deliveries s
232
232
 
233
233
  Source audit confirms that canonical inventory is not yet usable as a production readiness check:
234
234
 
235
- - `Profile path defect`: `lib/extension.ts` passes `resolveTelegramUpdateJournalPath` and `resolveTelegramWorkspaceAdmissionPath` directly to profile-only ports, although both functions take `(agentDir?, profileName?)`. For a named profile they derive relative `<profile>/tmp/telegram/inbox.json` and `workspace-admission.json`, not the canonical suffixed files. Pure probes confirm cwd-dependent locations; no live contents were inspected. The same relative spelling can refer to different physical namespaces. Do not silently correct the adapters and leave accepted work or admission leases behind; historical-location and reference reconciliation must precede activation or redirection.
235
+ - `Profile path correction`: Historical production wiring passed `(agentDir?, profileName?)` resolvers directly to profile-only ports, allowing a named profile to become a relative `<profile>/tmp/telegram/` root. The authorized bounded inventory inspected every current and retained historical CWD hint for exact candidates using the sole configured/retained `default` profile; no defective-path source existed. The canonical root's 30 v1 families were inspected without repair and retain five queued source records in two follower families, so they remain migration authority rather than empty-state evidence. Production now uses dedicated profile-only polling/admission resolvers that bind `resolveAgentDir()` and produce canonical suffixed paths; a composition invariant rejects the old callback shape. The isolated two-CWD fixture remains as historical counterexample evidence and preserves its queued work and leases. This correction does not establish writer exclusion, migrate canonical v1 custody or activate prepared v3 consumers.
236
+ - `Prepared reference preflight`: `paths.requireTelegramStoragePathReference(selected, approved)` returns only an already absolute, normalized, exactly matching spelling; relative paths, traversal aliases and different resource/profile paths throw without filesystem access or normalization of the returned reference. The approved path must come from independent caller authority. A corrected resolver output is not evidence that historical references were reconciled. This lexical check proves neither physical identity nor consumer/writer closure, and it does not authorize migration. Production uses the corrected profile-only resolvers. `Storage reference preparation` in `tests/integration.test.ts` reconstructs the historical callback shape only in a disposable fixture: default-profile publication still works, named leader/admission resolution refuses before mutation, and correct follower/recipient paths cannot bypass a mismatched admission location. All fixture files, segments, receipts, leases and directory names remain unchanged after refusal. The no-op-guard negative control fails both this composition test and the mirrored path test. The completed inventory found no defective-path source in the identified scope; moving canonical v1 storage and activating consumers remain separate gates.
236
237
  - `Reference coverage`: Inspected receipt/handoff callers select exact active lifecycle bindings rather than opening arbitrary receipt-supplied paths. Historical Workspace keys feed the canonical follower-path hasher; arbitrary path resolver wiring currently receives ordinary discovery results. No automatic reader of quarantined journal contents was found. Provision-recovery metadata and follower state hints do have automatic readers, but do not replay journal entries. None of these observations authorizes relaxing recovery refusal or proves live-reference completeness.
237
- - `Consumer ordering`: Replacement now snapshots originating runtime/recovery key values before awaited shutdown, rejects changed/missing bindings afterward, and uses a fresh matching descriptor before worker construction or recovery reads. Startup replacement and forced transport replacement have held-stop regressions, an old-code negative control and independent mutable-descriptor probes. Legacy same-key reuse without replacement remains unchanged; matching keys alone do not establish session/registration lifetime or compatibility of captured worker dependencies. Terminal retry and dead-owner cleanup precede worker start. Status and polling bootstrap also use recovery-capable reads. Workspace protection's reader port is constructed/exposed, but the audit found no active production invocation; it remains a gate before connection, not evidence of a running retirement loop.
238
+ - `Consumer ordering`: Replacement now snapshots originating runtime/recovery key values before awaited shutdown, rejects changed/missing bindings afterward, and uses a fresh matching descriptor before worker construction or recovery reads. Startup replacement and forced transport replacement have held-stop regressions, an old-code negative control and independent mutable-descriptor probes. Legacy same-key reuse without replacement remains unchanged; matching keys alone do not establish session/registration lifetime or compatibility of captured worker dependencies. Terminal retry and dead-owner cleanup precede worker start. Status and polling bootstrap also use recovery-capable reads. Workspace protection is now consumed by demand-driven slot rotation and uses a strict non-repairing binding reader rather than ordinary recovery-capable `read()`. Source-readiness and migration consumers retain their separate gates.
238
239
  - `Quiescence gaps`: Worker stop aborts/awaits draining but can leave unsettled handlers. Config append guards do not serialize other journal mutations or recovery. Whole-root churn also includes transaction staging before acquisition, state staging before owner-fenced rename, logs, admission ledgers, recovery metadata and endpoints. Holding config or owners alone does not quiesce that root. A journal transaction itself creates an `inbox`-containing name rejected by inventory, so acquiring journal locks and then invoking this census is not a supported composition. Metadata stability cannot protect an unguarded interval through grant publication.
239
- - `Preparation prerequisite`: Independent prototype probes falsified fresh-source preparation followed by reuse of lifetime-expired worker ports, and stopped donor readiness after offer/transfer/discard. Durable settlement CAS cannot undo control execution that precedes settlement. Complete queue wiring instead rejected the unprepared pending-mutation query, preserving safety but not proving accepted-work progress. These are bounded prototype counterexamples, not evidence of production duplicate execution. Defer prepared-mode integration until captured dependencies, executable receipt projections and pending-mutation evidence have coherent owners; retaining bytes or stopped-worker maps alone is insufficient. Preserve exact cancellation/settlement authority and actual unsettled-handler barriers across retries. Keep source inspection/grant wiring disconnected until reference, writer and migration closure is proven.
240
+ - `Prepared worker source lifetime`: A native config/journal/lifecycle fixture reproduced fresh maintenance reads followed by an old worker's revoked input context, blocking the next input despite unchanged source/runtime keys. The gated v3 resolver now keeps one narrow worker port per actual store handle in a `WeakMap`. Lifecycle snapshots the custody port captured at worker construction: renewing it triggers stop, fresh post-stop binding validation and worker replacement even with equal string keys or an in-place descriptor update. Repeated descriptors over the same store still reuse compatible dependencies; legacy bindings remain unchanged. No persistent key, schema, capability or production activation changed. Native leader-restart and active-follower tests cover renewal and stable reuse; a held-handler fixture preserves the exact durable `running` claim while independent input progresses, then rejects the cancelled origin's late effect. Negative controls prove both captured identity and stable per-store port reuse are required. This is source-handle freshness, not proof of arbitrary callback validity or global readiness; renewed callable lifetimes require a fresh store handle rather than in-place rewriting of its methods.
241
+ - `Prepared donor receipt projection`: Native temporary config, Workspace admission, v3 store, lifecycle and real queue binding reproduced control effects from stale donor readiness after offer, transfer or discard while raw transport authority was lost. The strict worker port now observes the complete source-ID group, kind, exact queued owner/acquisition and absence of an offer at readiness/owner lookup. It can only restrict an already retained receipt, never adopt a new one. Unavailable observation fails closed without repair or removing local work; unchanged/cancelled ownership and a later successful observation remain usable without transport reacquisition. Native grouped-receipt fixtures cover another store handle mutating without a wake, unrelated accepted work, late prompt revocation and lost completion ACKs. Three isolated loader controls establish the need for current observation, explicit completion acknowledgement and confirmed local cleanup. The shared [settlement contract](#queue-and-dispatch-safety) preserves acknowledged prompt cleanup before `agent_start`; a late CAS refusal cannot undo an earlier control effect. Production still uses legacy bindings, without this observation port. These are point-of-use observations, not a lock spanning every later effect, proof of all writers/consumers, multi-journal atomic completion, live acceptance or performance evidence.
242
+ - `Prepared pending mutations`: Native config/Workspace/v3/lifecycle/queue composition now proves accepted independent work progresses after transport loss while a governing reaction is pending or held in its actual handler. Successful Skip settles without a model turn; a failed running mutation stays outcome-unknown, blocks only the governed item and is not replayed. The fixture exposed a genuine lost wake: an older blocked drain result erased a newer signal after accepted-work completion. The worker now consumes newer wakes within the same drain run, preserving `waitForDrain`; signals preceding a later write failure cannot clear its latch or cause hot retry. Business-deletion probes also reproduced both wrong-namespace durable removal and memory-only removal after discard failure. Bot API `Message.business_connection_id` and `BusinessMessagesDeleted` establish that even equal chat/message IDs do not identify bot-chat work, so default routing now ignores this carrier rather than manufacturing deletion intent. Raw handlers remain usable. Independent loader controls falsify the namespace and wake fixes. These are native disposable composition results, not live Business/Telegram or Windows acceptance.
243
+ - `Immutable pending-mutation exclusion`: The native fixture reproduced a permanently excluded reaction stranding already accepted work after transport loss. The dependency scan now ignores only `preApprovalExcluded === true`; missing legacy evidence and explicit false remain protective. Real config unpairing establishes the veto, and re-pairing, duplicate append and reopening do not remove it. Both accepted prompts dispatch through the actual queue binding while transport remains unavailable; the excluded entry stays identical and no reaction handler runs. A loader control restoring the old scan fails both unpaired and re-paired cases. Observation does not dispose of excluded input or establish general source readiness.
244
+ - `Preparation prerequisites`: Reuse the completed native prerequisites; an unprepared source's read refusal is not permission to recover during queue preflight. The common worker's `removeCompleted` slot now delegates to `removeExcluded` only in the prepared v3 port. Native restoration and shutdown/restart cases drain the retained pending exclusion without executing its reaction or replaying accepted prompts, retaining the admission cursor. The journal owner rejects mixed excluded/non-excluded requests atomically; adapter checks preserve unclaimed, ready, running, queued and offered work. Queue/failure mutation stays forbidden. This is not generic raw completion, retry-quarantine disposition or production activation; existing strict exclusion-removal failure/replay-barrier tests remain authoritative. A native queue-binding call after worker shutdown exposed an unleased pending-mutation read of the retained source. Lifecycle now snapshots its reference binding and scopes pending-mutation/count reads when the long-lived reference is absent; active reads reuse it. Acquisition refusal occurs before I/O, and read failure releases the temporary reference without clearing accepted work. Stopped receipt readiness/owner projection reads nothing and cannot restore execution. The real registry/queue fixture covers stop, failure, capacity denial, restart, transport loss and forgotten-source refusal; a loader control removing the scoped reference fails. This tracks participating observation, not callable renewal or global writer exclusion. The donor handoff coordinator supplies a concrete retained mutation caller: its remote stage can reject after lifecycle shutdown, then attempt cancellation. That synchronous cancellation now uses the same scoped reference mechanism before the existing exact journal CAS; it does not reactivate the worker. Native tests hold the remote promise across shutdown and prove cancellation with original custody, denial before mutation at reference capacity, and failed cancellation after real recipient acceptance/lost ACK. Donor memory and recipient ownership survive; the latter case verifies that the native CAS was reached rather than mistaking a swallowed test assertion for refusal. A loader restoring unscoped cancellation fails all three cases. Forwarded durable admission and recipient acceptance have no await before their initial mutation. A separate native registration counterexample established the startup gap: a real prior-generation handler holds worker replacement while real local IPC registration publishes its generation; the receiver previously ACKed delivery into the retained old journal. The follower assembly now exposes inbound generation only after `onRegistered` succeeds for that exact generation and current context. Registration state itself remains available to prepare the worker. Native tests prove early refusal, old/new journal preservation, retry into the new binding after release, context-replaced refusal and failed-preparation refusal. Loader controls independently falsify early-publication and context checks. This is local IPC/lifecycle evidence, not live Telegram or native-Windows acceptance. The deferred media producer has additional native config/v3/lifecycle/media-group/turn-preparer/enqueue evidence using the execution-guard wiring supplied by `routing`: an active delayed attachment creates one queued receipt under a held reference; clearing groups and stopping the worker while preparation is held makes the existing post-await enqueue fence refuse before memory append, queued report or custody mutation. The exact running record remains unchanged. Removing that fence through an isolated loader falsifies the stopped case. No extra callback authority or reference reacquisition was added. A separate native paired-runtime denial witness confirmed that a reply returning after worker shutdown could previously default to complete and remove the running source without a reference. Admission now rechecks the shared execution fence after `defaultHandle` returns, before exposing any outcome to the custody session. Active denial completes once under the live reference; an interrupted denial makes no completion attempt, retains its exact running record, and is not replayed on restart while unrelated accepted prompts still dispatch after transport loss. A loader removing the new check reproduces the durable removal. This closes composed default-handler return, not every standalone session/report consumer. Same-generation follower refresh has separate native IPC evidence: after lifecycle shutdown, both a new context object and a reused object with an advanced session generation previously admitted into a stopped worker, and `setContext` did not reprepare it. The assembly's ready proof now binds registration generation, context and the supplied Pi session generation, checked on every inbound generation lookup. Its async `setContext` delegates heartbeat context publication and reuses binding preparation when that proof is stale; the refresh hook awaits it without changing bus membership. Failed preparation remains unready, is diagnostic rather than a thrown session-start failure, and can be retried. Native cases prove early refusal, unchanged journal, same registration generation and subsequent processing with the refreshed context, including failure/retry. Independent generation/preparation loader controls fail. Production supplies the context/session-generation ports; standalone callers without them retain their caller-owned lifecycle contracts. Initial/re-registration now captures Pi session generation before receiver startup and retains one local attempt identity. Checks after startup, RPC, preparation and first heartbeat refuse expired work; stop and a changed-context refresh invalidate the attempt. Owned stale cleanup clears only that request's local authority, never a newer attempt. Native local-IPC cases reproduce and fence response-time session drift on a reused object, startup-time drift, stop, overlapping newer registration and refresh during the initial heartbeat; stable registration and a fresh retry still succeed. Independent controls falsify session, attempt and refreshed-owner protection. This is client request authority, not remote provisioning rollback or global consumer/writer closure. The actual control-handoff reconciler now consumes the coordinator's already-serialized payload and changes only its destination journal binding; it no longer clones a live control's `execute` function. Native v3 config/journal, reconciler, wire-codec, recipient staging and queue-dispatch composition verifies one recipient-local execution and no model turn, rejection with original donor custody, and a lost post-acceptance ACK retaining donor memory without another transfer or effect. Restoring the old whole-item clone falsifies all three cases. This closes the concrete serialization blocker, not production legacy-copy replay, global physical-reference/writer closure, historical migration or live acceptance. Further cutover work requires its operator-authorized inventory and activation evidence. Preserve exact cancellation/settlement authority and actual unsettled-handler barriers. Source inspection/grant wiring remains disconnected.
240
245
 
241
246
  #### Source Operation Serialization
242
247
 
@@ -320,7 +325,7 @@ Read-only migration probes constrain that comparison. Publishing a v2 snapshot o
320
325
 
321
326
  ### Runtime Ownership
322
327
 
323
- - `/telegram-connect` acquires or moves the active profile's owner slot before polling starts. `/telegram-disconnect` keeps its destructive confirmation, then stops polling and releases only that exact slot. In Threaded Mode it tears down the disconnecting instance's bound Telegram thread: leaders delete their own thread directly, and followers send an authenticated exact-generation disconnect envelope and wait for confirmed leader cleanup before unregistering. Graceful Pi `quit` always preserves the owner slot as restart intent, allowing a reopened same-`cwd` session to reclaim the stale lease. When `threads.automaticCleanup` is enabled (the default), quit also deletes the bound Telegram tab without releasing that restart intent; disabling it preserves the tab through replacement-style suspension. Failed automatic cleanup records diagnostics and falls back to safe suspension so remaining lifecycle cleanup still runs.
328
+ - `/telegram-connect` acquires or moves the active profile's owner slot before polling starts. `/telegram-disconnect` keeps its destructive confirmation, then stops polling and releases only that exact slot. In Threaded Mode it tears down the disconnecting instance's bound Telegram thread: leaders delete their own thread directly, and followers send an authenticated exact-generation disconnect envelope and wait for confirmed leader cleanup before unregistering. Graceful Pi `quit` always preserves the owner slot as restart intent, allowing a reopened same-`cwd` session to reclaim the stale lease. When `threads.automaticCleanup` is enabled (the default), quit also deletes the bound Telegram tab without releasing that restart intent; disabling it preserves the tab. After delivery, polling and inbound-worker shutdown complete, a quitting leader detaches its exact owner record under profile admission while retaining the Workspace, letter and restart ownership. Publication rechecks the captured session generation, profile, leader epoch and successful current-generation suspension; unfinished startup/teardown, unproven suspension and pending cleanup stay protected. Preparation/publication errors are diagnostic and cannot block remaining session cleanup. Failed automatic deletion likewise falls back to safe suspension.
324
329
  - Session start schedules polling resume asynchronously only when the owner slot already points at the current `pid`/`cwd`, or when a stale same-`cwd` owner can be safely replaced after process restart. Under a live foreign leader, startup instead attempts capability-gated restore-only follower admission when the selected profile has a remembered exact-`cwd` Workspace binding; it never provisions an unremembered Workspace. Startup and `/resume` do not wait on leader election, Bot API probes, poller handoff, or thread reconciliation before restoring the Pi session.
325
330
  - The polling owner alone bounds `getUpdates`: each request derives its cancellation budget from Telegram's declared long-poll timeout plus 10 seconds of transport grace (10 seconds for the zero-timeout initial sync and 40 seconds for the normal 30-second poll). The request-local controller inherits poller cancellation, rejects its owner at the budget, and fences any late transport result. Ordinary Bot API and media operations do not receive speculative blanket deadlines. Existing caller signals remain authoritative through API retry waits, only retry-safe methods replay explicit retryable responses, and non-idempotent sends preserve commit-unknown evidence instead of risking duplicate mutation.
326
331
  - Ten consecutive `getUpdates` conflict responses, including initial cursor sync, terminate polling with `persistent-conflict`; a successful response or a different error resets the count. The controller detaches its inner promise before notifying the locked lifecycle, avoiding teardown waiting on itself. That lifecycle stops ownership checks, lease refresh, capability monitoring, typing, and classic or bus transport (including leader health, pruning, and IPC), withdraws local direct authority, and transactionally releases only its exact lock. A failed durable release leaves local authority revoked; explicit reacquisition mints a fresh epoch. Accepted queue receipts and local Pi dispatch survive. One terminal diagnostic distinguishes lost local ownership from a competing client despite an apparently owned lock and reports cleanup failures; ordinary status refreshes retain generic `error` until transport recovers. Remove the competing client, then use `/telegram-connect`; no automatic retry continues after the terminal threshold.
@@ -328,7 +333,7 @@ Read-only migration probes constrain that comparison. Publishing a v2 snapshot o
328
333
  - `pollingActive` reports only whether this runtime still owns an unresolved polling lifecycle; it is not health evidence. A separate observable state records `starting`, `long-poll`, `persisting-journal`, `persisting-offset`, `retrying`, or `stopped`, together with phase start, current update id, last successful response time/count, and terminal stop reason. This distinguishes a stuck HTTP poll from downstream update work without a wall-clock stale heuristic.
329
334
  - Built-in read-only menu commands return after required local mutation and schedule context-fenced rendering and command synchronization independently, so those effects cannot withhold the next inbound offset.
330
335
  - Pi `print`/`json` run modes stay passive. Inherited child sessions that share `telegram.json` but do not own the exact `pid`/`cwd` slot must not poll or call `getUpdates` unless the operator force-takes ownership.
331
- - Session replacement through `reload`, `new`, `resume`, or `fork` suspends polling/watchers without releasing ownership so the next session in the same process can resume. A registered follower snapshots its assigned target into a short-lived same-process handoff, stops the old receiver/heartbeat, and re-registers through the live leader without marking or replacing its Telegram thread. Hard process termination cannot run graceful teardown, so stale recovery retains its restart-hint path.
336
+ - Session replacement through `reload`, `new`, `resume`, or `fork` suspends polling/watchers without releasing ownership or publishing inactivity so the next session in the same process can resume. Late shutdown cannot clear a replacement context even when its identity is reused: the captured session generation must still match. A registered follower snapshots its assigned target into a short-lived same-process handoff, stops the old receiver/heartbeat, and re-registers through the live leader without marking or replacing its Telegram thread. Hard process termination cannot run graceful teardown, so stale recovery retains its restart-hint path.
332
337
  - Live external owners require explicit takeover confirmation. Long-lived timers compare against snapshotted owner identity and stop local transport work when the slot no longer matches.
333
338
  - `owners.json` owns only Telegram transport control. Local extension and accepted queue state remain per Pi instance when ownership moves, but previews, final delivery, dispatch transport mutations, and delayed Bot API work fail closed until exact direct or follower authority becomes valid again.
334
339
  - Exact ownership remains checked every second, while the durable owner heartbeat refresh runs every two seconds and becomes stale after eight seconds. This keeps replacement detection responsive while halving steady-state atomic `owners.json` rewrites without changing the cross-platform file-transaction authority. Every acquisition, refresh, release, takeover, and stale recovery serializes through the sibling `owners.json.transaction` guard. The guard publishes one private generation-named owner record atomically, validates filename/payload generation agreement, fences stale recovery and delayed release against replacement-owner ABA, and fails closed on malformed state, unverifiable ownership, contention timeout, or unsupported filesystem behavior. The JSON store publishes through a private same-directory temporary file and atomic rename; atomic payload replacement does not replace transaction serialization.
@@ -369,7 +374,7 @@ Named Telegram profiles are orthogonal to Threaded Mode. The selected profile ch
369
374
 
370
375
  Profile reality follows three explicit storage classes. `telegram.json` shared settings and extension registries are process-global platform configuration; `profiles.default` and `profiles.<name>` bot/session fields plus observable transport/routing authority are profile-scoped; queues, active turns, ownership caches, menu state, and runtime controllers are session-local memory. Config persistence serializes cross-process writers and applies each recursive mutation delta to the latest disk snapshot, so unrelated global/profile updates do not stale-replace one another. Each profile's journal transaction independently publishes its monotonic admission cursor together with admitted work; polling never writes runtime state through config persistence. Runtime profile switching follows stop-old/commit-new ordering: reload keeps the selected identity stable, old polling/lock/bus teardown finishes while all dynamic resolvers still point at the old profile, and only then may activation expose the new token, lock key, state path, target namespace, and IPC endpoint. Downloaded attachments use UUID-prefixed names in the shared Telegram scratch directory and are session artifacts rather than identity or routing authority, so cross-profile cleanup recognizes only those UUID-prefixed scratch files. It never age-deletes journals, ownership, state, logs, or other top-level runtime files and therefore cannot redirect live traffic or orphan immutable journal segments.
371
376
 
372
- When Threaded Mode is active, the current polling owner is also the Telegram bus leader. The leader owns the local bus endpoint (Unix-domain socket on Unix-like platforms, named pipe on native Windows), polls `getUpdates`, performs direct Bot API calls, records follower heartbeats, prunes stale followers, and provisions Telegram UI thread targets through live runtime/bus state. Followers heartbeat every `1s`; the leader uses a `15s` stale grace and a `1s` prune loop so transient IPC stalls do not create false routing gaps while active forwarded updates/API calls still refresh liveness. Heartbeat pruning is silent liveness bookkeeping and does not send a Telegram-visible disconnected notice, because the common cause may be leader reload or IPC handoff rather than a dead follower. Pruning alone preserves the binding; when Thread cleanup is enabled, only a subsequent OS check that confirms the exact registered PID absent may create fenced cleanup intent, and that cleanup serializes ahead of replacement registration. Successful follower target reuse refreshes the binding's recovery timestamp. Absent follower bindings remain durable restoration hints until explicit stale, deleted, offline, or reconciliation evidence invalidates them; process absence and heartbeat pruning alone do not remove them. If an authenticated live follower carries an exact target that is absent from current bindings, the leader recovers it only behind a synchronous visibility probe: success activates it, explicit stale evidence provisions a replacement, and ambiguous failure rejects registration. A carried slot is restored only when it is not already occupied. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging; it resets on extension start and runtime scope changes, and must not become routing/provisioning authority. `tmp/telegram/state.json` is an extension+bot observable/debug snapshot aligned with status diagnostics: `source: "snapshot"` and `writtenAtMs` mark it as observational, not authoritative. All instances on one Telegram profile read the same snapshot, but only the active transport lock owner persists it; followers become writers only after promotion. Status-only persistence reloads current disk bindings before serialization, preventing a stale follower/status view from erasing newer leader-owned targets. Fresh capability observations may skip redundant startup probes, but stale snapshots re-probe before suppressing bus/thread behavior. Top-level `bot` mirrors bot-wide capabilities such as thread mode, `runtime` describes process role/status, `liveRoster` mirrors followers/current targets/reservations, `diagnostics` mirrors recent status/debug signals including the latest thread-reconciler phase/counts, `threads` stores current routeable bindings, `workspaceBindings` stores profile-scoped normalized exact-`cwd` target/name/slot reuse hints, TTL-bounded reservations explain short-lived slot collision guards, and TTL-pruned `pendingProvisions` protects in-flight topic creation slots from cleanup/allocation races. Fresh provisioning writes pending state before the Bot API create call, adds the returned target to the pending record, persists a `starting` binding, then promotes it to `active` and clears pending state. If final binding persistence fails after Telegram returns a thread id, the targeted pending provision remains as cleanup/retry evidence. Once targeted pending provisions expire, they are retained for `thread-reconciler` close/delete cleanup and pending scratchpad removal after a successful cleanup apply; untargeted expired pending records can prune without cleanup because no Telegram thread id exists. Runtime events coalesce status-snapshot writes so transient bus/API/update failures remain inspectable even when the operator has not opened `/telegram-status`. The bridge must not keep a durable `telegram-targets.json` target history; stale/offline/failed thread observations are pruned instead of reused. Previous-process leader bindings that still probe alive become reservations/collision guards, not routeable active threads, so a reloaded leader can take the next free slot without duplicating the same visible tab name. The thread chat is always the private bot DM with the paired owner (`allowedUserId`). In Telegram private-chat Threaded Mode, the leader creates/reuses its own thread before polling — it is a real bound instance, not a dispatcher. Followers authenticate bus envelopes with the leader-minted capability secret stored in the active lock entry. Bot capability monitoring does not probe through the bus until the process either owns that direct lock or has completed authenticated follower registration. Leader lock entries also carry a stable `leaderEpoch` minted on acquisition and preserved across heartbeat refreshes; leader-owned cleanup/provisioning plans stamp that epoch, and Thread Reconciler apply skips destructive work if leadership has moved on before side effects run. Followers own their own Pi session state, queue, active turns, previews, menus, and lifecycle hooks, but route allowlisted, target-scoped Telegram API calls through the leader. When a follower promotes after heartbeat loss, status/state diagnostics expose only the transient `electing` lifecycle phase; stable `leader`/`follower` identity stays in the bus role so diagnostics do not duplicate role state. The TUI status bar and `/telegram-status` report `leader` or `follower` role so a registered follower is not shown as generically disconnected. Terminal status identity and the `[telegram|thread:name]` prompt label use the same target-aware current-instance resolver: registered local metadata wins over a stale shared binding for the matching target, while the binding remains a fallback for partial metadata.
377
+ When Threaded Mode is active, the current polling owner is also the Telegram bus leader. The leader owns the local bus endpoint (Unix-domain socket on Unix-like platforms, named pipe on native Windows), polls `getUpdates`, performs direct Bot API calls, records follower heartbeats, prunes stale followers, and provisions Telegram UI thread targets through live runtime/bus state. Followers heartbeat every `1s`; the leader uses a `15s` stale grace and a `1s` prune loop so transient IPC stalls do not create false routing gaps while active forwarded updates/API calls still refresh liveness. Heartbeat pruning is silent liveness bookkeeping and does not send a Telegram-visible disconnected notice, because the common cause may be leader reload or IPC handoff rather than a dead follower. Heartbeat pruning alone preserves stored owner records and Workspace bindings. The same leader can retain bounded, non-routing observations of actually registered PID/generation/target tuples for later absence proof and retry of uncommitted preservation; the [pruning contract](./multi-instance-bus.md#follower-heartbeat-is-missed) owns cancellation, admission identity and lifetime limits. When Thread cleanup is enabled, only a subsequent OS check that confirms the exact registered PID absent may create fenced cleanup intent, serialized ahead of replacement registration. With cleanup disabled, that proof instead permits a profile-admitted, fenced non-destructive detachment: the store removes the uniquely matching owner record and records first inactivity while retaining the exact Workspace binding and its letter. Publication rechecks PID absence, runtime generation, profile, leader epoch, replacement registrations and the record snapshot; missing or ambiguous restoration identity blocks it. The Thread and its messages remain untouched, accepted-work protection remains independent, and no deleted-Thread observation is invented. Successful follower target reuse refreshes the binding and clears inactivity. Durable Workspace bindings remain restoration hints after detachment; heartbeat silence or unverifiable process evidence cannot stamp inactivity. If an authenticated live follower carries an exact target that is absent from current bindings, the leader recovers it only behind a synchronous visibility probe: success activates it, explicit stale evidence provisions a replacement, and ambiguous failure rejects registration. A carried slot is restored only when it is not already occupied. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging; it resets on extension start and runtime scope changes, and must not become routing/provisioning authority. `tmp/telegram/state.json` is an extension+bot observable/debug snapshot aligned with status diagnostics: `source: "snapshot"` and `writtenAtMs` mark it as observational, not authoritative. All instances on one Telegram profile read the same snapshot, but only the active transport lock owner persists it; followers become writers only after promotion. Status-only persistence reloads current disk bindings before serialization, preventing a stale follower/status view from erasing newer leader-owned targets. Fresh capability observations may skip redundant startup probes, but stale snapshots re-probe before suppressing bus/thread behavior. Top-level `bot` mirrors bot-wide capabilities such as thread mode, `runtime` describes process role/status, `liveRoster` mirrors followers/current targets/reservations, `diagnostics` mirrors recent status/debug signals including the latest thread-reconciler phase/counts, `threads` stores current routeable bindings, `workspaceBindings` stores profile-scoped normalized exact-`cwd` target/name/slot reuse hints, TTL-bounded reservations explain short-lived slot collision guards, and TTL-pruned `pendingProvisions` protects in-flight topic creation slots from cleanup/allocation races. Fresh provisioning writes pending state before the Bot API create call, adds the returned target to the pending record, persists a `starting` binding, then promotes it to `active` and clears pending state. If final binding persistence fails after Telegram returns a thread id, the targeted pending provision remains as cleanup/retry evidence. Once targeted pending provisions expire, they are retained for `thread-reconciler` close/delete cleanup and pending scratchpad removal after a successful cleanup apply; untargeted expired pending records can prune without cleanup because no Telegram thread id exists. Runtime events coalesce status-snapshot writes so transient bus/API/update failures remain inspectable even when the operator has not opened `/telegram-status`. The bridge must not keep a durable `telegram-targets.json` target history; stale/offline/failed thread observations are pruned instead of reused. Previous-process leader bindings that still probe alive become reservations/collision guards, not routeable active threads, so a reloaded leader can take the next free slot without duplicating the same visible tab name. The thread chat is always the private bot DM with the paired owner (`allowedUserId`). In Telegram private-chat Threaded Mode, the leader creates/reuses its own thread before polling — it is a real bound instance, not a dispatcher. Followers authenticate bus envelopes with the leader-minted capability secret stored in the active lock entry. Bot capability monitoring does not probe through the bus until the process either owns that direct lock or has completed authenticated follower registration. Leader lock entries also carry a stable `leaderEpoch` minted on acquisition and preserved across heartbeat refreshes; leader-owned cleanup/provisioning plans stamp that epoch, and Thread Reconciler apply skips destructive work if leadership has moved on before side effects run. Followers own their own Pi session state, queue, active turns, previews, menus, and lifecycle hooks, but route allowlisted, target-scoped Telegram API calls through the leader. When a follower promotes after heartbeat loss, status/state diagnostics expose only the transient `electing` lifecycle phase; stable `leader`/`follower` identity stays in the bus role so diagnostics do not duplicate role state. The TUI status bar and `/telegram-status` report `leader` or `follower` role so a registered follower is not shown as generically disconnected. Terminal status identity and the `[telegram|thread:name]` prompt label use the same target-aware current-instance resolver: registered local metadata wins over a stale shared binding for the matching target, while the binding remains a fallback for partial metadata.
373
378
 
374
379
  Fresh follower binding is manual and process-first: the operator starts another Pi process, then runs `/telegram-connect`; only then may that process allocate a profile-scoped normalized exact-`cwd` Workspace identity and cause the leader to create a Thread. A later process reopening that remembered Workspace automatically sends capability-gated restore-only admission under a live leader. The leader may reclaim, visibility-probe, or stale-replace the remembered target, but an absent binding returns quietly without creating a Thread. `/telegram-connect [profile] as=Name` supplies a unique capitalized Latin-word identity only to fresh Workspace provisioning; an existing Workspace keeps its persisted name. Telegram `/name Name` stores the owning Thread's durable `manualThreadName` and immediately applies it over the active automatic display projection; bare `/name` opens five-minute exact-target input whose next valid text is consumed before agent dispatch. Name input, cancel, and reset are consume-once; stale scope, target, message ID, expiry, and duplicate callbacks cannot mutate. Reset clears only the override and restores the current automatic projection. Leader command routing reuses its already-held profile admission for the rename body rather than recursively entering the non-reentrant Workspace gate; standalone leader renames acquire their own admission. Followers send an authenticated exact-generation `workspace-thread-rename-v1` request, and the leader owns any Bot API mutation plus durable binding persistence. Concurrent processes from one directory receive deterministic Workspace suffixes, while leader/follower roles remain transient projections over that durable identity. Telegram does not expose `/thread`, auto-spawn arbitrary unbound threads, or launch hidden follower subprocesses. In Threaded Mode, `/telegram-connect` does not offer manual takeover while a live leader exists; takeover is reserved for stale-leader election/recovery. Leadership remains an ephemeral transport role that another live follower can take over after stale heartbeat detection. A confirmed runtime transition from Threaded to Singleton stops threaded transport and suspends the process-local leader target before classic polling can accept new work; durable Workspace slot, generated name, manual display name, and binding evidence remain retained. Re-enabling Threaded Mode restores or replaces that logical binding before publishing one new live target. Already-admitted turns keep their captured destination and are never silently retargeted or duplicated.
375
380
 
@@ -423,7 +428,7 @@ Authenticated live handoff uses journal CAS plus bounded local IPC. The donor cr
423
428
 
424
429
  During recipient staging, presenting the token atomically replaces the journal owner with a fresh acquisition carrying the handoff digest, removes the offer, and permanently fences donor settlement. The donor treats only an ACK carrying that exact accepted owner as success and never repeats acceptance against a donor-bound journal runtime. The recipient can repeat the same acceptance idempotently; a different token cannot claim an already accepted receipt. The coordinator contract orders offer → stage/accept exact receipt-and-owner ACK → donor removal → recipient readiness for direct leader→follower and follower→follower routing. Before acceptance, negative or mismatched acknowledgement exactly cancels the offer and keeps donor work. After acceptance, a lost acknowledgement cannot roll authority back: cancellation fails closed and donor memory remains frozen until exact accepted-owner reconciliation removes it. Recipient registration carries exact process-birth/session identity, and staged payloads remain outside the live dispatch store until accepted journal authority has been reconstructed. Production advertises `queue-handoff-v1` only with this exact role/journal selection and uses the same coordinator ordering for direct leader→follower and follower→follower routes.
425
430
 
426
- Queued semantic authority has no elapsed-time lease. A timeout cannot prove either owner death or effect quiescence, so it cannot safely resolve a receipt. Resolution is limited to authenticated live handoff, exact owner discard/settlement, or transaction-rechecked negative PID plus process-birth evidence that permits terminal cleanup without replay. Live or unverifiable owners remain queued rather than risking duplicate or cross-session execution.
431
+ Queued semantic authority has no elapsed-time lease. A timeout cannot prove either owner death or effect quiescence, so it cannot safely resolve a receipt. Resolution is limited to authenticated live handoff, exact owner discard/settlement, or transaction-rechecked negative PID plus process-birth evidence that permits terminal cleanup without replay. Live or unverifiable owners remain queued rather than risking duplicate or cross-session execution. Workspace pressure may request the same terminal dead-owner cleanup only after full `A`–`Z` allocation failure, current inactive-binding authority, complete strict source enumeration, no local/live/delivery authority, whole unoffered same-target receipt groups, and preflight death proof for every group. Journal CAS remains the mutation authority. Partial success or interruption is safe progress only: a fresh all-clear protection capture and normal retirement fence are still mandatory before Thread deletion.
427
432
 
428
433
  The initial `offset: -1` cursor bootstrap is allowed only when both cursor and journal are absent or empty. Thereafter process-level ordering is journal atomic rename → one monotonic offset atomic rename → worker signal. Failure before journal publication leaves the offset unchanged; failure after journal publication but before offset publication permits Telegram redelivery and journal dedupe; failure after offset publication but before worker signal replays from the journal on restart. Queue-owner, retry, terminal, handoff, and completion transitions use the same journal publication primitive and therefore share this process-crash boundary. The final completion window is at-least-once, so replay-sensitive external effects must use `update_id` or the stable delivery id as an idempotency key.
429
434
 
@@ -498,6 +503,8 @@ A dispatched prompt remains queued until `agent_start` consumes it. This keeps t
498
503
 
499
504
  Post-agent-end queue dispatch uses a session-bound deferred dispatcher. It is activated on session start, clears timers on shutdown, and skips callbacks from older generations before touching `ExtensionContext`. Dispatch stays session-bound after polling ownership moves elsewhere. When a queued Telegram prompt is forwarded into Pi, the bridge synchronously commits its exact durable receipt before calling normal `sendUserMessage(content)`; failed receipt commitment blocks dispatch, while the unavoidable crash window between commitment and Pi admission favors at-most-once execution over replay. It does not use Pi's `followUp` delivery option or inject terminal input.
500
505
 
506
+ Settlement returns an explicit acknowledgement only after exact source removal is confirmed. Missing readiness, frozen/transferred ownership, observation failure and an unknown completion result are not acknowledgements. The mux resolves every unsettled receipt to its owning runtime before mutation, rejects unknown/conflicting ownership, and requires each completion result; a completed prefix is not whole-request success. A private weak acknowledgement keyed by the original receipt object and its unchanged normalized metadata lets later local discard clean up a confirmed prompt still awaiting `agent_start`, including after worker replacement. It is never execution authority: it neither re-arms dispatch nor survives cloning/serialization, and is not a durable completion ledger. Failed or ambiguous completion creates no acknowledgement: prompt dispatch and bulk clearing retain the local item rather than inferring success from an absent source. This does not establish retention for every internal mutation primitive: direct message-ID removal still updates memory before containing settlement failure. Its former automatic Business-deletion producer was invalid because Business and bot-chat namespaces are independent, and default routing now ignores it. Do not restore that producer or infer private-message deletion intent from matching numeric IDs. The internal explicit deletion plan and helper are not a new automatic deletion capability.
507
+
501
508
  One monotonic session generation also fences agent/tool/message events, compaction callbacks, preview state, scheduled final delivery, controls, and shutdown. Distinct Pi context objects observed within one session adopt that generation; contexts already observed under an older generation remain stale after replacement. Session start invalidates pending preview work, delayed finals check their captured context before delivery, and shutdown rechecks after asynchronous polling/preview boundaries with a bounded preview-clear wait.
502
509
 
503
510
  For a configured Rich response with final text and exactly one supported queued PNG/JPEG, MP4, or MP3 artifact, queue orchestration asks `outbound-attachments` for one reply-anchored multipart Rich result before finalizing ordinary text. A successful result clears the preview, records exact message ownership, and suppresses duplicate text/file delivery. A known-safe rejection returns to the established paths; an ambiguous send stops the turn without fallback or replay. HTML mode, multiple or unsupported files, Guest Mode, and all voice-policy outputs bypass this optimization.
@@ -610,7 +617,7 @@ Queue reaction behavior, lane-tail transitions, Keep/Skip independence, multi-re
610
617
 
611
618
  `/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Polling diagnostics expose the exact phase, phase start, current update, last successful `getUpdates` response, and stop reason; outbound success never substitutes for inbound progress. Expected preview noise such as unchanged edit responses is filtered out. The compact TUI status renders only `error`; detailed failure text remains in diagnostics and profile-scoped logs instead of expanding the status line.
612
619
 
613
- Complete intermediate assistant text blocks from Telegram-originated activity are sent once to the immutable originating target before active-turn final delivery; final and terminal-partial segments stay with settlement so replies are not duplicated. While this instance has exact direct or follower transport authority, completed public blocks from local/autonomous work are always sent once and in source order to the instance's authorized target. Connected companion projection is not configurable; disconnect or authority loss is its boundary. Both paths use the configured Rich or HTML renderer and exclude reasoning, tool traffic, token deltas, local prompt text, unknown sources, and stale generations. Each admitted block remains fenced to its exact target, profile/token stamp, leader epoch or follower registration generation, and session generation; non-idempotent acknowledgement ambiguity never authorizes replay.
620
+ Complete intermediate assistant text blocks from Telegram-originated activity are sent once to the immutable originating target before active-turn final delivery; final and terminal-partial segments stay with settlement. If settlement nevertheless resolves to text exactly equal to an intermediate already admitted for that Telegram turn, it preserves lifecycle settlement but suppresses the second persistent reply. This same-owner equality fence covers ordinary finals and recovered fallback answers; it does not deduplicate different text or infer Telegram acknowledgement from chat history. While this instance has exact direct or follower transport authority, completed public blocks from local/autonomous work are always sent once and in source order to the instance's authorized target. Connected companion projection is not configurable; disconnect or authority loss is its boundary. Both paths use the configured Rich or HTML renderer and exclude reasoning, tool traffic, token deltas, local prompt text, unknown sources, and stale generations. Each admitted block remains fenced to its exact target, profile/token stamp, leader epoch or follower registration generation, and session generation; non-idempotent acknowledgement ambiguity never authorizes replay.
614
621
 
615
622
  `assistant.activity` is an independent bridge-owned projection over normalized Activity events. Each process reloads the shared file-backed setting at `agent-start` before activity admission, so multi-instance mode cannot continue projecting a stale broader process-local selection. Omitted values resolve to `verbose`, while invalid values fail closed to `quiet`; `thinking` and `tools` select one technical class, while `verbose` enables both. Provider-exposed thinking uses persistent ordinary HTML containing only a standard expandable blockquote with a bounded redacted latest-text window and inline Markdown rendered as Telegram HTML. Like answer drafts, it accumulates for two seconds before the first frame, publishes at most one update every two seconds, and flushes remaining buffered reasoning on terminal completion. Completed executed tools use native Rich Messages: each closed `<Tool>: <status>` root details node renders snake-case names as title words while preserving an uppercase two- or three-letter repeated prefix per word, then the native disclosure chevron reveals an open-by-default `arguments` child plus closed retained `update N` and `result`/`error` child details with lowercase monospaced, marker-free summaries and JSON pre blocks; known-safe Rich rejections fall back to the previous HTML disclosure. The projection captures the exact target and transport stamp at activity admission, serializes updates, preserves tool-start order, closes coalescing across assistant/thinking boundaries, bounds retained text/update memory plus edit frames and message/tool size, disables previews and HTTP(S) auto-link recognition inside technical evidence, and never replays a possibly committed send. Session generations own independent queues, so replacement drops queued old work without waiting on an old call. Bridge-owned assistant output and activity projection share one activity-publication sequence admitted synchronously from the activity bus, so later tool disclosures cannot overtake earlier assistant blocks. Activity targets and transport authority are captured before queued work starts. Active Telegram-turn final replies/artifacts and automatic compaction notices use that same publication owner before the existing direct/follower transport split, without mutually waiting on separate output tails. Final settlement captures the exact active turn and assistant result and reserves its publication position before config loading. Empty outcomes without a publication candidate reserve no position. Replacement, preparation failure, or an unused reservation releases the position without resetting or replying for a replacement turn. Final errors also publish through this owner. Reservations accept one task; cancellation cannot undo a published task, and session reset releases unresolved reservations while fencing old queued tasks. Terminal assistant messages with a publication candidate reserve the final position synchronously; settlement consumes that reservation only for the exact originating turn. Compaction notices enter the same queue immediately, behind the reserved final rather than through a separate notice buffer. Settlement, a new agent run, or session replacement cancels an unconsumed reservation. Notice target and authority are captured when the event is observed. Pi lifecycle completion does not wait for network publication. This order is process-local and does not serialize unrelated instances or independent registered activity handlers. Settlement, replacement, disconnect, failure, or stale authority clears only local ownership; already-sent activity messages remain in chat.
616
623
 
@@ -85,6 +85,8 @@ tmp/telegram/owners.json / <profile-slot> -> bus leader identity + heartbeat
85
85
 
86
86
  Followers do not poll. They register with the leader and receive routed inbound updates from it. Followers still own their local queue, active-turn state, previews, final delivery planning, model switches, and Pi lifecycle. The leader owns only Telegram transport and update fanout. Pi session replacement (`new`) changes follower agent context, not bus membership: a registered follower preserves its registration and refreshes the live context instead of disconnecting. A new follower process also attempts a restore-only registration at session startup when the selected profile's local state contains an exact-`cwd` Workspace binding. The Telegram bus belongs to the local set of cooperating visible Pi instances rather than to the first terminal session forever: if the visible terminal leader exits, a live registered follower can take over leadership.
87
87
 
88
+ The assembled follower receiver admits a registration generation only after its journal-binding preparation succeeds for the same generation and current Pi context. Registration identity is available during preparation so the worker can bind, but early inbound delivery receives a negative ACK rather than writing into a retained old journal. Readiness also binds the Pi session generation, so a reused context object cannot preserve it across session replacement. A registered follower's session refresh awaits binding preparation through `setContext` without re-registering or changing its bus generation. Preparation failure keeps inbound delivery unavailable and records a diagnostic; a later refresh can retry. Normal delivery retry proceeds after successful preparation. Registration requests also retain their original Pi session generation and local attempt authority across awaits. A late reply after session drift, stop or a newer request cannot publish success or tear down newer local authority; a context refresh likewise protects its retained membership from an older pending request. This refusal does not undo remote Thread provisioning or authorize deletion.
89
+
88
90
  A follower may route Bot API work or capability checks only after authenticated registration; an unregistered process that does not own the direct transport lock remains transport-passive. Follower request settlement remains owned by the existing local IPC operation budget rather than being inferred from Bot API method names; followers never invoke `getUpdates`.
89
91
 
90
92
  ## Target Abstraction
@@ -149,7 +151,7 @@ A registered instance exposes:
149
151
 
150
152
  ## Approved Next Contract: Directory Names And Reclaimable Slots
151
153
 
152
- Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. Confirmed owner cleanup now persists the first proven `inactiveSinceMs` transition atomically with target invalidation; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, but production deletion remains disconnected by this release scope; `BACKLOG.md` owns operator smoke and release readiness.
154
+ Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
153
155
 
154
156
  The pure policy distinguishes a free letter, a proposed pressure-reclamation victim, and protected/invalid capacity. Its caller must supply a validated profile-wide snapshot, reservations, proven inactivity start, and explicit protection classification; duplicate legacy letters block selection. The policy performs no filesystem or Telegram operations and does not establish liveness or deletion authority. It proposes a victim only when every profile-wide letter is occupied or reserved; elapsed time alone never triggers retirement.
155
157
 
@@ -159,27 +161,43 @@ Production retirement requires a durable profile-scoped reader/writer ledger own
159
161
 
160
162
  - Journal append, target-scoped Bot API issuance, provisioning, registration, and binding/target replacement must first transact against the ledger. They prune only leases whose process-birth owner is proven dead, reject a matching destructive fence, durably add an admission lease, perform the asynchronous or durable operation, then remove the exact lease transactionally. Exact-target leases conflict with that target; chat-only API work uses chat-wide scope; undecodable journal admission uses profile-wide scope. Duplicate release and process-crash recovery are idempotent, while an unknown release outcome remains protective.
161
163
  - Retirement acquires its fence transactionally only after exact profile/epoch/intent validation and only when no exact, chat-wide, or profile-wide lease conflicts. Once the fence is durable, new matching admission is rejected. After the final protection recheck, the ledger durably advances `fenced` → `deletion-issued` and returns the sole deletion permit; a retry after an ambiguous publication or crash observes the issued phase but receives no second permit. Exact confirmed deletion or already-absence advances to `commit-ready`, and only durable binding plus intent removal permits fence completion. A protection change before permit issuance releases the fence; an unknown deletion, ambiguous commit, authority loss after issuance, or crash retains it.
162
- - A successor may adopt a retained fence only together with the exact durable retirement intent and the existing profile/binding/leader-epoch adoption checks. It preserves original request/acquisition time and phase, replacing only the fence owner/epoch. An adopted `deletion-issued` fence must resolve through exact already-absence or retained unknown outcome and can never issue another deletion request. A different intent never clears or steals the fence. Startup restore-only and fresh capacity paths cannot bypass it; they return a truthful temporary-unavailable result without allocation or eviction.
163
- - The isolated ledger and process regressions now cover admission-before-fence, fence-before-admission, chat/profile-wide conflicts, concurrent contenders, proven-dead lease cleanup, unverifiable-owner protection, malformed-state rejection, exact release idempotence, ambiguous publication recovery, one-shot deletion permits, and successor fence adoption. Production journal stores resolve an admission adapter that conservatively derives batch scopes, holds leases through atomic publication, and releases partial acquisitions on rejection. The production common JSON/multipart client likewise resolves its adapter per current bot/profile: target/chat scopes span all transport retries through settlement, malformed declared targets use profile scope, targetless methods bypass the ledger, and release failure cannot replay a settled request. One production Workspace operation runtime resolves admission before a shared local gate. Leader assembly uses it around chat-wide leader provisioning and profile-wide follower provisioning/registration, disconnect/dead cleanup, leader/follower rename, and display preference/reconciliation; Sync topic lifecycle and routing restore/reclaim use the same profile-wide boundary, so fence rejection precedes store reads and mutation. The isolated retirement executor now requires the matching durable fence, closes admission before its final protection recheck, persists `deletion-issued` before calling an executor-only deletion port with the sole permit, retains unknown outcomes without reissue, and commits only from confirmed `commit-ready`; successor recovery is process-tested. Retained fences now project their uppercase slot into the Thread store's optional external reservations, blocking generic allocation, Workspace claims, and reclamation selection even after binding commit; malformed or unreadable evidence fails closed. The profile runtime resolves a separate `workspace-admission[.<profile>].json`, stores only token-hash authority, preserves named-profile identity across switching, permits changed-token rebind only from a provably empty ledger, and rejects rotation while leases or a fence remain. The admission side is production-wired through leader/follower mutations, topic lifecycle, reroute restore/reclaim, manual disconnect/session-restart cleanup, and exact stale-target recovery; journal-evidence pruning also requires exact-target admission through durable publication. Disconnect/restart cleanup acquires profile admission before the shared leader gate and holds it across cleanup intent, API, binding, and durable settlement, so both retained fence phases reject it before state access. The common async admission runner and API adapter reject concurrent reuse of one live operation ID before lease acquisition, while clearing that process-local guard after acquisition failure or complete settlement so retry-stable durable recovery remains separate. A 2/2 same-model post-fix quorum independently verified the complete production mutation map, delayed reconciliation fix, fence-first and lease-held schedules, and absence of nested-gate deadlock at 0.96 confidence per reviewer. Destructive retirement remains disconnected by release scope; the PASS clears this blocker but does not authorize live deletion.
164
+ - A successor may resume destructive work only with the exact durable retirement intent and the existing profile/binding/leader-epoch adoption checks. Terminal `commit-ready` or `deletion-rejected` recovery may finish after intent removal under the exact profile/fence authority and binding postcondition, without another deletion. It preserves original request/acquisition time and phase, replacing only the fence owner/epoch. An adopted `deletion-issued` fence must resolve through exact already-absence or retained unknown outcome and can never issue another deletion request. A different intent never clears or steals the fence. Startup restore-only and fresh capacity paths cannot bypass it; they return a truthful temporary-unavailable result without allocation or eviction.
165
+ - The isolated ledger and process regressions now cover admission-before-fence, fence-before-admission, chat/profile-wide conflicts, concurrent contenders, proven-dead lease cleanup, unverifiable-owner protection, malformed-state rejection, exact release idempotence, ambiguous publication recovery, one-shot deletion permits, and successor fence adoption. Production journal stores resolve an admission adapter that conservatively derives batch scopes, holds leases through atomic publication, and releases partial acquisitions on rejection. The production common JSON/multipart client likewise resolves its adapter per current bot/profile: target/chat scopes span all transport retries through settlement, malformed declared targets use profile scope, targetless methods bypass the ledger, and release failure cannot replay a settled request. One production Workspace operation runtime resolves admission before a shared local gate. Leader assembly uses it around chat-wide leader provisioning and profile-wide follower provisioning/registration, disconnect/dead cleanup and preserved-owner detachment, leader/follower rename, and display preference/reconciliation; Sync topic lifecycle and routing restore/reclaim use the same profile-wide boundary, so fence rejection precedes store reads and mutation. The isolated retirement executor now requires the matching durable fence, closes admission before its final protection recheck, persists `deletion-issued` before calling an executor-only deletion port with the sole permit, retains unknown outcomes without reissue, and commits only from confirmed `commit-ready`; successor recovery is process-tested. Retained fences now project their uppercase slot into the Thread store's optional external reservations, blocking generic allocation, Workspace claims, and reclamation selection even after binding commit; malformed or unreadable evidence fails closed. The profile runtime resolves a separate `workspace-admission[.<profile>].json`, stores only token-hash authority, preserves named-profile identity across switching, permits changed-token rebind only from a provably empty ledger, and rejects rotation while leases or a fence remain. The admission side is production-wired through leader/follower mutations, topic lifecycle, reroute restore/reclaim, manual disconnect/session-restart cleanup, and exact stale-target recovery; journal-evidence pruning also requires exact-target admission through durable publication. Disconnect/restart cleanup acquires profile admission before the shared leader gate and holds it across cleanup intent, API, binding, and durable settlement, so both retained fence phases reject it before state access. The common async admission runner and API adapter reject concurrent reuse of one live operation ID before lease acquisition, while clearing that process-local guard after acquisition failure or complete settlement so retry-stable durable recovery remains separate. A 2/2 same-model post-fix quorum independently verified the complete production mutation map, delayed reconciliation fix, fence-first and lease-held schedules, and absence of nested-gate deadlock at 0.96 confidence per reviewer. Demand-driven retirement is now operator-authorized and composed with the same ledger/gate. Local regression evidence does not replace disposable client acceptance.
164
166
  - Production mutation coverage is intentionally caller-owned rather than embedded in generic Thread-store primitives.
165
167
  - The shared profile-wide Workspace operation runtime covers observed topic lifecycle, the complete unbound-target and reroute restore/reclaim handlers, manual disconnect/session-restart cleanup, and leader assembly provisioning, registration, cleanup, rename, and display operations. Detached post-provision reconciliation reacquires a fresh profile lease through the same runtime before delayed API/store work. Every admission lease precedes one process-local gate and lasts through API plus durable settlement.
166
168
  - Journal append and common JSON/multipart transport use their own exact/chat/profile leases through publication or settlement. Follower promotion/target replacement, exact stale-target recovery, and journal-evidence pruning likewise hold dedicated admission through their complete mutation. These operations cannot overlap retirement because fence acquisition conflicts with the retained lease even when they do not need the shared local queue.
167
- - Retirement intent preparation/adoption/execution owns its exact gate-and-ledger protocol but remains absent from production composition. Status projection and polling/routing bot-mode writes change only diagnostic or capability metadata; they neither create nor remove Thread/Workspace authority and serialize through the store's local persistence queue.
169
+ - Retirement intent preparation/adoption/execution owns its exact gate-and-ledger protocol. The allocation wrapper invokes it outside ordinary registration/provisioning admission, after a typed capacity failure or to reconcile a retained retirement. Status projection and polling/routing bot-mode writes change only diagnostic or capability metadata; they neither create nor remove Thread/Workspace authority and serialize through the store's local persistence queue.
168
170
  - Production callers of reservation, provision/cleanup intent, target-record, Workspace-binding, and display mutators are contained by the owners above. The generic store remains policy-free for isolated tests and domain composition; calling a primitive directly is not production retirement authority.
169
171
  - Workspace identity remains the selected bot profile plus normalized exact full `cwd`; directory basenames are presentation, never routing keys. Each concurrent binding receives one profile-wide unique lowercase slot from `a` through `z`, persisted on the wire/store as its uppercase equivalent, independent of directory and leader/follower role. This replaces the two competing displayed allocation identities; immutable legacy `instanceSlot` and `bindingKey` remain recovery keys, not another displayed pool.
170
172
  - Automatic display mode is a bot-profile setting shared by Telegram Thread titles and Pi TUI status. The selector offers `letters`, `names`, `directory-snake`, then `directory-title`; absent or invalid values resolve to `letters`. Letters show `A`, `B`, `C`; Names shows the generated dictionary name for the slot, such as `Anchor` for `A`. The retired `directories` value is unsupported: it resolves to `letters`, is omitted from Settings, and is not rewritten automatically. A durable per-Workspace `manualThreadName`, set through Telegram `/name`, overrides any automatic projection until explicitly reset. Bare `/name` immediately enters exact-target rename input: cancel is always available, while reset is shown only when a manual override exists; no intermediate action-selection step exists. Store the automatic preference at `profiles.<name>.threadDisplayMode`, not as a process-local choice or a setting shared by unrelated bots.
171
173
  - Directory formatting operates on the shortest path-segment suffix that distinguishes the normalized exact cwd from every other displayed cwd, while routing and identity continue to use the unmodified full cwd. Each segment is tokenized deterministically at runs of non-letter/non-number characters, lowercase-or-number to uppercase transitions, and the acronym boundary before an uppercase-plus-lowercase word (`apiPRDServer` becomes `api`, `PRD`, `Server`). Empty tokens are discarded. `directory-snake` lowercases tokens and joins them with `_`; segment qualifiers also join with `_`, so `/work/API tools` previews as `api_tools`. `directory-title` joins words with spaces and qualified segments with ` / `; an all-uppercase token containing a letter is preserved (`PRD` remains `PRD`), while every other token is lowercased and capitalized (`api_tools` becomes `Api Tools`). Unicode letters and numbers participate in tokenization and locale-independent Unicode case conversion; punctuation is only a separator. Root and token-empty segments retain the existing safe fallback instead of fabricating an empty title.
172
174
  - Settings previews are computed by the same pure projector used for initial creation and reconciliation, against the current binding set rather than illustrative hard-coded text. Slot suffixes are applied after base formatting (`api_tools_a` and `Api Tools A`) and remain outside abbreviation/case normalization; title-case suffixes use one plain space and never a middle-dot separator. Manual names are never normalized. Case-insensitive or 128-character-bounded collisions remain fail-closed under the existing ambiguity contract. New persisted modes and follower requests require a new negotiated display-format capability; an older peer may continue operating only while the effective profile mode remains Letters or Names, and cannot silently reinterpret either new value.
173
- - The unreachable legacy `directories` projector remains only for decoding old internal snapshots; effective configuration falls back to Letters. The `directory-snake` and `directory-title` modes derive suffix visibility exclusively from the leader's current authenticated live-owner snapshot: one live binding for a normalized exact cwd has no suffix; two or more live bindings for that cwd all expose their globally assigned slots; dormant retained bindings never affect the count. `showSlotSuffix` remains readable for legacy presentation but is ignored by both new projectors and is never written merely because live concurrency changed. The leader captures exact profile, epoch, binding target, and follower registration generation before projection; a provisioning candidate counts only after its authenticated exact owner is admitted to the same serialized Workspace mutation, while an owner losing authority is excluded before reconciliation. Registration/provision completion, authenticated disconnect or confirmed-dead prune, target replacement, and promotion schedule one serialized reconciliation of every affected live same-cwd binding. A failed or partial Telegram rename retains acknowledged per-binding progress and retries only under a fresh live snapshot; dormant tabs are not renamed until they regain authenticated ownership. Equal basenames from different paths still require deterministic parent-path qualification independently from same-cwd suffixing. Preserve the existing `threadName` as generated/recovery identity while automatic modes are selected. New manual names live only in `manualThreadName`; do not guess manual provenance from a legacy name or palette membership. Telegram `/name Name` changes the manual override and displayed title only for its exact originating target. Leader and follower requests carry that target through final generation/binding checks, so replacement cannot redirect a stale dialog mutation. Reset uses the same negotiated `workspace-thread-rename-v1` capability and exact follower generation; the leader computes the current automatic projection from the latest live snapshot, edits the exact target, clears only `manualThreadName`, and persists before acknowledging. Follower metadata refresh preserves an acknowledged display title only while target and registration generation stay unchanged. Named-profile setup preserves the latest saved automatic preference even if another instance changes it while the token form is open.
175
+ - The unreachable legacy `directories` projector remains only for decoding old internal snapshots; effective configuration falls back to Letters. The `directory-snake` and `directory-title` modes derive suffix visibility exclusively from the leader's current authenticated live-owner snapshot: one live binding for a normalized exact cwd has no suffix; two or more live bindings for that cwd all expose their globally assigned slots; dormant retained bindings never affect the count. `showSlotSuffix` remains readable for legacy presentation but is ignored by both new projectors and is never written merely because live concurrency changed. The leader captures exact profile, epoch, binding target, and follower registration generation before projection; a provisioning candidate counts only after its authenticated exact owner is admitted to the same serialized Workspace mutation, while an owner losing authority is excluded before reconciliation. Registration/provision completion, authenticated disconnect or confirmed-dead prune, target replacement, and promotion schedule one serialized reconciliation of every affected live same-cwd binding. A failed or partial Telegram rename retains acknowledged per-binding progress and retries only under a fresh live snapshot; dormant tabs are not renamed until they regain authenticated ownership. Leader startup treats its authenticated follower roster as unsettled for one follower-staleness window: automatic display reconciliations requested by election or incremental re-registration are deferred and coalesced until that boundary. A same-cwd follower returning within the window therefore preserves the already acknowledged suffix without an `unsuffixed → suffixed` Telegram rename; if it stays absent, the settled reconcile removes the suffix once. Explicit mode changes remain immediate, and leader stop/restart invalidates the pending timer. Equal basenames from different paths still require deterministic parent-path qualification independently from same-cwd suffixing. Preserve the existing `threadName` as generated/recovery identity while automatic modes are selected. New manual names live only in `manualThreadName`; do not guess manual provenance from a legacy name or palette membership. Telegram `/name Name` changes the manual override and displayed title only for its exact originating target. Leader and follower requests carry that target through final generation/binding checks, so replacement cannot redirect a stale dialog mutation. Reset uses the same negotiated `workspace-thread-rename-v1` capability and exact follower generation; the leader computes the current automatic projection from the latest live snapshot, edits the exact target, clears only `manualThreadName`, and persists before acknowledging. Follower metadata refresh preserves an acknowledged display title only while target and registration generation stay unchanged. Named-profile setup preserves the latest saved automatic preference even if another instance changes it while the token form is open.
174
176
  - Fresh provisioning projects the candidate together with retained bindings and sends the active mode's title in `createForumTopic`. The exact targeted provision retains creation-title evidence until the Workspace commit publishes the binding and consumes that evidence together. Recovery preserves it even when a starting record already exists; an untargeted or unknown creation never authorizes a title commit. Proven deletion removes exact-target pending creation evidence, including when no current record was committed. Older contradictory pending/deleted snapshots settle that evidence durably before replacement; closed targets and pending cleanup block recovery until reconciliation, rather than becoming active again. The same exact-target check protects follower reconnect/carried-target shortcuts and the final Workspace commit, before creation evidence can be consumed. A matching carried pending target resumes through the provisioner that owns its reserved slot and acknowledged title instead of allocating that slot again. The shared provision-commit helper first commits the claim, then applies the acknowledged title with exact-binding comparison, preserving generic stale-title rejection on target replacement. Switching display mode changes projection only: preserve Thread ID, binding identity, slot, queue ownership, and routing. `displayTitle` records a successful Telegram edit independently of `threadName`, survives same-target registration updates, and is cleared on target replacement. The title reconciler captures profile, mode, leader epoch, and exact live-binding authority before each edit, rechecks after ACK and persistence, and skips dormant bindings. Failed persistence retains acknowledged dirty metadata for a later persist without repeating that API edit; a late or unknown ACK never commits a title to a replacement binding. Keep the stable palette/manual name separate from the current display title so switching back does not generate a different name. The leader owns Telegram title edits and acknowledged follower/TUI convergence, with generation/profile fencing and truthful partial-failure recovery. Successful registration ACKs optionally carry the acknowledged `displayTitle` with the exact target and registration generation, making it available before the initial status refresh. Heartbeats carry later title changes; stale generations cannot update display state. Connected notices use acknowledged titles while runtime `threadName` remains the stable restoration identity. Live bot chooser/notice labels, prompt attribution, and cross-instance agent-target name selection use the same acknowledged projection, but candidate liveness and the captured numeric `{chatId, threadId}` remain authoritative. Ambiguous projected names fail closed. Older peers can ignore the optional field, and no separate polling connection or follower snapshot-read loop is needed; do not expose a setting control that merely stores a preference without updating its promised surfaces.
175
177
  - Reopening a retained inactive binding restores its slot and name without taking leadership. Explicit connection from the same directory may allocate a second binding. Startup restore remains restore-only: it must not evict another Workspace or allocate a fresh binding merely because all remembered bindings are owned. Explicit connection already skips a live peer's migrated binding instead of attempting to adopt it; that admission rule is independent of the display redesign.
176
178
  - Prefer free letters in deterministic order. Only under full slot exhaustion may retirement choose the eligible binding with the oldest proven inactivity start, not necessarily slot `a` after `z`. Time alone never retires a binding. If every slot is protected or its ownership is unverifiable, reject new allocation with a truthful capacity explanation; never evict a live owner or silently expand into `aa`.
177
- - Inactivity begins at a verified transition to no live owner; an idle but connected process is active. Do not derive it from last user message, heartbeat silence alone, snapshot-write time, file age, or any elapsed-time threshold. Legacy records without sufficient evidence have no `inactiveSinceMs` and remain ineligible rather than inheriting an invented old timestamp. Malformed inactivity values are ignored without dropping the binding. Repeated inactive observations retain the first timestamp for pressure ordering; stale same-target upserts cannot postpone it, while target replacement or successful active provisioning clears it.
179
+ - Inactivity begins at a verified transition to no live owner; an idle but connected process is active. Exact confirmed Thread deletion also proves that the binding has no active Telegram target: the common Thread store records first inactivity with invalidation, even if an earlier stale observation already removed the live record. Fenced invalidation publishes this metadata only at its durable commit; closure or an unknown API outcome alone supplies no inactivity. With automatic cleanup disabled, confirmed-dead follower pruning instead acquires profile admission and commits non-destructive owner detachment. It requires one exact owner record and one matching Workspace binding with the same letter; final publication rechecks the registered positive PID's absence, prune generation, leader epoch, profile and replacement registrations. It retains the tab, Workspace identity, slot and journals without claiming that Telegram deleted the Thread. For graceful leader quit with cleanup disabled, the [lifecycle boundary](./architecture.md#runtime-ownership) supplies completed quiescence and exact session/epoch/profile authority for the same atomic detachment; reload/new/resume/fork never enter it. A failed pre-commit publication retains the owner record; a lost post-rename acknowledgement leaves the committed fact intact. No-op snapshot equality cannot bypass the fenced transition into memory. Missing/ambiguous legacy identity remains protected. Live-owner, accepted-work and delivery protection still apply independently before retirement. Do not derive inactivity from last user message, heartbeat silence alone, snapshot-write time, file age, or any elapsed-time threshold. Legacy records without sufficient evidence have no `inactiveSinceMs` and remain ineligible rather than inheriting an invented old timestamp. Malformed inactivity values are ignored without dropping the binding. Repeated inactive observations retain the first timestamp for pressure ordering; stale same-target upserts cannot postpone it, while target replacement or successful active provisioning clears it.
178
180
  - Eligibility excludes live or unverifiable owners, exact transient claims, accepted pending/active work, unresolved delivery authority, and pending provisioning, handoff, or cleanup operations. The store can capture one candidate snapshot from bindings, live records, process-local exact claims, reservations, and persisted provision/cleanup intents, but requires the caller to supply separate tri-state evidence for external live ownership, accepted work, and delivery authority. Only three explicit `clear` results plus valid continuous inactivity produce `eligible`; any protected or unknown dimension fails closed. Before future deletion, `workspace-retirement` selects the oldest eligible pressure victim only after all slots are occupied or reserved. It captures the profile and leader epoch, rechecks protection, then persists one durable Workspace retirement intent containing the complete expected binding, pressure reason, leader epoch, and request time. The intent itself protects that binding from competing selection; only the exact captured intent may be excluded during a fenced recheck. Duplicate intent ids, bindings, targets, or letters fail closed. Each new Workspace binding durably accumulates the registration/profile keys used to route its historical follower journals and marks that set complete. An existing legacy binding may learn current keys but cannot claim its unknown historical set is complete without separate discovery proof. Accepted-work policy treats an exact local queued/active target or any unsettled entry in a binding-specific follower journal as protected. Shared leader-journal entries protect only a decoded exact target; undecodable relevant entries, unreadable sources, and incomplete source enumeration remain unknown. The read-only evidence capture resolves the shared leader journal and every recorded follower key; missing resolvers, read errors, or an incomplete legacy key set make coverage unknown. A complete readable capture may prune a known empty follower-journal key only when a separate process/writer check is explicitly clear and the exact binding, profile, and leader epoch remain current; nonempty, unreadable, writable, or unknown keys remain. For incomplete legacy metadata, `journal` can discover canonical profile-exact follower snapshot and segment roots. Matching paths with unexpected/unreadable shape make discovery incomplete; successfully read discovered journals are target-scoped evidence because their one-way filename hash cannot recover the historical routing key. Legacy completeness remains unset, so later retirement repeats discovery rather than forgetting that historical domain. Live composition includes decoded process-birth proof, registry liveness, queue/journal state, and delivery-authority discovery.
179
- - Reclamation deletes the exact inactive Telegram Thread and therefore removes its history; this is distinct from dropping a local label. The leader owns a durable, exact-binding-and-epoch-fenced retirement intent. The store validates and persists this contract, and preparation resumes an exact current-epoch intent after persistence failure or reload. The isolated executor requires both the caller-owned exclusive gate and durable admission ledger. It acquires or exactly adopts the matching fence, rechecks protection after admissions close, persists `deletion-issued`, and invokes an executor-only `deleteForumTopic` port with the sole non-retried permit. Unknown/rejected results keep the binding, intent, slot, and issued fence; retries and successors cannot issue again and require exact already-absence proof. Success or confirmed absence advances to `commit-ready`, then a store-owned commit hides the free slot while persistence is in flight, restores in-memory authority on known failure, and removes binding+intent before exact fence completion. One failure-resilient Workspace operation runtime owns the shared gate for topic lifecycle, reroute restore/reclaim, leader/follower provisioning, delayed post-provision reconciliation, disconnect and confirmed-dead cleanup, rename, and display reconciliation; registration cannot enter the live registry until its gated provisioning completes. Timer-owned mutation never inherits expired registration authority: it reacquires profile admission before cleanup and remains blocked without store/API effects behind either retained fence phase. The executor consumes that runtime's exposed gate rather than nesting another lock. A successor leader cannot execute the old epoch directly. It may durably adopt exactly one intent into its current epoch only when the profile, complete binding snapshot, and fresh protection recheck still match; known persistence failure restores the old intent and request time. Leader composition now exposes external protection from exact live registry targets, active/queued local targets, shared and historical follower journals, and profile-exact discovery for incomplete legacy history. An accepted item without an exact target for the candidate chat remains unknown. Exact JSON and multipart Bot API operations are counted at the shared direct-client boundary through final settlement; a message-scoped edit/delete without thread identity conservatively protects all bindings in its chat. Historical follower owner keys are decoded before process-birth liveness checks. Exact durable intents block matching claims, activation, title/journal commits, and binding upserts, preventing normal mutation from making their snapshots stale. Closed-but-existing topics do not count as confirmed absence. The former process-local TOCTOU is closed in the isolated ledger/executor and production journal/API/mutation/allocation composition, including delayed post-provision cleanup. Independent post-fix review passed, but production pressure retry and leader execution remain disconnected by release scope; capacity still fails closed instead of evicting. Reuse changes only the letter assignment: routing remains exact numeric target authority, so traffic for the retired target cannot reach the replacement. Recheck eligibility before Telegram mutation and serialize against re-registration. Release the letter only after confirmed deletion or exact already-absent evidence and a successful durable retirement commit. The current confirmed cleanup path records inactivity only after its exact deletion/already-absent result; an unconfirmed API outcome records no inactivity. Unknown API outcomes, failed persistence, owner replacement, and interrupted cleanup retain the reservation and recovery evidence, preventing reassignment or deletion of a replacement target.
181
+ - Reclamation deletes the exact inactive Telegram Thread and therefore removes its history; this is distinct from dropping a local label. The leader owns a durable, exact-binding-and-epoch-fenced retirement intent. The store validates and persists this contract, and preparation resumes an exact current-epoch intent after persistence failure or reload. The isolated executor requires both the caller-owned exclusive gate and durable admission ledger. It acquires or exactly adopts the matching fence, rechecks protection after admissions close, persists `deletion-issued`, and invokes an executor-only `deleteForumTopic` port with the sole non-retried permit. Unknown results keep the binding, intent, slot, and issued fence; retries and successors cannot issue again and require exact already-absence proof. Exact parsed Telegram rejection instead follows the cancellation protocol below, retaining the binding and letter. Success or confirmed absence advances to `commit-ready`, then a store-owned commit hides the free slot while persistence is in flight, restores in-memory authority on known failure, and removes binding+intent before exact fence completion. One failure-resilient Workspace operation runtime owns the shared gate for topic lifecycle, reroute restore/reclaim, leader/follower provisioning, delayed post-provision reconciliation, disconnect and confirmed-dead cleanup, rename, and display reconciliation; registration cannot enter the live registry until its gated provisioning completes. Timer-owned mutation never inherits expired registration authority: it reacquires profile admission before cleanup and remains blocked without store/API effects behind either retained fence phase. The executor consumes that runtime's exposed gate rather than nesting another lock. A successor leader cannot execute the old epoch directly. It may durably adopt exactly one intent into its current epoch only when the profile, complete binding snapshot, and fresh protection recheck still match; known persistence failure restores the old intent and request time. Leader composition now exposes external protection from exact live registry targets, active/queued local targets, shared and historical follower journals, and profile-exact discovery for incomplete legacy history. An accepted item without an exact target for the candidate chat remains unknown. Exact JSON and multipart Bot API operations are counted at the shared direct-client boundary through final settlement; a message-scoped edit/delete without thread identity conservatively protects all bindings in its chat. Historical follower owner keys are decoded before process-birth liveness checks. Exact durable intents block matching claims, activation, title/journal commits, and binding upserts, preventing normal mutation from making their snapshots stale. Closed-but-existing topics do not count as confirmed absence. The former process-local TOCTOU is closed in the isolated ledger/executor and production journal/API/mutation/allocation composition, including delayed post-provision cleanup. Fresh leader/follower allocation now retries once after a confirmed pressure retirement. Allocation requests serialize before taking ordinary leases; failure releases those leases before retirement enters the shared mutation gate and acquires its destructive fence. Restore-only follower startup never enters this path. Protected or unverifiable capacity still fails closed. Reuse changes only the letter assignment: routing remains exact numeric target authority, so traffic for the retired target cannot reach the replacement. Recheck eligibility before Telegram mutation and serialize against re-registration. Release the letter only after confirmed deletion or exact already-absent evidence and a successful durable retirement commit. The current confirmed cleanup path records inactivity only after its exact deletion/already-absent result; an unconfirmed API outcome records no inactivity. Unknown API outcomes, failed persistence, owner replacement, and interrupted cleanup retain the reservation and recovery evidence, preventing reassignment or deletion of a replacement target.
180
182
  - A retired binding no longer promises its former name/letter on reopen; the Workspace may be provisioned anew. Retained message ownership and journal evidence must never route old work through a recycled letter: full binding generation and target remain mandatory independently of display labels.
181
183
  - Implement and validate using isolated stores and mocked Telegram APIs. Do not delete live Threads, migrate live journals/locks manually, publish, or restart operator instances as part of local preparation. Operator smoke on disposable Threads is a separate gate.
182
184
 
185
+ ### Demand-Driven Slot Rotation
186
+
187
+ A fresh leader allocation or authenticated explicit follower registration first attempts ordinary reuse/allocation. Only a typed slot-unavailable result enters pressure retirement; unrelated failures and restore-only follower startup never initiate eviction. The leader serializes allocation requests, releases their ordinary Workspace leases, then holds the shared mutation gate across fresh snapshot capture, retirement preparation/recovery, and fenced execution. A retained retirement is reconciled before admitting another fresh allocation; a `commit-ready` fence left after binding/intent removal can finish without another Telegram request.
188
+
189
+ The executor validates exact bot/profile, leader epoch, fence owner, binding, target, slot, operation and issuance time immediately before `deleteForumTopic`. This executor-only transport uses the existing client without ordinary target admission, which its own fence intentionally blocks; both API retries and network-family fallback are disabled. Only `true` or a definite HTTP 400 absent-Thread response confirms deletion. A parsed Telegram `ok: false` response with matching HTTP/error codes in `400 | 401 | 403 | 404 | 429`, the exact `deleteForumTopic` method and target, and no confirmed-absence evidence instead proves rejection. The executor durably records `deletion-rejected`, withdraws only the matching retirement intent, persists that withdrawal while retaining the binding, then releases the exact fence. Reconstruction finishes cancellation even after intent removal or lost publication acknowledgements; it does not require deletion eligibility or issue another delete. Every fresh attempt uses a new operation identity, even within the same clock tick. A runtime that does not understand the retained rejection phase cannot recover it; use a supporting build, not manual ledger edits. Transport failures, HTTP 5xx, malformed/mismatched responses and forged status fields remain unknown: they retain the binding and fence without reissuing or assigning the letter. No background timer or elapsed-age threshold performs rotation.
190
+
191
+ Protection reads use the binding's strict non-repairing journal inspector. Corruption, unsupported platforms/schemas, incomplete historical coverage and unresolved named-profile paths remain unknown, never empty-work evidence. Legacy inactivity timestamps are not fabricated. If full pressure is blocked only by queued work, rotation may inspect candidates oldest-first and invoke the journal's existing exact dead-owner recovery—never raw file or queue clearing. The current binding, leader/profile authority, local queue, live owner and delivery authority must all remain clear; source enumeration must be complete; each relevant entry must form a whole unoffered receipt group for the same target and owner; and every grouped PID plus process birth must be freshly proven dead before the first mutation. The journal rechecks the exact receipt, owner/acquisition, source IDs, handoff absence and liveness transactionally. Any live/unverifiable owner, partial/mixed group, mutation refusal, replacement or unknown evidence stops that candidate. A committed prefix may remain discarded after interruption, but it grants no deletion authority: rotation performs a fresh complete protection capture and ordinary retirement eligibility check before preparing the destructive fence. Unrelated journal work is untouched, no payload is replayed, and restore-only startup never enters this path. Deletion removes **the Telegram Thread and all its messages** as documented by [`deleteForumTopic`](../.agents/skills/telegram-bot/api.md#deleteforumtopic); retirement removes that exact local Workspace binding, not Pi session files, project files, State Flow memory, or durable update journals. Reopening a retired session may create a fresh Thread rather than restoring its deleted history.
192
+
193
+ ### Ambiguous Retirement Recovery Evidence
194
+
195
+ Production `confirmTargetAbsent` remains unwired until an approved observation can establish exact Thread absence. An empty `editForumTopic` is not that observation: [TDLib `edit_forum_topic` at `ea97bcdd`](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/ForumTopicManager.cpp#L565-L592) returns local success when neither name nor icon is changed, before issuing a topic-edit RPC. A successful empty edit therefore proves neither target presence nor absence. Supplying a retained name or icon would instead risk overwriting a later external edit and is not an implicit read.
196
+
197
+ `sendChatAction/cancel` is accepted by upstream Bot API source but is not in the documented action set and is not a verified existence lookup. [TDLib action handling](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L350-L437) can skip an unneeded action, and its [query handler](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L37-L105) also maps a cancelled query to success. Do not interpret that success as Thread evidence or replace it with a typing indicator during recovery.
198
+
199
+ An explicit operator-confirmed diagnostic send could provide a real target-bound API outcome, but it can leave a message when the Thread exists or the acknowledgement is lost. Approval of that recovery behavior and control is separate from automatic rotation; implementation and disposable live verification remain gated in `BACKLOG.md`. Until then, ambiguous or legacy `deletion-issued` attempts retain their fence. Never infer absence from cached client visibility, release because a target is present, retry an unknown deletion, or edit runtime files to force progress.
200
+
183
201
  ## Approved Next Contract: Session-Aware Workspace Identity
184
202
 
185
203
  Status: locally implemented and validated for the next minor release; final compatibility acceptance and the operator-authorized live smoke remain tracked in `BACKLOG.md`.
@@ -255,7 +273,7 @@ Run this only with an operator-approved disposable bot/profile and disposable Th
255
273
  6. From `All`, create an unbound disposable Thread. Test forward and Replace/restore separately. Confirm accepted content reaches only the selected live instance, Restore carries the selected identity onto the source target, and only the confirmed old/chooser targets are deleted.
256
274
  7. Replace a follower session, then stop the leader and allow follower promotion. Confirm profile, target, slot, saved name, acknowledged title, accepted queue work, and routing survive without a new Thread or competing poller.
257
275
  8. With cleanup enabled, run confirmed `/telegram-disconnect` and one session-restart cleanup on disposable Threads. Confirm cleanup intent/API/store settlement completes before transport release; on an induced transient failure, the exact intent/slot remains pending rather than deleting or reusing another target.
258
- 9. Leave pressure retirement untouched: this release intentionally keeps automatic retirement disconnected. Confirm capacity failure is truthful and no Thread is deleted or slot reused merely from pressure, elapsed time, or heartbeat silence.
276
+ 9. In the separately authorized disposable rotation scenario, exhaust A–Z with known bindings and request one fresh connection. Confirm only the oldest proven inactive, fully unprotected Thread is deleted, including its messages, before its letter is reused. Verify live/queued/unknown candidates remain intact and restore-only startup never evicts. Elapsed time or heartbeat silence alone must not cause deletion.
259
277
  10. Capture `/telegram-status --debug`, relevant redacted runtime logs, screenshots of Telegram titles/routes, and the disposable state snapshots after each role/mode transition. Stop immediately on wrong-target delivery, duplicate publication, unexpected deletion, slot reuse, stale-title authority, split-brain polling, or loss of accepted work.
260
278
 
261
279
  Native Windows must additionally complete the named-pipe and process-lifecycle checklist below. A PASS requires every applicable step with captured client/platform evidence; local tests and mocked Telegram do not substitute for this acceptance.
@@ -311,7 +329,7 @@ In Telegram private-chat Threaded Mode:
311
329
  - Each live bound instance gets one visible thread.
312
330
  - Each instance has a durable single-letter slot (`A`-`Z`) assigned by the extension and a bridge-authored `threadName`.
313
331
  - New slots advance through the alphabet and wrap after `Z` only to a free slot, intentionally capping concurrent visible instances to the alphabet without duplicating occupied letters. The compact `bot.lastSlot` cursor persists while its binding remains live or recoverable, including true `Z → A` wraparound. Pending provisions, reservations, and retained restart bindings occupy their slots until explicit stale/deleted evidence invalidates them.
314
- - Workspace identity is profile-scoped normalized exact `cwd`, independent from process role and lifetime. Readable bounded directory keys are collision-verified against the exact path. Workspace claims reserve a profile-wide letter before asynchronous Bot API work, across both same-directory and different-directory instances; dormant bindings and transient claims reserve their letters. The pure policy uses lowercase `a`–`z`, while the existing transport `slot` field carries the same letter in uppercase. Legacy `instanceSlot` suffixes remain immutable binding-key components, not a second display-slot allocator. Fresh Workspace allocation prefers the first free letter and fails closed at protected capacity; pressure retirement is not yet connected. Claims are transient; successful target/name/slot bindings persist as restart hints. On upgrade, an exact-`cwd` leader record or a manual-follower record matching the current/previous authenticated process is claim-fenced into the first compatible Workspace slot, preserving its target, name, and ordering slot without creating a replacement Thread. After a fully cold restart with several dormant bindings for the same directory and no exact session handoff, the bindings are reclaimed in new claim/opening order; process labels or prior terminal opening order do not identify a particular lowercase slot.
332
+ - Workspace identity is profile-scoped normalized exact `cwd`, independent from process role and lifetime. Readable bounded directory keys are collision-verified against the exact path. Workspace claims reserve a profile-wide letter before asynchronous Bot API work, across both same-directory and different-directory instances; dormant bindings and transient claims reserve their letters. The pure policy uses lowercase `a`–`z`, while the existing transport `slot` field carries the same letter in uppercase. Legacy `instanceSlot` suffixes remain immutable binding-key components, not a second display-slot allocator. Fresh Workspace allocation prefers the first free letter; at full capacity it rotates the oldest proven inactive, fully unprotected binding and retries once. Protected or unverifiable capacity remains blocked. Claims are transient; successful target/name/slot bindings persist as restart hints. On upgrade, an exact-`cwd` leader record or a manual-follower record matching the current/previous authenticated process is claim-fenced into the first compatible Workspace slot, preserving its target, name, and ordering slot without creating a replacement Thread. After a fully cold restart with several dormant bindings for the same directory and no exact session handoff, the bindings are reclaimed in new claim/opening order; process labels or prior terminal opening order do not identify a particular lowercase slot.
315
333
  - A follower that later becomes leader keeps its Workspace target, uppercase slot, and thread name; leadership changes are transport role changes, not identity resets. Immediately after follower promotion succeeds, the new leader retains a short-lived process-local handoff bound to its exact Telegram profile owner key and refreshes it before session replacement. The replacement session consumes it only after acquiring leader authority, converts any surviving manual-follower record for that target into the current leader binding, persists the Workspace target/slot/name under its exact claim, and only then runs ordinary topic provisioning; neither handoff nor dormant binding is live routing authority.
316
334
  - Instance-thread names are short and recognizable. Default provisioning chooses an unused baked 4-6 letter single-word Latin name, excluding current bindings, dormant Workspace hints, and in-flight provisions before creating the Telegram Thread. If the assigned slot's five-name palette is occupied, selection continues through the remaining palettes rather than duplicating an identity. The uppercase slot remains separate ordering metadata. Bare slot titles are fallback/legacy state only. Existing human-named Threads are preserved across reloads and leadership changes; a concurrent process in the same Workspace receives the next Workspace suffix and a distinct Thread.
317
335
  - A thread-local `/start` opens that instance's menu.
@@ -319,7 +337,7 @@ In Telegram private-chat Threaded Mode:
319
337
  - Replies, previews, files, voice, and buttons stay in that thread.
320
338
  - Queue controls and reactions affect only that instance target.
321
339
  - Telegram's native `…typing` indicator for real agent work is sent to that instance thread and mirrored to `All`; `All` is the aggregate surface and should show activity when any bound instance is running a Telegram turn, local prompt, or autonomous continuation. Terminal `Active` remains Telegram-turn-specific. Startup/connect/reload/recovery must not send activity by themselves.
322
- - Generic heartbeat pruning remains silent and preserves the thread as a restart hint. With cleanup enabled, a later exact-PID death confirmation may delete it without posting an `Instance offline` notice. Confirmed deletion removes live routing authority but retains the Workspace binding's friendly name and uppercase ordering slot; a later authenticated reopen probes the old target, replaces it only on exact stale/deleted evidence, and carries that name and slot onto the new Thread.
340
+ - Generic heartbeat pruning remains silent and preserves the thread as a restart hint. With cleanup enabled, a later exact-PID death confirmation may delete it without posting an `Instance offline` notice. With cleanup disabled, a fenced owner-detachment commit records inactivity but retains the Thread and its Workspace slot for restoration or fully guarded capacity rotation. Confirmed deletion removes live routing authority but retains the Workspace binding's friendly name and uppercase ordering slot; a later authenticated reopen probes the old target, replaces it only on exact stale/deleted evidence, and carries that name and slot onto the new Thread.
323
341
  - If the same binding identity returns, authenticated registration can reclaim the thread after the required visibility proof.
324
342
 
325
343
  ## Bot API Evidence For Private-Chat Threaded Mode
@@ -469,6 +487,9 @@ All files containing routing, chat ids, thread ids, or process details use priva
469
487
 
470
488
  - Leader tolerates up to 15 seconds without a follower heartbeat before pruning it from the live registry, so short local event-loop or IPC stalls do not create false routing gaps. A follower response deadline defers its final timeout through one socket poll phase: an acknowledgement already buffered while the Pi/TUI event loop was blocked wins, while a genuinely silent peer still fails in the same event-loop turn. Heartbeat pruning remains liveness bookkeeping. One leader generation owns at most one prune operation; stop makes late endpoint, policy, and cleanup settlement inert, while durable-profile mutation serialization prevents replacement registration from crossing confirmed-dead cleanup.
471
489
  - A missed heartbeat does not delete, close, mark offline, or send a disconnected notice for the follower's Telegram thread binding because the common cause may be leader reload, IPC handoff, or transient reconnect rather than a dead follower.
490
+ - After removal from live routing, the current leader retains at most 26 volatile preservation observations with exact registered PID, generation, target, profile and leader epoch. The existing prune loop rechecks alive/unverifiable PIDs and retries non-destructive publication only after fresh explicit absence and a known disabled-cleanup policy. Missing identity or overflow stays protected. Observations never answer heartbeat/API/forwarding requests, launch processes, or authorize deletion. Enabling cleanup cancels deferred preservation rather than promoting it into destructive work; the existing first-prune cleanup path is not retried by this mechanism.
491
+ - Registry registration invalidates overlapping instance/profile-owner/target observations, even if that replacement is removed before the next prune. Admitted same-instance provisioning cancels the old observation before its store writes can precede live registration. Profile/epoch drift, registry clear and leader stop invalidate it; late old completion cannot erase a replacement runtime's observation. No observation survives leader restart, reconstructs a PID from an opaque instance ID, or invents legacy inactivity.
492
+ - Each unfinished preservation retains one admission operation ID across sequential attempts, so a lost acquisition acknowledgement reuses and releases the exact lease instead of leaking another. A rejected pre-rename write leaves the owner intact; a committed write with lost acknowledgement is recognized without rewriting first inactivity. A liveness interruption is retryable, not successful settlement. Every attempt reacquires profile admission and the shared mutation gate, rechecks authority at rename, and preserves accepted work. Cancellation or scope loss never guesses away an unresolved admission lease; existing exact lease/dead-owner recovery remains authoritative.
472
493
  - Followers treat rejected/missing heartbeat acknowledgements as registration loss: retain the last known target locally, clear registered truth, show `reconnecting` instead of a healthy `follower` status, try to re-register with the current leader, wait a short leader-reload grace window, and retry. They promote only after the exact leader lease becomes stale or inactive; a live owner with an unreachable endpoint leaves the follower disconnected/retrying rather than creating a competing poller.
473
494
  - Persisted current manual-follower bindings survive abrupt process absence as restoration hints when Thread cleanup is disabled. When enabled, graceful Pi quit requests exact-generation teardown before lifecycle suspension; if that envelope is missed, stale pruning may delete only after the leader's OS confirms the exact registered PID has exited.
474
495
  - Fresh registration sends one compact connected notice in the assigned thread. An exact immediate session handoff uses a target-scoped `sendChatAction` as its synchronous visibility probe, avoiding a duplicate notice while retaining stale/ambiguous recovery; other cross-session restoration keeps the connected notice as its probe.
@@ -146,6 +146,8 @@ The registry object on `globalThis.__piTelegramUpdateHandlerRegistry__` is versi
146
146
 
147
147
  `pi-telegram` invokes registered handlers first, then routes the update through its own handlers: commands, app menu, queue menu, model menu, default prompt routing, and callback namespace fallback. If any handler returns `"consume"`, `pi-telegram` skips the rest of routing for that update.
148
148
 
149
+ Default routing ignores `deleted_business_messages`. Telegram's [BusinessMessagesDeleted](https://core.telegram.org/bots/api#businessmessagesdeleted) belongs to a connected business account; [Message.business_connection_id](https://core.telegram.org/bots/api#message) explicitly distinguishes that account's chats from bot chats with the same IDs. Such an update cannot delete private Pi queue entries or pending media groups. Raw handlers still receive the carrier and may own their own namespace-aware behavior. The polling filter is not an authority check: Telegram may return updates created before a changed `allowed_updates` setting.
150
+
149
151
  This means:
150
152
 
151
153
  - Extensions can claim callback namespaces that `pi-telegram` would otherwise forward as `[callback] <data>` text.
@@ -69,12 +69,12 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
69
69
  onItemsDiscarded: (
70
70
  items: readonly Queue.TelegramQueueItem<TContext>[],
71
71
  ctx: TContext,
72
- ) => void;
72
+ ) => boolean;
73
73
  isItemReady: (item: Queue.TelegramQueueItem<TContext>) => boolean;
74
74
  onPromptHandedOff?: (
75
75
  item: Queue.PendingTelegramTurn,
76
76
  ctx: TContext,
77
- ) => void;
77
+ ) => boolean;
78
78
  onControlSettled: (
79
79
  item: Queue.PendingTelegramControlItem<TContext>,
80
80
  ctx: TContext,
@@ -108,8 +108,7 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
108
108
  if (durableItems.length === 0) return true;
109
109
  const settlement = deps.admission.getSettlement();
110
110
  if (!settlement) return false;
111
- settlement.onItemsDiscarded(durableItems, ctx);
112
- return durableItems.every((item) => !settlement.isItemReady(item));
111
+ return settlement.onItemsDiscarded(durableItems, ctx) === true;
113
112
  };
114
113
  const mutation = Queue.createTelegramQueueMutationController({
115
114
  ...deps.store,
@@ -150,8 +149,7 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
150
149
  if ((item.admissionReceipts?.length ?? 0) === 0) return true;
151
150
  const settlement = deps.admission.getSettlement();
152
151
  if (!settlement?.onPromptHandedOff) return false;
153
- settlement.onPromptHandedOff(item, ctx);
154
- return !settlement.isItemReady(item);
152
+ return settlement.onPromptHandedOff(item, ctx) === true;
155
153
  },
156
154
  onControlSettled(item, ctx) {
157
155
  deps.admission.getSettlement()?.onControlSettled(item, ctx);
@@ -1143,7 +1141,7 @@ export function registerTelegramLifecycleRuntimeHooks({
1143
1141
  getActiveTurn: activeTurnRuntime.get,
1144
1142
  loadConfig: configStore.load,
1145
1143
  extractAssistant: Replies.extractRunAssistantMessage,
1146
- isRecoveredAssistantAlreadyPublished: (assistant) =>
1144
+ isAssistantAlreadyPublished: (assistant) =>
1147
1145
  !!assistant.text && assistantOutputRuntime.hasAdmittedTelegramIntermediate(assistant.text),
1148
1146
  getFoldQueuedPromptsIntoHistory:
1149
1147
  lifecycle.shouldFoldQueuedPromptsIntoHistory,