@llblab/pi-kit 0.36.1 → 0.36.3
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.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +2 -2
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +1 -1
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +43 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +45 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +17 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +23 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +24 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +7 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +22 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +52 -10
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +6 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +50 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +120 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +39 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +144 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +15 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +14 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +122 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +9 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +52 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +17 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +25 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +10 -10
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +23 -3
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +24 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +30 -13
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +25 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +34 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +54 -11
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +12 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +4 -3
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +79 -16
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +142 -26
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +18 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +173 -17
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +14 -8
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +166 -10
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +9 -6
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +8 -7
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +56 -2
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +44 -0
- package/node_modules/@llblab/pi-telegram/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/scripts/run-macos-tests.mjs +43 -0
- package/package.json +4 -4
|
@@ -89,12 +89,12 @@ Only the live cleanup origin uses the approved historical-protection reduction.
|
|
|
89
89
|
- `bus` / `bus-api` / `bus-leader` / `bus-follower` / `ownership`: 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, and live message ownership. `bus` owns session-native protocol v3 independently from package build, canonical capabilities, compatibility, process runtime 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, so a retained fence rejects it before leadership acquisition or store mutation. Production leader composition resolves the admission assembly at each operation boundary. The proof-aware follower Restore receiver replaces legacy target replacement: the old `leader.replaceFollowerTarget` producer, parser and receiver are removed, so even authenticated old-format requests are rejected before recipient effects. Profile admission surrounds exact retained-operation, authenticated leader/context, registration-generation, session/CWD and canonical binding/slot checks. `apply` updates only local registration after the leader's canonical commit; an already matching target does not switch again. `inspect` never switches targets and reports the observed local target plus readiness. Neither mode persists canonical state, manufactures deletion/probe evidence or dispatches input. Validated loading alone does not authorize the recipient effect. `threads.withWorkspaceRestoreSnapshot` now holds the canonical transaction while checking the exact retained intent, binding protection and recovery evidence, then supplies fresh binding/owner data to a synchronous read-only observation. Both `apply` and `inspect` run their existing recipient checks, local application and readiness construction inside this boundary, with fresh scoped-intent and live-context checks. The callback contract excludes asynchronous observers; neither cached projection nor callback completion grants canonical persistence. A follower with `canPersist: false` can observe but still cannot use the leader's registration publisher. Native regressions reject receipts, binding regression, owner detachment, intent adoption and context replacement arriving after load; valid reobservation never repeats apply. The transaction spans the local effect, releases on failure, and neither receiver mode changes canonical bytes. The leader recipient path uses the same observation: session/CWD/slot and active-owner selection consume its fresh binding/owner rows, not the warm store, and the transaction spans local identity application and readiness construction. Full-capacity native worker fixtures reject post-grant receipts, binding/owner regression and context changes both before first apply and during inspection after a lost apply reply, preserving the pending original and issued grant without undoing an earlier valid apply. These fixtures do not authorize activation or live fault injection. A composed capability-gated controller and authenticated envelope connect this receiver across native IPC, with one transport attempt and exact readiness-response validation; see the [bus contract](./multi-instance-bus.md#protocol-identity-and-compatibility). Successor inspection can verify a changed follower registration or process only against its actual session/CWD, one current canonical owner, the unchanged binding/slot and observed local target; an old local target remains not ready. Successors cannot use the original apply grant. Follower target preparation uses `threads.assertWorkspaceRestoreRegistration` before recovery allocation, visibility probes and owner transfer, after probe awaits, and before returning the prepared target. The read-only precondition checks fresh canonical and recovery evidence under the snapshot transaction; a protected target requires its exact binding key, slot and relocated target, never the old target. Lost evidence or late recovery conflicts cannot fall through to visibility-error recovery or leave a transient claim held. This is not registration/transport authority: callers still own authentication, profile/session/leader/generation checks and Workspace admission. Bootstrap now re-enters profile admission and the shared mutation gate for `commitWorkspaceRestoreRegistration`; declared Workspace bindings are checked again in the publication snapshot, even without retained Restore. The synchronous callback rechecks captured leader epoch, profile and operator before writing. Missing, unused, reused or expired publication authority cannot report success; the post-commit ACK rechecks current registration identity, allowing heartbeat, connection-time and Thread-name metadata changes. Lost post-publication authority never removes independently published state. Native authenticated bootstrap tests cover the real transaction and late recovery/authority changes. The root protocol identity now advertises `workspace-restore-v1`; every participating peer must run this build before its Restore is used.
|
|
90
90
|
- `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.
|
|
91
91
|
- `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.
|
|
92
|
-
- `thread-cleanup-manager`:
|
|
93
|
-
- `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`;
|
|
92
|
+
- `thread-cleanup-manager`: Proof-only review and confirmed manual inactive-Workspace cleanup coordination. 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 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. The isolated executor requires an injected exclusive Workspace deletion boundary across fresh planner evidence, exact retained-snapshot comparison, permit acquisition/recording, delete callback, and confirmation. The 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 on the leader exposes **Review inactive tabs** under profile-wide admission with a fresh canonical store reload. One canonical 128-bit-digest work-set is retained; the summary reports the proven count, explicit no-deletion state and Back navigation, warns that Clean deletes the Telegram tabs and their messages but not Pi history/project files, and offers one separate **Clean inactive tabs** confirmation. The exact confirmation callback fits Telegram's 64-byte bound and accepts only canonical work-set IDs. Preview and Back perform no deletion. 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 kind-specific acquire/adopt/issue/absence/release/complete methods are implemented; pressure methods reject manual fences and cleanup methods reject pressure fences. The 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`. The 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. The production runtime wraps this coordinator in the shared Workspace gate without nesting ordinary admission under a manual fence, reloading exact evidence both before and after fence acquisition. It captures profile/token/operator/session-generation/leader authority, binds work-set permits to the exact token-scoped ledger identity rather than the human profile name, and rechecks authority before transport, after deletion and after awaited local commit. The direct executor-only client validates the retained exact permit at issuance and disables retries/fallback. A Settings-port adapter reports deleted, outcome-unknown and blocked counts; a retained unreleased fence cannot be reported as completed cleanup. 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. Slotless bindings with proven inactivity and clear protection are eligible for review. Their work-set, manual fence and permit carry explicit `slot: null`; missing, malformed or non-uppercase letter fields are rejected rather than interpreted as absence. Pressure retirement still requires a real uppercase letter. A slotless manual fence protects its exact target/admission scope without reserving another binding's recycled letter. Existing letter-bearing v1 records remain readable; older readers reject the new null-bearing records, so mixed-version cleanup must not bypass that refusal. Native filesystem regressions exercise confirmed and unknown one-shot outcomes, cold work/fence reads and exact binding commit while the replacement letter holder is preserved; deletion transport remains injected. Production supplies `cleanInactiveThreads` only through this fenced runtime. A new owner may adopt a retained fence only with fresh process-birth death proof; alive/unverifiable predecessors remain protected. Native filesystem tests with injected transport cover success, changed/protected candidates, post-fence protection, unknown deletion, authority loss during loading/deletion/commit and commit-ready retry. They are not live Telegram deletion or native Windows acceptance.
|
|
93
|
+
- `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`; slotless retained bindings use that generated name (or their manual override) rather than a recycled letter, even without a live owner. The caller owns triggers and live-owner discovery; successful metadata reuse awaits a targeted display attempt under fresh profile admission before fresh allocation resumes. 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.
|
|
94
94
|
- `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.
|
|
95
95
|
- `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.
|
|
96
|
-
- `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.
|
|
97
|
-
- `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, fenced exact-session target relocation, and primitive provision helpers. Its profile/token/path-bound `workspaceRestore` operations own Restore transitions and publish the exact full source-bound operation in `state[.<profile>].json.workspaceRestore` with binding/owner relocation in one ownership-fenced rename, preserving session, slot, naming preferences and journal evidence. There is one stored commit proof, not two stores exchanging receipts. The same owner enforces strict private regular-file/link/size inspection, scope checks inside publication, capacity and transactional revision CAS. Snapshot `read/relocate/update` mechanics are private; there is no separate Restore module or store factory. POSIX files are `0600`; Windows uses directory ACLs. Restore metadata remains bounded to 26 operations and 1 MiB, within an 8 MiB canonical snapshot. Movement clears stale display/probe evidence without claiming deletion. Conflicting source, binding, slot or targets fail closed. Every supporting snapshot publisher compares the full Restore state and its monotonic revision immediately before rename. It also preserves each retained binding's session identity, slot and relocated target, reserves both targets against other bindings, and rejects owner projections on the old target or conflicting slot. Pending creation and reservation staging reject a retained Restore's binding key, slot or either known target before mutating memory; a creation attempt lacking binding identity also conflicts on the retained owner profile or instance, rather than falling through to another slot. Primitive provisioning therefore cannot publish a conflicting creation intent or call `createForumTopic`, while valid existing-target reuse remains available. Candidate and final-disk validation also reject conflicting pending provisions/reservations, including late evidence, without discarding either intent. Restore publication and transitions inspect the private bounded `.provision-recovery.json` receipt file, matching pending ID, creator instance/profile and leader epoch before considering a recovered target. Both predecessor and candidate references remain protected at final publication. Snapshot publication, Restore transitions and recovery receipt writes serialize through the canonical snapshot transaction before the transport-owner publication fence. Conflicting targets or unreadable recovery evidence block advancement without erasing either source; Restore inspection remains available. Recovery writers never replace a differing receipt or repair damaged JSON, and exact duplicates perform no rewrite. Protected snapshot loading also uses strict recovery-file reads. Before publishing a new Restore, final transactional validation requires a known target for every creation retained in the predecessor snapshot, including entries that an in-memory expiry filter would omit. Canonical targets or exact creator/profile/epoch recovery receipts may establish a non-conflicting target; missing or foreign receipts cannot establish availability. Contradictory canonical/recovered targets fail closed. Loading reads recovery evidence once and, while Restore is retained, validates that exact evidence against the predecessor provisions before replacing any working projection. Conflicting, contradictory or invalid matching receipts reject warm and cold loading without changing disk or the previous projection; retained intent inspection remains available independently. Valid receipts merge before expiry filtering, so a late known creation cannot disappear solely because its original timer elapsed. A failed callback reload remains under the existing worker retry/diagnostic policy without another recipient apply, forwarding or cleanup grant; loading is not effect-time authority. Candidate validation refuses conflicting writes; final disk validation prevents a valid warm projection from silently repairing regressed canonical state. Restore transitions likewise refuse advancement or retirement over inconsistent canonical bindings, while retained intent inspection remains available for source protection. Metadata changes, binding-preserving owner succession and non-destructive owner detachment remain permitted; runtime still authenticates successor session authority. The revision survives final removal, closing empty-record ABA; a read-only Restore observation cannot bless an older binding projection. Warm missing/backward/contradictory evidence, corruption, unsupported schema and revision exhaustion refuse mutation. Old draft receipts or a separate Restore file block rather than migrate or manufacture missing originals. Callers hold Workspace admission and exact source/session/target authority. The initial phase is `relocated`, with no separate `prepared` record or receipt handoff. Subsequent `recipient-issued`, `ready`, source-dispatch and cleanup grants remain distinct; executor adoption never resets issuance. Only authenticated readiness observations may confirm the original recipient or retain a same-session `readyRecipient` successor. Terminal source settlement needs positive journal-owner disposition ACKs, never readiness, queue admission or absence. Canonical `queued` facts remain admission-only; `queue-completed` requires matching queued acceptance/receipt/kind and may upgrade only its proven IDs, leaving siblings nonterminal. Cleanup and retirement reject admission-only sources, including legacy facts; cold terminal receipt facts without acceptance or admission with cleanup fail closed without repair. The prepared `recordSourceAcceptance` stores a separate source-hashed positive execution/recipient result under an issued routing grant; it never counts as settlement or authorizes cleanup/source removal. Exact duplicates are read-only, malformed/contradictory cold evidence is retained without repair, and executor adoption preserves the proof. Follower forwarding composes this pre-report publication from the worker's fresh exact deferred-source hash under its existing dispatch admission; a lost publication reply reconciles only the identical retained proof. Its completion report carries that same hash into journal-owned `removeCompletedExact`; missing/changed sources reject atomically, with no ID-only fallback. Follower Restore now derives its scope hash from the immutable request/operator/retained acceptance, excludes mutable executor/progress, and carries it through admission into atomic source removal plus a scoped journal ACK. The worker requires matching returned and strictly retained evidence with fresh post-publication/inspection authority checks before notification. Production binding composition supplies serialized strict completion ports while preserving ordinary recovery. Follower cold ACK hints now consume exact scoped evidence under fresh admission and authenticated read-only recipient inspection, re-reading after awaits; missing/foreign evidence cannot adopt or settle. Leader command Restore now publishes `completed` acceptance before disposal through a private execution-fenced routed carrier; deferred/queued reports bypass that publisher. Memoized detached evidence handles duplicates without changing the original binding. An optional prepared worker queue-publication barrier now holds readiness through async acceptance and exact receipt/owner reinspection, including grouped and same-process cold reconstruction; serialized production bindings now supply strict full-group v1 queue inspection with exact owner/unoffered source and owner hashes, without recovery or writer admission. Production queued Restore publication now waits for fresh Workspace admission, canonical/live leader identity, exact receipt/source/owner evidence and retained `queued` acceptance, then rechecks receipt and recipient after publication/admission without replay. Same-process worker reconstruction resumes only proof publication, not semantic execution. Native two-source media-group integration now holds whole-receipt readiness through partial proof, changed digest and offered ownership, and resumes only proof on same-process worker reconstruction. A private Restore command carrier now suppresses trailing implicit completion after queued reporting and rejects explicit disposal guards, preserving receipt ownership even if queue commit fails. Native `/continue` keeps one queued continuation through publication faults/reconstruction; `/compact` keeps its confirmation-dialog/completed-source semantics. Direct prompt Restore uses queued admission, not another unqueued completion producer. Cold completed-command ACK continuation now uses fresh admission and exact scope inspection before adoption, read-only canonical current-leader ownership, post-await proof rereads and live/canonical cleanup fences without apply, handler, RPC or disposal replay. Missing/partial/foreign evidence and changed authority preserve protection; actual successor ownership/startup and accepted-work clearance remain fixture preconditions. The strict journal now prepares separate `completeQueuedExact` whole-receipt owner/hash-CAS removal plus atomic scoped ACKs, with complete-group cold continuity and no ordinary/v3 downgrade. It is supplied by the serialized private binding sibling. The worker's explicit scope API still requires full-batch immutable scopes. Cached prepared subsets now require a captured strict full queued-receipt origin inspector: exact complete group/full owner and matching queued-entry hashes precede readiness. Every scoped receipt needs an origin witness. Queue membership is immutable for that owner/acquisition; native scoped ACK publication and cold continuity require complete unoffered group disposal, so matching retained queued-origin witnesses acknowledge that whole receipt after lost replies. Unscoped siblings acquire no marker. The worker consumes those scopes through captured exact disposal/readback capabilities, clearing memory only after returned and retained ACK checks under current binding/context/process/session authority. Required scopes stay sticky; failed or uncertain issuance cannot downgrade to ordinary completion, replay disposal or lend its attempt across worker stop/start. Read-only reconciliation requires every requested exact scope and a proven origin witness for each whole receipt; it never infers ordinary sibling completion from absence. Cold queued ACK hints now share exact leader canonical ownership, pre-adoption scope reads and post-await rereads, publishing terminal receipt facts grouped by receipt/kind without replay. Native cases seed the post-disposal boundary and still supply startup/ownership and work clearance. Production queued acceptance now returns immutable full-receipt scopes through the publication barrier. The worker detaches and retains them before readiness, refuses missing terminal capabilities and uses cached scopes when lifecycle callers complete owned receipts. Its captured post-ACK observer emits a routing hint without requesting Pi dispatch; canonical queue-completed publication still rereads exact proof under fresh admission. Completion-only mux selection permits read-only reconciliation of an issued attempt despite lost execution readiness, never another dispatch or disposal. Subset-scoped sources within one receipt now need strict confirmed whole-receipt queued origin before readiness. Any issued disposition, including pre-write uncertainty, closes execution readiness while permitting completion-only proof reconciliation. Mixed batches of independent whole receipts now settle the scoped group before a separate ordinary group, rechecking owner/context/process/session/binding between them. A component failure cannot roll back another positive ACK. Mux/runtime completion retries reuse exact acknowledged receipt objects for local cleanup only, never readiness or another disposal. Ordinary sources acquire no scoped ACK; their unknown disposition retains ordinary protection. Post-ACK authority loss suppresses a stale scoped worker wake. Native producer/lifecycle fixtures cover grouped, discard, lost ACK/readback and canonical publication interruption; Pi handoff remains supplied. Terminal proof lifetime follows [settlement ordering](./multi-instance-bus.md#restore-settlement-ordering); actual startup was accepted in the operator's 0.52.0 live smoke. Cleanup can be issued only after all originals settle; unknown issuance cannot become not-issued. Exact old-target completion or positively unissued cleanup permits terminal retirement without touching accepted recipient queues or authorizing operation-ID reuse. Lost commit replies reconcile only the exact full operation. These operations perform no transport, source dispatch or deletion. Production resolves this native view for authenticated follower reception, exact unfinished-source startup holds even after the target becomes bound, and pressure-retirement protection. Restore capability is advertised; every participating peer must run 0.52.0 or later. The same scoped Restore snapshot also stores at most 26 source-bound temporary-Thread entries for All commands. Each entry records the exact journal binding and update ID, operator, executor, a unique 128-bit title token, `creating` or `created` phase and the acknowledged operator-chat target. It holds no Workspace binding or slot. `reserveTemporaryThread` publishes `creating` before the caller's single creation request. An existing source entry is returned instead of licensing another creation, and only explicit retirement frees the source. `acknowledgeTemporaryThread` records the target once; `adoptTemporaryThread` and `retireTemporaryThread` are executor-fenced exact CAS transitions that release protection only. Created targets are refused to provisions, reservations, Workspace bindings and owner records, both when staged and at final publication. The only exception is a retained Restore of the same source, which may rebind an existing Workspace and slot to the tab. Transition publication checks this protection against the resulting file, not only its predecessor. Strict parsing rejects malformed tokens, duplicate sources, tokens or targets, empty lists and foreign-chat targets. Followers cannot publish. Routing's `sendAllTabTemporaryThreadChooser` uses this store for a known threadless owner command whenever this process owns a leader epoch (no flag or configuration; each effect rechecks epoch, journal binding, operator and context). Under profile admission it adopts or reuses the source's entry, or reserves one and issues a single `createForumTopic` named as described above. A positive thread ID is acknowledged; any error or missing ID leaves `creating` as an unknown outcome. The command original is reported deferred, never completed, and stays in the All journal. A created entry publishes the full reroute/restore chooser inside the tab, whose target becomes the pending chooser's `sourceTarget`. An unknown entry is held with an All notice and never retried. A failed publication leaves the source retryable, and a replay or restart reuses the same tab. The All-command age limit still terminally settles a never-presented replay but never expires a presented tab. Forward from the tab uses the existing command dispatch and unbound-Thread cleanup: the command runs once in the selected Pi, routing reports the deferred All original complete, and the tab is removed through `thread-reconciler`. A worker completion observer then retires the entry under fresh profile admission and current temporary-Thread authority, adopting a predecessor executor first; absence of the entry or lost authority leaves it retained. When the fresh source still supports deferred abandonment, the tab's chooser also offers one **Cancel routing** action and states that it keeps the original privately, without sending it to Pi, and removes this tab. That click first commits the existing private retention and discard tombstone. It then makes one `thread-reconciler` removal attempt for the entry's own acknowledged target and retires the entry only after confirmed removal. A skipped or failed removal edits the chooser to say the tab could not be removed; the entry keeps protecting the tab, and nothing repeats the deletion automatically. A duplicate click abandons nothing twice. After restart a still-waiting source is revived with its Cancel; a selected or bound one belongs to the worker's first snapshot and is not offered Cancel. A retained tab is protected against other cleanup. The shared `createTelegramCleanupTargetProtection` used by bus, Sync and Thread lifecycle now calls the store's `listTemporaryThreadTargets()`, a fresh strict read of acknowledged targets that protects on any read failure. Routing's `isRerouteTargetProtected` exempts only the exact own entry (token, source and target) passed by that tab's Forward, Cancel or pending unbound cleanup. For example, a prompt typed inside the tab can be routed, but its Forward cannot remove the tab. After the entry retires, a still-pending Forward cleanup is ordinary unbound cleanup. Restore from the tab passes the same own-entry exemption to its source-ownership check and then uses the shared Restore producer with the tab as destination. `threads` accepts that relocation only for the Restore of the same source. The command is dispatched once to the restored recipient and reported complete, and the completion observer retires the entry. The Workspace keeps its slot on the tab, and the tab is not removed. A native leader fixture proves this path with Restore authority enabled. Without a leader epoch or the stores, the legacy All chooser remains the fallback. The lifecycle sections below own Restore from the tab, cancellation cleanup and cleanup protection; unknown creation or removal outcomes are never released automatically. 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.
|
|
96
|
+
- `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. When retirement finds no eligible binding, the wrapper instead reassigns the least recently used letter through the store's metadata-only `reuseLeastRecentlyUsedWorkspaceSlot`, deleting nothing. 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.
|
|
97
|
+
- `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, fenced exact-session target relocation, and primitive provision helpers. Its profile/token/path-bound `workspaceRestore` operations own Restore transitions and publish the exact full source-bound operation in `state[.<profile>].json.workspaceRestore` with binding/owner relocation in one ownership-fenced rename, preserving session, slot, naming preferences and journal evidence. There is one stored commit proof, not two stores exchanging receipts. The same owner enforces strict private regular-file/link/size inspection, scope checks inside publication, capacity and transactional revision CAS. Snapshot `read/relocate/update` mechanics are private; there is no separate Restore module or store factory. POSIX files are `0600`; Windows uses directory ACLs. Restore metadata remains bounded to 26 operations and 1 MiB, within an 8 MiB canonical snapshot. Movement clears stale display/probe evidence without claiming deletion. Conflicting source, binding, slot or targets fail closed. Every supporting snapshot publisher compares the full Restore state and its monotonic revision immediately before rename. It also preserves each retained binding's session identity, slot and relocated target, reserves both targets against other bindings, and rejects owner projections on the old target or conflicting slot. Pending creation and reservation staging reject a retained Restore's binding key, slot or either known target before mutating memory; a creation attempt lacking binding identity also conflicts on the retained owner profile or instance, rather than falling through to another slot. Primitive provisioning therefore cannot publish a conflicting creation intent or call `createForumTopic`, while valid existing-target reuse remains available. Candidate and final-disk validation also reject conflicting pending provisions/reservations, including late evidence, without discarding either intent. Restore publication and transitions inspect the private bounded `.provision-recovery.json` receipt file, matching pending ID, creator instance/profile and leader epoch before considering a recovered target. Both predecessor and candidate references remain protected at final publication. Snapshot publication, Restore transitions and recovery receipt writes serialize through the canonical snapshot transaction before the transport-owner publication fence. Conflicting targets or unreadable recovery evidence block advancement without erasing either source; Restore inspection remains available. Recovery writers never replace a differing receipt or repair damaged JSON, and exact duplicates perform no rewrite. Protected snapshot loading also uses strict recovery-file reads. Before publishing a new Restore, final transactional validation requires a known target for every creation retained in the predecessor snapshot, including entries that an in-memory expiry filter would omit. Canonical targets or exact creator/profile/epoch recovery receipts may establish a non-conflicting target; missing or foreign receipts cannot establish availability. Contradictory canonical/recovered targets fail closed. Loading reads recovery evidence once and, while Restore is retained, validates that exact evidence against the predecessor provisions before replacing any working projection. Conflicting, contradictory or invalid matching receipts reject warm and cold loading without changing disk or the previous projection; retained intent inspection remains available independently. Valid receipts merge before expiry filtering, so a late known creation cannot disappear solely because its original timer elapsed. A failed callback reload remains under the existing worker retry/diagnostic policy without another recipient apply, forwarding or cleanup grant; loading is not effect-time authority. Candidate validation refuses conflicting writes; final disk validation prevents a valid warm projection from silently repairing regressed canonical state. Restore transitions likewise refuse advancement or retirement over inconsistent canonical bindings, while retained intent inspection remains available for source protection. Metadata changes, binding-preserving owner succession and non-destructive owner detachment remain permitted; runtime still authenticates successor session authority. The revision survives final removal, closing empty-record ABA; a read-only Restore observation cannot bless an older binding projection. Warm missing/backward/contradictory evidence, corruption, unsupported schema and revision exhaustion refuse mutation. Old draft receipts or a separate Restore file block rather than migrate or manufacture missing originals. Callers hold Workspace admission and exact source/session/target authority. The initial phase is `relocated`, with no separate `prepared` record or receipt handoff. Subsequent `recipient-issued`, `ready`, source-dispatch and cleanup grants remain distinct; executor adoption never resets issuance. Only authenticated readiness observations may confirm the original recipient or retain a same-session `readyRecipient` successor. Terminal source settlement needs positive journal-owner disposition ACKs, never readiness, queue admission or absence. Canonical `queued` facts remain admission-only; `queue-completed` requires matching queued acceptance/receipt/kind and may upgrade only its proven IDs, leaving siblings nonterminal. Cleanup and retirement reject admission-only sources, including legacy facts; cold terminal receipt facts without acceptance or admission with cleanup fail closed without repair. The prepared `recordSourceAcceptance` stores a separate source-hashed positive execution/recipient result under an issued routing grant; it never counts as settlement or authorizes cleanup/source removal. Exact duplicates are read-only, malformed/contradictory cold evidence is retained without repair, and executor adoption preserves the proof. Follower forwarding composes this pre-report publication from the worker's fresh exact deferred-source hash under its existing dispatch admission; a lost publication reply reconciles only the identical retained proof. Its completion report carries that same hash into journal-owned `removeCompletedExact`; missing/changed sources reject atomically, with no ID-only fallback. Follower Restore now derives its scope hash from the immutable request/operator/retained acceptance, excludes mutable executor/progress, and carries it through admission into atomic source removal plus a scoped journal ACK. The worker requires matching returned and strictly retained evidence with fresh post-publication/inspection authority checks before notification. Production binding composition supplies serialized strict completion ports while preserving ordinary recovery. Follower cold ACK hints now consume exact scoped evidence under fresh admission and authenticated read-only recipient inspection, re-reading after awaits; missing/foreign evidence cannot adopt or settle. Leader command Restore now publishes `completed` acceptance before disposal through a private execution-fenced routed carrier; deferred/queued reports bypass that publisher. Memoized detached evidence handles duplicates without changing the original binding. An optional prepared worker queue-publication barrier now holds readiness through async acceptance and exact receipt/owner reinspection, including grouped and same-process cold reconstruction; serialized production bindings now supply strict full-group v1 queue inspection with exact owner/unoffered source and owner hashes, without recovery or writer admission. Production queued Restore publication now waits for fresh Workspace admission, canonical/live leader identity, exact receipt/source/owner evidence and retained `queued` acceptance, then rechecks receipt and recipient after publication/admission without replay. Same-process worker reconstruction resumes only proof publication, not semantic execution. Native two-source media-group integration now holds whole-receipt readiness through partial proof, changed digest and offered ownership, and resumes only proof on same-process worker reconstruction. A private Restore command carrier now suppresses trailing implicit completion after queued reporting and rejects explicit disposal guards, preserving receipt ownership even if queue commit fails. Native `/continue` keeps one queued continuation through publication faults/reconstruction; `/compact` keeps its confirmation-dialog/completed-source semantics. Direct prompt Restore uses queued admission, not another unqueued completion producer. Cold completed-command ACK continuation now uses fresh admission and exact scope inspection before adoption, read-only canonical current-leader ownership, post-await proof rereads and live/canonical cleanup fences without apply, handler, RPC or disposal replay. Missing/partial/foreign evidence and changed authority preserve protection; actual successor ownership/startup and accepted-work clearance remain fixture preconditions. The strict journal now prepares separate `completeQueuedExact` whole-receipt owner/hash-CAS removal plus atomic scoped ACKs, with complete-group cold continuity and no ordinary/v3 downgrade. It is supplied by the serialized private binding sibling. The worker's explicit scope API still requires full-batch immutable scopes. Cached prepared subsets now require a captured strict full queued-receipt origin inspector: exact complete group/full owner and matching queued-entry hashes precede readiness. Every scoped receipt needs an origin witness. Queue membership is immutable for that owner/acquisition; native scoped ACK publication and cold continuity require complete unoffered group disposal, so matching retained queued-origin witnesses acknowledge that whole receipt after lost replies. Unscoped siblings acquire no marker. The worker consumes those scopes through captured exact disposal/readback capabilities, clearing memory only after returned and retained ACK checks under current binding/context/process/session authority. Required scopes stay sticky; failed or uncertain issuance cannot downgrade to ordinary completion, replay disposal or lend its attempt across worker stop/start. Read-only reconciliation requires every requested exact scope and a proven origin witness for each whole receipt; it never infers ordinary sibling completion from absence. Cold queued ACK hints now share exact leader canonical ownership, pre-adoption scope reads and post-await rereads, publishing terminal receipt facts grouped by receipt/kind without replay. Native cases seed the post-disposal boundary and still supply startup/ownership and work clearance. Production queued acceptance now returns immutable full-receipt scopes through the publication barrier. The worker detaches and retains them before readiness, refuses missing terminal capabilities and uses cached scopes when lifecycle callers complete owned receipts. Its captured post-ACK observer emits a routing hint without requesting Pi dispatch; canonical queue-completed publication still rereads exact proof under fresh admission. Completion-only mux selection permits read-only reconciliation of an issued attempt despite lost execution readiness, never another dispatch or disposal. Subset-scoped sources within one receipt now need strict confirmed whole-receipt queued origin before readiness. Any issued disposition, including pre-write uncertainty, closes execution readiness while permitting completion-only proof reconciliation. Mixed batches of independent whole receipts now settle the scoped group before a separate ordinary group, rechecking owner/context/process/session/binding between them. A component failure cannot roll back another positive ACK. Mux/runtime completion retries reuse exact acknowledged receipt objects for local cleanup only, never readiness or another disposal. Ordinary sources acquire no scoped ACK; their unknown disposition retains ordinary protection. Post-ACK authority loss suppresses a stale scoped worker wake. Native producer/lifecycle fixtures cover grouped, discard, lost ACK/readback and canonical publication interruption; Pi handoff remains supplied. Terminal proof lifetime follows [settlement ordering](./multi-instance-bus.md#restore-settlement-ordering); actual startup was accepted in the operator's 0.52.0 live smoke. Cleanup can be issued only after all originals settle; unknown issuance cannot become not-issued. Exact old-target completion or positively unissued cleanup permits terminal retirement without touching accepted recipient queues or authorizing operation-ID reuse. Lost commit replies reconcile only the exact full operation. These operations perform no transport, source dispatch or deletion. Production resolves this native view for authenticated follower reception, exact unfinished-source startup holds even after the target becomes bound, and pressure-retirement protection. Restore capability is advertised; every participating peer must run 0.52.0 or later. The same scoped Restore snapshot also stores at most 26 source-bound temporary-Thread entries for All commands. Each entry records the exact journal binding and update ID, operator, executor, a unique 128-bit title token, `creating` or `created` phase and the acknowledged operator-chat target. It holds no Workspace binding or slot. `reserveTemporaryThread` publishes `creating` before the caller's single creation request. An existing source entry is returned instead of licensing another creation, and only explicit retirement frees the source. `acknowledgeTemporaryThread` records the target once; `adoptTemporaryThread` and `retireTemporaryThread` are executor-fenced exact CAS transitions that release protection only. Created targets are refused to provisions, reservations, Workspace bindings and owner records, both when staged and at final publication. The only exception is a retained Restore of the same source, which may rebind an existing Workspace and slot to the tab. Transition publication checks this protection against the resulting file, not only its predecessor. Strict parsing rejects malformed tokens, duplicate sources, tokens or targets, empty lists and foreign-chat targets. Followers cannot publish. Routing's `sendAllTabTemporaryThreadChooser` uses this store for a known threadless owner command whenever this process owns a leader epoch (no flag or configuration; each effect rechecks epoch, journal binding, operator and context). Under profile admission it adopts or reuses the source's entry, or reserves one and issues a single `createForumTopic` named as described above. A positive thread ID is acknowledged; any error or missing ID leaves `creating` as an unknown outcome. The command original is reported deferred, never completed, and stays in the All journal. A created entry publishes the full reroute/restore chooser inside the tab, whose target becomes the pending chooser's `sourceTarget`. An unknown entry is held with an All notice and never retried. A failed publication leaves the source retryable, and a replay or restart reuses the same tab. The All-command age limit still terminally settles a never-presented replay but never expires a presented tab. Forward from the tab uses the existing command dispatch and unbound-Thread cleanup: the command runs once in the selected Pi, routing reports the deferred All original complete, and the tab is removed through `thread-reconciler`. A worker completion observer then retires the entry under fresh profile admission and current temporary-Thread authority, adopting a predecessor executor first; absence of the entry or lost authority leaves it retained. When the fresh source still supports deferred abandonment, the tab's chooser also offers one **Cancel routing** action and states that it keeps the original privately, without sending it to Pi, and removes this tab. That click first commits the existing private retention and discard tombstone. It then makes one `thread-reconciler` removal attempt for the entry's own acknowledged target and retires the entry only after confirmed removal. A skipped or failed removal edits the chooser to say the tab could not be removed; the entry keeps protecting the tab, and nothing repeats the deletion automatically. A duplicate click abandons nothing twice. After restart a still-waiting source is revived with its Cancel; a selected or bound one belongs to the worker's first snapshot and is not offered Cancel. A retained tab is protected against other cleanup. Native worker/Filesystem regressions execute an actual unbound deletion plan against waiting tabs with warm, cold, newly created and unreadable protection evidence; all veto the transport. Cached inaccessible callbacks without message content or a Thread header still Cancel through exact current journal custody, but a source whose custody changed to queued remains untouched. The shared `createTelegramCleanupTargetProtection` used by bus, Sync and Thread lifecycle now calls the store's `listTemporaryThreadTargets()`, a fresh strict read of acknowledged targets that protects on any read failure. Routing's `isRerouteTargetProtected` exempts only the exact own entry (token, source and target) passed by that tab's Forward, Cancel or pending unbound cleanup. For example, a prompt typed inside the tab can be routed, but its Forward cannot remove the tab. After the entry retires, a still-pending Forward cleanup is ordinary unbound cleanup. Restore from the tab passes the same own-entry exemption to its source-ownership check and then uses the shared Restore producer with the tab as destination. `threads` accepts that relocation only for the Restore of the same source. The command is dispatched once to the restored recipient and reported complete, and the completion observer retires the entry. The Workspace keeps its slot on the tab, and the tab is not removed. A native leader fixture proves this path with Restore authority enabled. Without a leader epoch or the stores, the legacy All chooser remains the fallback. The lifecycle sections below own Restore from the tab, cancellation cleanup and cleanup protection; unknown creation or removal outcomes are never released automatically. 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.
|
|
98
98
|
- `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. Its composed producer captures original journal references and canonical binding authority, then uses `advanceTelegramWorkspaceRestore` for native transitions and authenticated recipient observations under admission. The leader adapter updates its actual local identity; the follower adapter invokes the native one-attempt controller and publishes its live registry target only after exact readiness validation. Its final synchronous registry write runs through `threads.commitWorkspaceRestoreRegistration`: fresh disk/recovery validation and the caller's current-authority check remain inside the same canonical transaction through publication. The preparation assertion shares that validator but cannot substitute for the commit boundary. A late recovery conflict or disk-only binding regression retains the original and issued recipient grant without publishing new routing authority, forwarding input, deleting a Thread or undoing an already applied recipient target. Readiness and dispatch recheck the canonical relocated target as well as session, generation and the recipient's local target; local readiness cannot substitute for a regressed binding/owner projection. Only fresh issuance selects `apply`; retained or unknown issuance selects read-only `inspect`. Exact operation/session/generation/target/slot proofs and post-await/final-observation checks remain mandatory. Same-session successor readiness never changes the original issuance recipient; foreign sessions, ambiguous owners and regressed local targets stay protected. Lost readiness replies reconcile exact retained proof. The shared control path preserves original selection across uncertainty and limits recipient/dispatch callbacks to one admitted invocation; there is no replaceable producer or exported cleanup callback. Reroute forwarding resolves full live recipient binding/generation authority for the selected target and rechecks it after delivery; only a positive matching delivery ACK reports original-source completion through the admission carrier. Restore first publishes that acceptance from a fresh exact worker-source observation and the current recipient binding/generation; failure retains the original without resending. The report carries a detached source digest through admission; the worker checks current binding/context/process/session identity and requires `journal.removeCompletedExact` to match it transactionally before removal. Unsupported exact-disposal ports fail closed; duplicate ordinary reports cannot downgrade the guard. For scoped Restore reports, only an exact returned ACK plus strict retained readback emits the existing post-removal observer; missing inspection capability blocks disposal, and foreign/missing evidence or post-commit authority changes suppress notification. Exact capabilities are snapshotted at construction, and passed marker/inspection arguments are detached. A report or absent source still is not settlement proof, and lost-ACK continuation requires the strict active-journal scope-reader port. Original journal message targets survive rerouting and are not proof of accepted execution destinations; target-only inspection cannot replace conservative binding-wide accepted-work protection. The existing `workspace-retirement` capture now offers the prepared `requireBindingProvenance` option: relevant binding-associated entries remain protected regardless of original target, while nonempty shared/discovered sources without binding provenance return unknown instead of target-based clearance. Complete empty evidence can clear; incomplete or unreadable evidence cannot. Native queued-receipt and corrupted-family fixtures use strict read-only inspection with real reference leases and prove that capture changes no source files or receipts. The default retirement policy is unchanged. Restore cleanup now invokes this strict capture through the composition root before issuing cleanup and at the existing close/delete protection boundaries. A read-only canonical transaction supplies fresh binding references, unioned with retained predecessor references; cache-only metadata cannot clear work added while close awaited. Missing capture, unavailable snapshots, unknown journals and observation errors (including errors after a clear callback result) remain protective. Both roles have native worker fixtures for queued work whose original target differs, damaged journals and references published during close; accepted recipient work remains intact and skipped deletion never retires the intent or replays an issued grant. Other dispatch/cleanup fixtures explicitly supply a clear-evidence precondition and do not prove journal clearance. No own-source exemption exists: own accepted originals and unclassified shared controls may hold cleanup. A confirmed completion in the same source journal now also wakes a ready operation whose originals already have positive settlements and whose cleanup remains unissued. The wake reacquires current profile admission and authority, re-reads the retained operation and strict protection, and never adds the unrelated completion to its settlements. An unrelated queued receipt, foreign journal, ended authority or missing original settlement cannot supply this wake; unknown protection still holds, and an issued cleanup is never retried. Native worker callback-completion fixtures cover both roles with a separately supplied clearance precondition. The optional `onWorkspaceRestoreRecipientObserved` runtime hook in `bus-leader` uses existing authenticated heartbeat traffic, not a new RPC or completion ACK. Both peers must advertise Restore; missing auth/epoch/operator/profile-reader evidence suppresses observation. Delivery and the callback's currentness fence recheck leader epoch, operator, profile, runtime generation and exact live registration; heartbeat/connection timestamps and names are incidental. Runtime stop invalidates the fence before teardown, and listener settlement ends it. Identical in-flight observations coalesce, while old finalization cannot erase a successor observation. Listeners receive isolated snapshots and must return asynchronous work to retain their fence; the heartbeat ACK does not await listener work, and listener or diagnostic-sink failure cannot turn it into a failed ACK. Native IPC fixtures cover these boundaries, including default-profile identity and stop/restart. The composition root now returns the routing controller's continuation promise to preserve that fence through fresh admission and cleanup. Source evidence and recipient hints use distinct typed inputs: a hint cannot manufacture a source settlement. The recipient-hint path independently matches the live registration, retained ready-recipient instance/session/generation, normalized CWD, slot and target; it selects only ready operations in the current source journal whose original IDs already have positive settlements and whose cleanup is unissued. It rechecks this evidence after acquiring admission and carries both local authority and notification currentness through awaited effects. Eight native worker/IPC cases use strict real-journal inspection rather than supplied clearance: a recipient marker worker positively consumes accepted input without emitting a source-leader completion callback, then heartbeat-driven cleanup resumes once. Independent accepted work remains protective until separately consumed; damaged journals, missing original ACKs and uninspected successor generations cannot clear cleanup. A registration change after close retains the issued grant; an uncertain deletion reply cannot be replayed even by a current matching recipient. These fixtures prove native marker consumption, not live Pi scenario acceptance. Cold-snapshot successor inspection also crosses authenticated native IPC: after a new leader adopts the retained operation, a same-session follower successor answers `inspect` under fresh admission and the read-only canonical transaction without applying a local target, even while the original grant is still `recipient-issued`. Confirmation records only `readyRecipient`; the original recipient, request, source settlements and issued cleanup remain unchanged and cannot be reissued or retired. Foreign sessions, ended epochs and unadopted executors observe nothing. These fixtures supply successor registration publication as a precondition and do not prove startup composition. `advanceTelegramWorkspaceRestore` now adopts a retained operation whose full request and operator exactly match but whose executor differs, provided the caller's authority is current; a lost adoption reply reconciles from the retained operation. Adoption changes only executor authority: a `relocated` operation may still receive its first issuance, while `recipient-issued` and `ready` operations proceed only through inspection, and retained routing/cleanup evidence is never reset, settled or retired. The predecessor executor's later transitions are fenced by the executor CAS. Mismatched requests, other operators and ended authority neither adopt nor rewrite canonical bytes. A chooser re-click reaches this path; a new leader can also adopt a ready forwarded or completed-command Restore through exact scoped-ACK continuation. A new leader does not reconstruct source-bound controls. Restore has one controller: the legacy leader new-slot allocation and follower replacement branches are removed. Without that controller or current Restore authority (an unnegotiated peer or lost leadership), a fresh Restore click is refused before selection or any Restore transition, so the chooser keeps its ordinary routes and Cancel. After restart, root composition holds retained Restore originals as historical inputs. The Historical inputs menu entry and its confirmation submenu are removed; old callbacks are refused without routing or abandonment. Existing exact retained abandonment may still retire an unsent Restore through the cold continuation below. `threads.workspaceRestore.retireAbandoned` requires the exact operation, executor and operator, `ready` phase, absent routing and the complete sorted original set. It releases protection without cleanup: the relocated binding, owner and slot stay on the restored Thread, and the previous Thread is kept. Nothing is delivered, deleted or reissued. An interrupted retirement keeps protection and a later independently completed update may recheck its retained proof. The journal's read-only `inspectAbandonedPending(updateId)` returns proof only when exactly one committed `abandon-<sha256>` discard tombstone exists, the source is no longer pending and its private retention validates against the same binding, entry hash and disposition. Missing or altered retention throws, and inspection never repairs. Root composition exposes this strictly for the active leader journal under an `operator-disposition` reference. Any later completion in that journal selects ready, undispatched Restores and requires committed abandonment evidence for every original, then rechecks under fresh profile admission. Retirement then requires matching journal binding, update and owner authority (`telegram-owner:<operator>`); a predecessor executor is adopted first. Startup alone, journal absence, unverifiable evidence or another owner's authority keep protection. Recipient heartbeat hints also select a follower-owned `recipient-issued` operation when the live same-session registration already names the relocated target, normalized CWD and slot. Under fresh admission, `advanceTelegramWorkspaceRestore` adopts it and asks the follower controller only to `inspect`. Positive readiness records `readyRecipient` without changing the original recipient, and issues no dispatch or cleanup; a lost reply or unready target keeps the grant issued. An old target, foreign session, `relocated` phase, leader owner, other journal or stale hint never reaches the recipient. Once ready and still undispatched, the operation can end only after exact retained abandonment proof covers every original. A source-journal completion similarly selects a leader-owned `recipient-issued` operation issued to another leader instance, when this process has the same session and normalized CWD and its local leader identity already holds the relocated slot and target. After a fresh load and admission it adopts and inspects only. The read-only snapshot must show one current binding at the relocated target and one active owner record for this leader instance. Confirmation records a `readyRecipient` for this instance and session generation; it never calls `setCurrentLeaderIdentity`, dispatch or cleanup. Same-process lost replies stay with the original chooser's inspect path. A still-owned predecessor record, old local target, foreign session or CWD, follower owner, other journal or inactive context keep the grant issued. Both recovery paths call `advanceTelegramWorkspaceRestore` with `inspectOnly`. This also lets them take an operation still in `relocated`: the canonical binding already moved, so a same-session successor starts on the relocated target. In that case, and only when the owner was another instance, the first grant is issued to the observed successor and proven by `inspect`; `apply` never runs. `inspectOnly` never commits a relocation when no operation is retained. A successor still on the old target, or the same leader process that owns the chooser, is not selected. Issued, unready, partially abandoned or gated Restores stay protected. Without the strict abandonment reader, source absence never makes a Restore finishable. Exact evidence for any own-source exemption and interrupted startup integration remain activation gates; journal absence is never source completion evidence. The composed worker-owner observers pass committed queue receipts and completion IDs with the journal binding captured before completion commit. Routing snapshots the proof and generation, then re-enters the existing profile admission/gate without awaiting that continuation inside the dispatch gate. It rereads exact retained operations, records only matching unsettled source IDs, issues old-target cleanup once through its existing owner, and retires only terminal evidence. Routing protects both targets of every retained Restore, including against another Restore and ordinary unbound cleanup. Local queued and active work also protects its target. Cleanup exempts only its own old target under an exact retained issued grant, rechecking that evidence and protection at the reconciler's API boundaries. Known protection prevents grant issuance; protection appearing during cleanup retains the issued attempt without retry. A reconciler skip is not deletion confirmation and cannot retire the operation. Stale authority and unknown issued cleanup stay protected; a lost ACK cannot be reconstructed from absence. The continuation never captures an expired dispatch callback. For a ready forwarded Restore with unissued cleanup, completion/recipient hints now read exact immutable removal scopes under fresh admission through the active-journal `operator-disposition` reference. Positive ACKs authorize exact executor adoption and authenticated follower `inspect`, never apply, handler, forwarding or removal replay. Fresh session/CWD/slot/target and protocol checks gate inspection even when registration generation changes. Every unsettled proof is re-read after the await before canonical settlement; partial/missing evidence cannot release another source. The observed live-recipient fence remains active through cleanup. Missing/foreign receipts cannot re-key intent; interrupted settlement publication resumes on a later hint from unchanged journal evidence. Actual successor registration and accepted-work clearance remain fixture preconditions; terminal ACK lifetime and startup composition are still gated. Producer, controller, receiver and settlement are composed and gated by the negotiated `workspace-restore-v1` capability on both peers. Exact local message ownership keeps a published chooser callback local even after its Thread moves to a follower. Transport stays in `bus*`. Native full-capacity tests drive original and callback updates through the real worker and production producer for both roles.
|
|
99
99
|
- `media` / `text-groups` / `time-injection` / `turns` / `inbound`: inbound extraction, rich reply plaintext, grouped debounce, split-text coalescing (one shared pending-group machine in `media`, `transitionTelegramPendingGroups`, under both albums and split text; batches end at a message ID, never a count), 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. Sticker format follows Bot API `Sticker.is_video` / `is_animated`: static WebP may enter image content, while video WebM and animated TGS remain file attachments with their native extension/MIME and are never read as image payloads. The Sticker object has no documented `mime_type`; frame extraction is not performed.
|
|
100
100
|
- `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.
|
|
@@ -119,7 +119,7 @@ Only the live cleanup origin uses the approved historical-protection reduction.
|
|
|
119
119
|
|
|
120
120
|
Pi is the primary and only officially supported host. `pi-telegram` may still accept narrow, host-neutral representation differences at its existing Pi-facing boundary when they preserve native Pi behavior and do not create a second runtime policy layer:
|
|
121
121
|
|
|
122
|
-
- `prompts`
|
|
122
|
+
- `prompts` uses Pi's structured `telegram` section for stable connection guidance and a hidden `telegram-turn` message for Telegram-specific turn instructions. Local and custom starts do not receive a new turn note, and Telegram/local switching leaves the connection section unchanged. The note remains in session history as context for its originating input; it is not re-injected on later local/custom starts or resume. Hosts without structured sections, caller-supplied custom hooks and an earlier extension-owned opaque prompt retain equality-guarded append/strip fallback for strings or ordered block arrays without collapsing host-owned blocks. An absent/null prompt is treated as empty, including disconnected metadata stripping. An unchanged fallback returns no override; the bridge does not clear another extension's forced prompt or promise cache stability while that independent override remains active.
|
|
123
123
|
- `pi` normalizes settings-manager construction that is either synchronous or asynchronous, then adapts either Pi's legacy enabled-model methods or a generic `get` / `set` settings service before model-menu reads and scoped-model persistence use it. Hosts without an explicit reload method rely on fresh asynchronous construction; durable writes still require `flush`.
|
|
124
124
|
- `lifecycle` continues to require Pi's semantic `agent_settled` boundary. It does not infer terminal settlement from host-specific `agent_end`, retry, or stop events; a compatibility shim must reproduce that contract before it can safely support activity identity and unrecovered-error finalization.
|
|
125
125
|
|
|
@@ -457,7 +457,7 @@ The journal owns the `abandonPending` primitive for exact unclaimed v1 sources.
|
|
|
457
457
|
|
|
458
458
|
Abandonment checks exact source binding and entry evidence, caller authority at publication boundaries, and existing discard/retention agreement on retry. It refuses queued, failed, retrying, foreign, changed or absent sources, does not repair corrupt evidence, and is not exposed by the v3 custody store. Retention/publication failure leaves original authority intact unless strict read-back proves the exact tombstone committed before a later compaction failure. The worker now exposes source-bound abandonment only for a captured v1 pending entry with its exact live deferred claim, session signal, journal binding and transport/context authority. `abandonTelegramDeferredUpdate` is the admission-carrier entrypoint; raw journal cancellation is not a substitute for worker coordination. The carrier suspends its execution fence during cancellation, including forwarded clones and late completion/queue reports. A failed or unknown acknowledgement leaves that source's claim suspended for exact cancellation retry while unrelated inputs can drain. Successful abandonment releases the claim without publishing task completion; its discard tombstone prevents restart replay. An unattempted request that fails eligibility leaves dispatch authority unchanged.
|
|
459
459
|
|
|
460
|
-
Each pending chooser carries one routing phase: `waiting`, `selected`, `released` (a selection that routed nothing; All-command expiry re-arms), `forward-unknown`, `cleanup` (with its cleanup intent) or `finalizing` (with its pending confirmation). Only `waiting` with no command dispatch in flight counts as untouched, which is what Cancel, Restore sibling retention and cancellation review require. Thread selection reserves the source before awaiting Workspace admission or destination lookup, and releases the reservation after routing settles. Cancellation cannot win against this in-flight reservation or an already reported queue/completion outcome, even before its late journal settlement runs. Conversely, old destination buttons refuse a suspended or abandoned source before routing effects. Every unbound chooser whose sources can all be retained privately offers Cancel routing, whatever the content kind or group size. Admission publishes this capability only for a captured v1 pending original, copied before handler projections can mutate execution input; capability publication is not cancellation authority. The click binds the current paired owner, source sender, profile/journal, session signal, leader epoch and exact chooser target/message under profile-wide Workspace admission. Eligibility needs only an exact whole-source capability for every source of the chooser; content kind and group size do not matter. One tap retains each source once and keeps per-source receipts, so a retry finishes only the remainder; confirmed temporary tabs also permit command cancellation through their captured source. Unsupported journals, prior selection, partial forwarding, unknown issuance and cleanup-only states remain excluded. Ordinary source-only Cancel never deletes the original Telegram message or its
|
|
460
|
+
Each pending chooser carries one routing phase: `waiting`, `selected`, `released` (a selection that routed nothing; All-command expiry re-arms), `forward-unknown`, `cleanup` (with its cleanup intent) or `finalizing` (with its pending confirmation). Only `waiting` with no command dispatch in flight counts as untouched, which is what Cancel, Restore sibling retention and cancellation review require. Thread selection reserves the source before awaiting Workspace admission or destination lookup, and releases the reservation after routing settles. Cancellation cannot win against this in-flight reservation or an already reported queue/completion outcome, even before its late journal settlement runs. Conversely, old destination buttons refuse a suspended or abandoned source before routing effects. Every unbound chooser whose sources can all be retained privately offers Cancel routing, whatever the content kind or group size. Admission publishes this capability only for a captured v1 pending original, copied before handler projections can mutate execution input; capability publication is not cancellation authority. The click binds the current paired owner, source sender, profile/journal, session signal, leader epoch and exact chooser target/message under profile-wide Workspace admission. Eligibility needs only an exact whole-source capability for every source of the chooser; content kind and group size do not matter. One tap retains each source once and keeps per-source receipts, so a retry finishes only the remainder; confirmed temporary tabs also permit command cancellation through their captured source. Unsupported journals, prior selection, partial forwarding, unknown issuance and cleanup-only states remain excluded. Ordinary source-only Cancel never deletes the original Telegram message. For a Thread that is not a temporary tab, a completed Cancel (including its recovery review) removes the tab through the same `thread-reconciler` unbound-topic plan once no other chooser, queue, live owner, Workspace/Restore record or retained journal arrival (strict namespace census, exempting only its own cancelled group) still names it; missing census authority, protection or any failure keeps the tab, reports a diagnostic, never retries and never changes the Cancel outcome. Temporary-tab cancellation has the separate conditional all-resolved cleanup contract below. After the discard acknowledgement, the chooser becomes an HTML cancellation notice with an empty keyboard; an edit failure retains the committed receipt so retry updates the UI without repeating abandonment. Uncertain storage results retain only cancellation retry, not destination/restore authority. Owner-facing recovery after restart uses the Status surface below; historical orphaned inputs remain held internally, without a historical-review menu.
|
|
461
461
|
|
|
462
462
|
Before executing a pending v1 entry, the worker uses the journal's read-only `inspectPendingRetention` port to inspect only that entry's deterministic private retention path. An intact matching copy, or an unreadable/invalid candidate at that path, parks the source in an `abandoning` claim before routing. `abandoningClaimCount` exposes the protected subset of deferred claims. Other independently verifiable inputs continue draining; repeated wake-ups do not replay the parked source. Inspection validates bounded regular-file evidence and the full entry/binding/disposition match without repairing the archive, publishing a journal revision, or treating the requested disposition as committed. A fresh authorized caller may retry the exact journal abandonment through the worker; corrupt evidence stays protected and is never silently overwritten. The package-private `inspectTelegramAbandoningUpdates` carrier port prepares this handoff: it returns at most 20 update-id-ordered protected sources per page, detached original evidence, and exact-source retry closures. Observation reads only the worker's protected claims, not archive directories or journal files; it neither proves current eligibility/archive integrity nor authorizes dispatch. Page access and retries require the captured live worker generation, journal binding, transport/context and caller authority. Retry reuses the journal CAS, cannot be retargeted by editing display metadata, and caches an acknowledged receipt for presentation retries only while authority remains current. Ordinary deferred, queued and executing inputs are not candidates. Human-owner/source-kind filtering and the recovery control surface remain routing-owned.
|
|
463
463
|
|
|
@@ -473,13 +473,13 @@ Production leader admission composes routing's narrow historical classifier; fol
|
|
|
473
473
|
|
|
474
474
|
Acknowledged private-owner routing choosers, including commands and grouped prompts, own one fixed 60-minute lifetime from their first positive publication. `journal.routingInputs.arm` retains each exact v1 source's operator, `publishedAtMs`, `expiresAtMs` and `waiting` phase; refresh/restart never renews it. Destination validation precedes atomic `select` under Workspace admission. Selection still prevents another issuance, but does not extend the donor deadline: an unclaimed selected source with an unknown delivery outcome also expires. Queue admission carries the selected clock into the queued entry; queued/running recipient work is outside donor expiry. Unsupported carriers, unconfirmed clocks and historical originals without lifetime metadata acquire no invented deadline.
|
|
475
475
|
|
|
476
|
-
The operator-approved optimistic chooser policy accepts nondelivery after the fixed deadline. Expiry reacquires profile-wide Workspace admission and exact owner/context/leader/source authority, then CAS-removes the unclaimed donor pending source and writes a body-free legacy-compatible discard tombstone. It creates no private prompt archive, execution completion or recipient cancellation. Exact tombstone readback reconciles lost ACKs; changed/queued/owner-bearing sources refuse. The originating carrier loses authority, old controls are retired, and late reports cannot revive the input or block unrelated work. A lost selection ACK may reconcile only the waiting-to-selected phase of an otherwise identical source, never grant another send. Clock-bearing historical sources survive cold spending until the same deadline; positively unbound temporary frames associated with them survive new-world forgetting. The saved clock also records where its chooser was published. At startup the routing classifier returns `revive` only for a still-`waiting`, unexpired clock with a recorded chooser, from the current owner, whose tab is unbound while live threads exist; the worker re-checks that proof, drops the source from its startup set and handles it as this generation's live input, and routing edits the recorded chooser in place (or posts a fresh one if that message is gone) without renewing the deadline. Every other clock-bearing source keeps the protective hold. The worker retries only unconfirmed source disposition. A positive expiry ACK immediately drops its deferred source and claim, before observer/UI awaits. Failed cleanup metadata is retried once per minute by the existing one-timer-per-tab scheduler, reconstructing known groups from body-free journal evidence rather than retaining prompt bodies. Future metadata retries do not join settlement waits; only running attempts do. Same-instance session replacement may reprepare unissued expiry frames under fresh authority without resurrecting a source. Missing/unknown grant state or an issued destructive cleanup forbids retry.
|
|
476
|
+
The operator-approved optimistic chooser policy accepts nondelivery after the fixed deadline. Expiry reacquires profile-wide Workspace admission and exact owner/context/leader/source authority, then CAS-removes the unclaimed donor pending source and writes a body-free legacy-compatible discard tombstone. It creates no private prompt archive, execution completion or recipient cancellation. Exact tombstone readback reconciles lost ACKs; changed/queued/owner-bearing sources refuse. The originating carrier loses authority, old controls are retired, and late reports cannot revive the input or block unrelated work. Expiry edits the saved journal chooser even after cold reconstruction. If that view is unavailable, or its temporary tab is about to be removed, one best-effort notice goes to this leader instance's current owned Thread in the same private chat, without replying to a possibly deleted original. A proven unissued input says it was not delivered; selected/issued or legacy-uncertain work says delivery is unconfirmed and previously accepted work may continue. Notification has independent profile/journal/session/leader/target fences after donor revocation, never writes to All and never authorizes source replay or deletion. Missing owned destinations and failed delivery remain diagnostics. A bounded body-free warm view cache suppresses duplicate album notices. A lost selection ACK may reconcile only the waiting-to-selected phase of an otherwise identical source, never grant another send. Clock-bearing historical sources survive cold spending until the same deadline; positively unbound temporary frames associated with them survive new-world forgetting. The saved clock also records where its chooser was published. At startup the routing classifier returns `revive` only for a still-`waiting`, unexpired clock with a recorded chooser, from the current owner, whose tab is unbound while live threads exist; the worker re-checks that proof, drops the source from its startup set and handles it as this generation's live input, and routing edits the recorded chooser in place (or posts a fresh one if that message is gone) without renewing the deadline. Every other clock-bearing source keeps the protective hold. The worker retries only unconfirmed source disposition. A positive expiry ACK immediately drops its deferred source and claim, before observer/UI awaits. Failed cleanup metadata is retried once per minute by the existing one-timer-per-tab scheduler, reconstructing known groups from body-free journal evidence rather than retaining prompt bodies. Future metadata retries do not join settlement waits; only running attempts do. Same-instance session replacement may reprepare unissued expiry frames under fresh authority without resurrecting a source. Missing/unknown grant state or an issued destructive cleanup forbids retry.
|
|
477
477
|
|
|
478
478
|
With confirmed Threaded Mode, live bindings or a retained temporary entry, a known command or a fresh private plain-text prompt from All first enters the shared source-bound temporary-Thread path under current owner/profile/journal/context/leader authority and profile-wide Workspace admission. Threads atomically reserves a source/token intent before one `createForumTopic` attempt and records only its exact acknowledged target. The tab owns no Pi binding or A–Z slot; its presentation target is separate from the original All source. A creation error, missing target or lost publication result leaves unknown custody held, never a second creation attempt. A `created` entry can republish its chooser while its source is still pending after restart; a `creating` entry supplies no creation or dispatch grant. The acknowledged tab offers the shared Forward/Restore chooser and Cancel only when the current source has fresh private-abandonment capability. Publication defers rather than completes the original command; selection retains command semantics, and no current Pi is chosen automatically. Commands already completed by the compatibility path are not reconstructed. Unpresented expired commands keep the existing command-expiry fence; a confirmed retained tab is not evidence of expiry. Fresh threadless plain-text prompts use `New chat` with the same source-bound routing menu; unavailable creation authority records failure and retains the source without All guidance or unguarded dispatch. Recognized commands include built-in handlers (not only bot-menu entries), registered Telegram extension commands regardless of menu visibility, and discovered prompt-template commands. Bound-Thread and classic routing are unchanged. Local candidate composition is default with the required stores and owned epoch; there is no temporary-tab feature flag. Actual Pi/Telegram behavior was accepted in the operator's 0.52.0 live smoke; no owner-visible uncertainty recovery is scheduled.
|
|
479
479
|
|
|
480
480
|
#### Temporary Thread Multi-Input Lifecycle (Approved Design)
|
|
481
481
|
|
|
482
|
-
Every temporary routing tab carries one fixed routing name ([copy](./ui-style.md#route-chooser)), never its first input, because later prompts may join it: the bot creates its own tabs with that name and renames a native tab once, best-effort, when it adopts it. Commands and prompts share the same mode chooser. A command delivered with a Telegram-provided unbound private target uses the same chooser and fresh exact-source Cancel capability in that tab, without renaming it or creating another one. Mobile clients sending from `All` first create an implicitly named native tab and then deliver the input without a thread ID, although the client shows it inside that tab: an `All` input whose text starts with the name of such an observation (mobile names the tab after the input), or whose update immediately follows it (generic names such as desktop's `New Chat`), from the same exact scope and created at most ten seconds earlier, registers that native tab as its temporary tab through `registerImplicitTemporaryThread` instead of creating a second one, and renames it once to the routing name. A received target or successful rename alone never manufactures temporary authority. A fresh owner-authenticated private `forum_topic_created` with literal `is_name_implicit: true` supplies native creation evidence: routing retains a bounded process-local observation tied to the exact context, session generation, admission scope, operator, journal binding and executor. The next live input group in that exact target, of any content kind and either one message or an album, registers it through Threads `registerImplicitTemporaryThread` under profile admission, with that group as the entry's first input (its smallest update ID is the source), without calling or fabricating `createForumTopic` (adoption renames it once, best-effort); the publication CAS refuses any binding, owner record, reservation, provisioning, cleanup or Restore conflict. Normal grouped membership, one-shot Forward, source disposition, last-Cancel quiet period and cleanup issuance then apply identically. Historical creation signals, context/epoch changes, manual names, string flags, foreign creators and missing observations grant nothing. The observation is consumed on registration, never reconstructed from a title or restart. Existing bindings/reservations and acknowledged temporary targets retain their protections. Their root chooser offers Reroute, Restore and eligible Cancel; each mode opens its own submenu of live thread targets without dispatching the held input. A submenu Back returns to the mode chooser with its exact original description, retained on that pending chooser; navigation never substitutes or recomputes the root copy. Copy, emoji and button layout live in [UI Style](./ui-style.md#route-chooser). Cancel appears only at the root. After cancellation, the immediate cleanup remains proof-gated. Private-chat temporary tabs skip the unsupported `closeForumTopic` call and issue only one `deleteForumTopic` attempt; literal
|
|
482
|
+
Every temporary routing tab carries one fixed routing name ([copy](./ui-style.md#route-chooser)), never its first input, because later prompts may join it: the bot creates its own tabs with that name and renames a native tab once, best-effort, when it adopts it. Commands and prompts share the same mode chooser. A command delivered with a Telegram-provided unbound private target uses the same chooser and fresh exact-source Cancel capability in that tab, without renaming it or creating another one. Mobile clients sending from `All` first create an implicitly named native tab and then deliver the input without a thread ID, although the client shows it inside that tab: an `All` input whose text starts with the name of such an observation (mobile names the tab after the input), or whose update immediately follows it (generic names such as desktop's `New Chat`), from the same exact scope and created at most ten seconds earlier, registers that native tab as its temporary tab through `registerImplicitTemporaryThread` instead of creating a second one, and renames it once to the routing name. A received target or successful rename alone never manufactures temporary authority. A fresh owner-authenticated private `forum_topic_created` with literal `is_name_implicit: true` supplies native creation evidence: routing retains a bounded process-local observation tied to the exact context, session generation, admission scope, operator, journal binding and executor. The next live input group in that exact target, of any content kind and either one message or an album, registers it through Threads `registerImplicitTemporaryThread` under profile admission, with that group as the entry's first input (its smallest update ID is the source), without calling or fabricating `createForumTopic` (adoption renames it once, best-effort); the publication CAS refuses any binding, owner record, reservation, provisioning, cleanup or Restore conflict. Normal grouped membership, one-shot Forward, source disposition, last-Cancel quiet period and cleanup issuance then apply identically. Historical creation signals, context/epoch changes, manual names, string flags, foreign creators and missing observations grant nothing. The observation is consumed on registration, never reconstructed from a title or restart. Existing bindings/reservations and acknowledged temporary targets retain their protections. Their root chooser offers Reroute, Restore and eligible Cancel; each mode opens its own submenu of live thread targets without dispatching the held input. A submenu Back returns to the mode chooser with its exact original description, retained on that pending chooser; navigation never substitutes or recomputes the root copy. Copy, emoji and button layout live in [UI Style](./ui-style.md#route-chooser). Cancel appears only at the root. After cancellation, the immediate cleanup remains proof-gated. Private-chat temporary tabs skip the unsupported `closeForumTopic` call and issue only one `deleteForumTopic` attempt; only literal `true` or the client's parsed exact-target `deleteForumTopic` HTTP 400 response confirming deletion/absence closes the issued temporary grant. Method, HTTP/error code, missing-target vocabulary and request target must all match; closed, denied, foreign, malformed or forged errors remain uncertain and never authorize a retry. Every kind of input sent from All takes this path: a lone text message may carry a command, while a voice note, file or sticker is held untouched as a prompt, and an album first gathers through the shared pending-group machine into one input whose whole update group the tab records as its creating input (`reserveTemporaryThread` with a group starting at the source update). All is input-only: no assistant answer, chooser, fallback guidance, draft or activity is emitted there. A failed routing-tab creation retains the source, including a coalesced album, rather than silently settling it. A stale Thread does not prove Classic Mode. The transport/API fence rejects private output without a valid top-level Thread, including retained aggregate markers; Classic DM and explicit channel/business namespaces remain separate. No historical All-output cleanup is scheduled. The Bot API files every threadless All input in All, a desktop menu-picked command as well as a mobile input its client first draws inside its new native tab. So a tab opened for such an input (bot-created or adopted, never on restart re-entry) first receives one silent best-effort `forwardMessage` of it, its chooser replies to that copy, and only a confirmed copy lets the bot delete the All original at once: the input moves into its tab on every client. That opening forward has one unretried, generation-fenced API attempt. Only a complete set of distinct positive safe-integer copy IDs acknowledged under current authority permits removal of the All originals. The live chooser retains the exact target and full source/copy ID mapping as display provenance, not input acceptance or cleanup authority; this warm mapping is not a cold-recovery grant and never rewrites journal originals. Temporary Reroute resolves its forwarding addresses from fresh worker-owned original snapshots before selecting the destination: exact source binding/update, paired sender, bot-chat message namespace and positive unique original IDs must all match. Complete warm copies take precedence; original All IDs may be reused only when this live opening attempt is known not to have removed them. A revived chooser cannot reconstruct that fact from journal IDs or its saved location. Unknown cold All copies, unavailable readers, partial group proof and synthetic zero IDs refuse Reroute before native issuance, leaving the source waiting with its exact Cancel capability; actual native-Thread originals may still be recovered. Warm target projection is not proof of an original message location. Restore retains its separate held-input path. Ended authority stops subsequent original-removal delegation and chooser publication; already issued requests cannot be unsent. A failed forward keeps the original in All; it is then deleted best-effort when the input is consumed by Forward, Cancel or expiry, which remove the tab too, but never by Restore, which keeps the tab as the Thread and must not leave it without its input. Failure is recorded and never retried. Pending-cancellation recovery deletes nothing, and typed All inputs already live in their own tab and leave with it.
|
|
483
483
|
|
|
484
484
|
This multi-input lifecycle is implemented in the local candidate, not authority for live deletion. It supersedes unconditional per-chooser tab removal. The reject-only guard prevents cleanup/retirement while another known same-tab chooser remains, including unresolved cleanup-only work. `temporaryThreads.inputs` now records bounded append-only journal source groups through the unbound producer before chooser publication; duplicates are read-only, partial/foreign/overlapping groups refuse, and faults retain the source without a new chooser. Cold reads preserve this protection without process-local chooser memory. A group has no readiness or disposition flag, and an absent legacy field does not prove complete coverage. `cancelledInputs` records exact whole known donor groups after either same-journal/operator retained-cancellation evidence or the separate body-free expiry tombstone. Expiry recording may clear a group's unknown Forward issuance or its expired Restore intent, but never mutates a canonical binding, recipient queue or immutable scoped settlement proof. If Restore already bound the tab, expiry releases the temporary frame without deletion. Otherwise fully resolved groups enter the existing one-second quiet-period cleanup. A partially acknowledged known cohort may finish donor disposition when every member is absent from that exact source journal and at least one member has same-operator expiry proof; this is not recipient completion or non-delivery proof. Fresh strict namespace/protection evidence still gates deletion; independent work and bound Threads remain protected. Store publication and returned/retained acknowledgement recheck that proof and operator/executor authority; a lost reply reconciles the exact fact read-only, while missing/foreign/corrupt proof stays protective. The producer composes this into explicit Cancel before temporary cleanup or chooser retirement. Cancelled groups cannot Restore; independent sources remain untouched. More than one known group still blocks cleanup and raw retirement, including all-cancelled membership: these facts grant no deletion. Each successful Cancel retires only its own chooser. If durable membership is fully resolved by exact cancellation/completion facts and no chooser remains, a process-local cleanup is queued with no grace delay (it still waits behind the resolving operation's Workspace admission, which Cancel releases right after its notice, answering the toast and deleting any All copy alongside the cleanup; new input in the tab before it runs cancels it). Its single attempt, under profile admission, rechecks authority, whole-group cancellation/expiry evidence, exact entry CAS, a strict per-Thread journal census (every namespace source must read strictly; any retained update naming the Thread in any state, any of its own groups, and any custody-bearing, failed or non-plain entry anywhere still protect; only an unrelated waiting plain message, edit, callback or reaction elsewhere is exempt, so a chooser held in another tab cannot pin every temporary tab, and so is a plain button tap in this tab, since it carries no input and the in-flight Cancel itself may outlast the quiet period), absence of choosers and the normal reconciler protections. `issueTemporaryThreadCleanup` publishes canonical `cleanupIssued: true` only for an exact fully resolved entry with strict unbound canonical evidence checked inside the publication transaction; the normal target/source guards precede issuance and run again at API boundaries. The reconciler requires that exact retained issued frame and limits its plan to this tab; unrelated expired-provision cleanup never borrows the grant. Close/delete calls disable retries and transport fallback and require a positive acknowledgement. Confirmed current deletion alone permits entry retirement; skip, negative/lost reply, authority loss or post-rename publication interruption retains the marker. Cold reads classify already-issued entries as unknown, new membership refuses, and executor adoption never renews the grant. Missing evidence only refuses; an unpublished grant issues no API call and may still receive its first grant. The quiet period remains process-local, but issued uncertainty survives restart. The census cannot see updates in transit before journal append. Restore from a multi-input tab is allowed only when each other known group is durably cancelled, Forward-completed or a live unselected chooser that supports private abandonment. A previously completed Forward never supplies a new selection or cancellation grant. After positive settlement of every selected source, such siblings are abandoned with cancellation facts and their controls edited to cancelled, then `retireTemporaryThread(expected, authority, completed)` releases the entry (one newly completed selected group, all others durably cancelled or previously Forward-completed). Missing authority/proof or an unproven Restore leaves siblings pending and protected. On startup, a retained journal input recorded in a created temporary tab whose target a Workspace binding now owns is held as historical, never rerouted; an exact saved chooser clock may expire its unclaimed donor attempt without affecting the binding. Live pending sources and due retry-wait sources (including startup retries) use the same fresh membership/binding predicate through `shouldHoldPendingInput` before execution, with source/context/binding/transport checks after classification awaits. The hold grants neither cancellation nor delivery and does not block unrelated inputs. Recipient custody never uses this direct-source classifier. A native production witness admits a recorded command after the worker has already processed an unrelated command, proving it cannot fall through to ordinary bound dispatch. A production retry witness keeps recorded failure metadata intact without command execution, cancellation or deletion. Claimed retries cannot schedule repeated due-time wakes; independent due and future retries remain executable. Real-IPC Forward/Restore sibling witnesses now include recipient journal drain through the real worker and follower router: only the selected message executes, source disposal is observed, and repeated controls do not replay the command. Existing message-ownership composition keeps a locally published chooser's callbacks at its leader publisher after follower rebinding; the native fixture now records publication ownership rather than relying only on target ownership. Four native delivery-reply-loss witnesses cover both routing actions (Forward/Restore), before recipient execution and after worker disposal: the leader source remains unresolved without ACK, independent sibling Cancel preserves the selected group/tab, and a late reply or repeated issued Restore click cannot settle/replay it. Explicit Forward retry after a lost ACK reproduced command duplication when the recipient worker had already disposed its journal record; stable delivery identity alone was not terminal deduplication. The follower's bounded process-local delivery window now acknowledges such a retry without re-execution while it retains that delivery. Ordinary Forward from a temporary tab first publishes a durable per-group `forwardedInputs` issuance fact in the canonical Threads owner under current authority, before either local command handling/prompt preparation/queue admission or follower RPC. Only positively unpublished issuance may receive its first grant; a published fact (even if its reply is ambiguous) is never retried, including after reload, restart or leader replacement. Local repeated selection refuses the retained grant before handler or queue re-entry. Captured All-tab provenance and acknowledged typed-input membership remain protective if the metadata reader disappears; unavailable evidence never falls back to unguarded generic dispatch. Restore retains its separate canonical routing grant. Native local fixtures interrupt source completion before/after commit, prompt preparation and issuance publication/authority; issued sources stay nonterminal, independent siblings remain cancellable and cold restart never manufactures completion or another attempt. A process-local `forward-unknown` chooser phase gates the live chooser. An issued group is refused by repeat Forward, explicit Cancel and Restore (the store also rejects them); before its fixed chooser deadline it keeps sibling accounting protective, while body-free donor expiry or positive `completedInputs` may resolve it; independent siblings remain operable and a restarted chooser cannot resend. Issuance is not proof of delivery, and recipient-side deduplication is limited to the bounded process-local delivery window; the chooser deadline supplies a loss-accepting exit for unclaimed unknown issued groups; generic journal forwarding and non-chooser retry policy are unchanged. Canonical Restore keeps its existing durable grants. Mixed native witnesses prove that a completed Forward allows Restore of another group without replay/recancellation, while an unknown Forward prevents sibling Restore before an intent is created. Prompt Restore leaves a command sibling pending at queue admission, then privately cancels it only after exact scoped worker receipt disposition. Independently Forwarded queued input is neither unassigned nor cancellation-capable: either receipt may dispose first, the other retains custody, and only both positive dispositions release temporary membership while keeping the bound tab/slot. An ordinary queued ACK emits the existing source-completion hint only after whole acknowledged removal and fresh context, owner, generation and originating journal-binding checks; it creates no immutable Restore marker. A published independent Forward group fact wakes pure inspection of already settled Restore only after its Workspace admission releases. The store refuses both Forward facts and cancellation for any source overlapping a retained Restore grant, regardless of target; malformed partial selected groups stay protective. Native ordinary prompt Forward plus sibling Cancel also delays deletion until receipt disposition and fresh all-resolved cleanup. Pi handoff is supplied by the fixture, not observed model/task completion. The retained All-command fixture registers 84 scenarios across creation, Forward, Cancel, cleanup/membership and Restore, including full slots, publication faults, sibling protection, native Unix IPC and recipient worker drain through the real non-reentrant Workspace gate. Full-slot follower cases hold a real Unix response before apply, after apply, and after read-only inspection until client timeout. Fresh same-session registration/context generations reconcile only through inspection; late replies never publish readiness, repeat apply or dispatch, cancel siblings, release independent queued recipient work or delete the tab. Successful continuation retains exact scoped source-disposition proof. A separate cold-successor witness loads fresh stores and a journal binding after an issued command lost acceptance authority, then starts a fresh context/generation/epoch under supplied same-session owner publication. Original command and sibling stay pending, spent routing/canonical facts remain unchanged, and stale Restore/Forward/Cancel controls cannot replay or dispose them. No chooser is reconstructed: the protective hold is proven, not a usable cold recovery surface. Pi effects and startup registration remain supplied adapters/preconditions; actual Pi startup and live behavior were accepted in the operator's 0.52.0 live smoke. Forward completes only its own input: no Forward removes the tab, the worker's completion ACK records a durable `completedInputs` group (process-local accumulation across the group's sources), and when every known group is cancelled or completed the delayed, fully rechecked cleanup removes the tab once. Cancelled and completed groups are disjoint, cannot Restore, and a completed Forward queued prompt holds the tab until its receipt completes. The tab chooser discloses removal only when no other input remains; a failed removal no longer edits the original chooser. Source-only prompt Cancel remains unchanged. The candidate's temporary-tab chooser is the default under the owned leader epoch, not a configurable feature flag. Upgrade all prospective canonical readers/writers before live activation; the metadata (including `forwardProtocol`, `forwardedInputs` and `cleanupIssued`) is not downgrade-safe through older snapshot writers, which could drop issuance facts.
|
|
485
485
|
|
|
@@ -503,7 +503,7 @@ A Pi process restart starts a new routing world. Unfinished routing from the pre
|
|
|
503
503
|
- `Spent prompts`: Pending prompts that Pi never accepted into its queue — unselected, chooser-held, temporary-tab or Restore-held, including an issued but unconfirmed Forward — are spent: removed from pending custody without Pi delivery, a private retained copy, a notification or task completion. The polling cursor already prevents Telegram redelivery; spending is disposition, never replay. Implemented: the leader worker's `spendHistoricalInput` removes startup-census pending sources that carry a routing input or that the domain classifier marks boolean true, under current authority, through ordinary removal without completion observers. A distinct retain-only verdict takes precedence over an old routing clock and spending; unsupported protected originals remain unchanged. Retry-wait sources, interrupted private abandonments, queued receipts and live arrivals keep their existing holds; unknown classification spends nothing. Also implemented: the leader's startup hint, fenced by the owned polling generation, calls routing `forgetPreviousWorld`. Under profile admission it atomically removes this operator's Restore intents and temporary entries whose executor belongs to another runtime instance (a reload is a new instance), then makes one non-idempotent `deleteForumTopic` attempt per previously classified disposable created tab still unreferenced by bindings, active records, temporary entries or Restore intents, and only after a fresh strict source census returns complete-empty. Retained, unreadable or unavailable source evidence blocks that deletion without reconstructing the forgotten intent.
|
|
504
504
|
- `Old temporary tabs`: An unbound tab with positive disposable provenance gets one silent deletion attempt. Failure or an unknown outcome leaves the tab without retry.
|
|
505
505
|
- `Unfinished Restore`: The intent is forgotten. Already committed canonical binding changes stay as they are, without rollback.
|
|
506
|
-
- `Old controls`: Choosers and buttons from the previous process answer with the shared expired-choice notice `TELEGRAM_ROUTING_CHOICE_EXPIRED
|
|
506
|
+
- `Old controls`: Choosers and buttons from the previous process answer with the shared expired-choice notice `TELEGRAM_ROUTING_CHOICE_EXPIRED`. A newly committed expiry uses the more specific undelivered/unconfirmed notice described above ([copy](./ui-style.md#route-chooser)).
|
|
507
507
|
- `Untouched`: Committed Workspace tabs and bindings, prompts already accepted into the Pi queue (exact receipts), follower custody, and in-process `/new` succession, which is not a restart.
|
|
508
508
|
|
|
509
509
|
The user recreates a temporary tab and restores again when needed. This superseded the earlier cold policy: working-tab attestation, held-source inspection/scope capabilities and strict per-group cold disposition/finalization were removed. The historical classifier, temporary-tab hold, one-shot startup hint and polling-generation fence remain as inputs to spending and forgetting.
|
|
@@ -731,7 +731,7 @@ The `generative-apps` Skill owns the general Generative Apps concept, applicatio
|
|
|
731
731
|
|
|
732
732
|
The bridge owns canonical `<agent-dir>/genapps/<app>/<app>.mjs` identity, installation/replacement, bounded runtime ports, state revision/timeline integrity, `app::method` routing before Pi queue admission, ordinary prompt routing through Pi, and Telegram delivery of returned Markdown/buttons. External capability ownership remains outside the bridge.
|
|
733
733
|
|
|
734
|
-
`telegram_bind` is the agent-facing lifecycle surface. Installation and explicit replacement invoke mandatory `init`; replacement stages and initializes a complete candidate before publishing it under the same app name. During an active Telegram turn, successful Tool output is planned and delivered directly to that exact target by default, and the Tool result suppresses model duplication; `display: false` retains agent-only diagnosis. Outside an active turn no implicit target is chosen. Buttons emitted as `app::method` or `app::method(<strict JSON>)` take the inference-bypass route.
|
|
734
|
+
`telegram_bind` is the agent-facing lifecycle surface. Installation and explicit replacement invoke mandatory `init`; replacement stages and initializes a complete candidate before publishing it under the same app name. During an active Telegram turn, successful Tool output is planned and delivered directly to that exact target by default, and the Tool result suppresses model duplication; `display: false` retains agent-only diagnosis. Outside an active turn no implicit target is chosen. Buttons emitted as `app::method` or `app::method(<strict JSON>)` take the inference-bypass route. For buttons carrying an app binding (emitted by that app's output), missing apps, malformed actions, stale revisions, invalid output, or method failure fail closed without becoming ordinary prompts. A button without a binding is ordinary model-authored text: a prompt that is malformed as a bound action, or names an app that is not installed, stays an ordinary prompt (`std::map`, `a::b(c)`) and never raises a Generative App error.
|
|
735
735
|
|
|
736
736
|
The concrete runtime and wire reference remains in [Generative Apps Runtime For Telegram](./generative-apps.md). The bundled `generative-apps` Skill owns the concept and agent workflow; `generated-control-surface` owns its separate ephemeral interface protocol; `telegram-bridge` owns transport, target authority, delivery, and general turn operation.
|
|
737
737
|
|
|
@@ -222,10 +222,10 @@ Production retirement requires a durable profile-scoped reader/writer ledger own
|
|
|
222
222
|
- 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.
|
|
223
223
|
- 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.
|
|
224
224
|
- 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.
|
|
225
|
-
- 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,
|
|
225
|
+
- 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, retirement is impossible and allocation falls back to the metadata-only letter reuse described under Demand-Driven Slot Rotation instead of refusing the connection; never delete a live owner's Thread or silently expand into `aa`.
|
|
226
226
|
- 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.
|
|
227
227
|
- 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.
|
|
228
|
-
- 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
|
|
228
|
+
- 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 deletes nothing and falls back to metadata-only letter reuse. 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.
|
|
229
229
|
- 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.
|
|
230
230
|
- 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.
|
|
231
231
|
|
|
@@ -235,6 +235,12 @@ A fresh leader allocation or authenticated explicit follower registration first
|
|
|
235
235
|
|
|
236
236
|
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.
|
|
237
237
|
|
|
238
|
+
#### Metadata-Only Letter Reuse
|
|
239
|
+
|
|
240
|
+
When pressure retirement finds no eligible binding (every letter protected or unverifiable), the same wrapper reassigns the least recently used letter rather than refusing the connection. Under the shared gate it reloads the store and, if still no letter is free, removes the `slot` from one binding and from its current Thread record, persists once, and retries the claim, which then takes the freed letter. The victim is the binding with the oldest last use: detached bindings by their first-inactivity time, live owners counted as most recent and chosen only when nothing detached remains, ties by letter. The metadata reassignment keeps the Thread, binding, journals, accepted work and generated name; it performs no deletion. Before retrying allocation, the leader awaits one display-only reconciliation of the displaced binding under fresh profile admission. A slotless binding displays its retained generated name in every automatic mode, or keeps its manual override. Routing remains the exact numeric target plus binding key, so old work can never reach the replacement through the recycled letter.
|
|
241
|
+
|
|
242
|
+
A binding or letter held by a claim, reservation, pending provision, cleanup, retirement intent or an external admission reservation is skipped, as is a letter that a different current Thread record also holds. An unreadable external reservation set, lost leader authority or refused publication restores the previous assignment and reports capacity unavailable. The displaced binding is slotless like a legacy binding: it holds no letter, is not a capacity-retirement candidate, and regains one only through a later allocation-authorized connection. Displaced bindings and their Threads therefore accumulate; nothing deletes them automatically. On the leader, Settings → Thread cleanup → Review inactive tabs can prepare exact inactive slotless bindings with complete clear protection. Back leaves them untouched; the separate Clean action confirms deletion of the reviewed Telegram tabs and their messages. The shared gate reloads and revalidates each exact candidate before and under its manual cleanup fence; changed/protected candidates are skipped, and an unknown issued deletion never repeats. The manual-cleanup path retains explicit `slot: null` through its work-set, fence, permit and exact binding commit; it never uses the letter now held by another binding. Older readers reject these new null-bearing records rather than inferring cleanup permission. Pressure retirement still requires a real letter. Restore-only startup and background heartbeat recovery never reuse a letter, which prevents eviction churn. A rejected or unknown title edit retains the previous acknowledged title and records a diagnostic; it cannot undo or repeat the published reassignment, block the fresh connection, or authorize deletion. Ordinary display reconciliation also includes slotless bindings without live owners, so startup or a later display trigger can repair the title after failure or a crash between reassignment and editing. Title edits retain exact binding/profile/mode/epoch fences and require a positive acknowledgement; this display authority never creates live routing ownership.
|
|
243
|
+
|
|
238
244
|
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.
|
|
239
245
|
|
|
240
246
|
### Ambiguous Retirement Recovery Evidence
|
|
@@ -667,12 +673,26 @@ This inspection authorizes no reload/restart, live-state mutation, fault injecti
|
|
|
667
673
|
## Security Boundaries
|
|
668
674
|
|
|
669
675
|
- Messages, edits, callbacks, and reactions check user authorization, not only chat/thread membership.
|
|
670
|
-
- Followers authenticate to the local leader IPC with a leader-minted capability secret carried in the active lock entry; registration, heartbeat, forwarded updates, and follower API calls without the secret are rejected. Registration rejections are surfaced verbatim in the follower `/telegram-connect` result, registration waits through leader-side Telegram thread provisioning, and successful registrations send an immediate heartbeat before the interval ticker so the leader does not prune a live follower before its first scheduled heartbeat. The local bus socket is also created under a private `0700` directory with `0600` socket permissions as a first local-only boundary.
|
|
676
|
+
- Followers authenticate to the local leader IPC with a leader-minted capability secret carried in the active lock entry; registration, heartbeat, forwarded updates, and follower API calls without the secret are rejected. Registration rejections are surfaced verbatim in the follower `/telegram-connect` result, registration waits through leader-side Telegram thread provisioning, and successful registrations send an immediate heartbeat before the interval ticker so the leader does not prune a live follower before its first scheduled heartbeat. The local bus socket is also created under a private `0700` directory with `0600` socket permissions as a first local-only boundary. Follower API authorization refusal surfaces as `TelegramApiFollowerNotAllowedError` with the requested target when available. Only the leader's structured `follower-api-not-allowed` negative acknowledgement, emitted before API delegation and matched to the requested method, populates `rejectedRequestMethod`. Legacy error text, identical text thrown after delegation, a mismatched method or a lost acknowledgement supplies no such proof; old peers continue to receive the same human-readable rejection. The structured refusal proves only that the rejected API request was not executed, not that the command had no earlier effects. Admission therefore never immediately re-enters the handler or retargets its source to recover a reply. The original input remains in durable `retry-wait`; ordinary retries retain their documented at-least-once semantics. Safe reply-request recovery for re-created recipient Threads remains open in the backlog.
|
|
671
677
|
- Follower Bot API proxying is allowlisted and target-scoped where applicable. Ordinary sends remain confined to the follower's assigned thread; trusted runtime-marked `telegram_message` cross-target sends may address only a different thread inside the same paired chat, and the leader strips the internal marker before calling Telegram. This preserves requested inter-thread delivery without granting arbitrary bot or cross-chat control.
|
|
672
678
|
- Button and section callbacks verify authorized `from.id` and owning target/instance.
|
|
673
679
|
- Generated artifacts stay scoped to the owning thread after leader failover.
|
|
674
680
|
- Diagnostics redact bot tokens, large prompts, attachment paths, and handler output.
|
|
675
681
|
|
|
682
|
+
### Reply Recovery Ownership Boundary
|
|
683
|
+
|
|
684
|
+
A destination belongs to the complete delivery operation, not just the outgoing Bot API body. `replies.sendTelegramChunkSequence` captures one target for all chunks and records message ownership against it. Menu state independently retains `chatId`, `threadId` and the returned message ID through `menu-model.createTelegramMenuDelivery`. Its numeric send result cannot report a relocated Thread to the menu owner. Rewriting only a refused send's `message_thread_id` would therefore separate actual delivery from ownership and callback state; moving only later chunks would also split one reply across tabs.
|
|
685
|
+
|
|
686
|
+
Follower API arguments use one private JSON-wire snapshot made before registration waits. Issued method/body, refusal target/method and slow-call diagnostics derive from that same snapshot; later caller mutations cannot relabel the attempt. Caller serialization runs once, with local authority checked before capture and again before issuance.
|
|
687
|
+
|
|
688
|
+
Admission and recipient identity are separate. The local update execution fence carries its admission generation, update ID and abort signal; the queue-owner snapshot carries instance, process/birth and session-generation identity. Neither contains a complete recipient/session/profile/registration/target grant. Receipt and Workspace-admission namespaces are not the follower's registered profile key: selected-menu composition supplies that registered key separately, while the leader independently captures its active Workspace scope. No ordinary recipient-scoped reply-delivery lease joins these views today.
|
|
689
|
+
|
|
690
|
+
Pre-publication destination selection is separate from refused-send recovery. For an executing private-DM follower journal, the status/model/settings and text-reply target adapters select the acknowledged recipient target while the active profile/session journal binding still matches the worker. The execution fence checks admission currency; it does not grant delivery. The selected target is passed to the menu owner before state construction and to the existing reply sender before chunking, with the old reply anchor removed on a Thread change. The original input and its journal are not rewritten. A different chat, a Thread-to-All fallback or missing recipient binding refuses selection; explicit selected-menu authority keeps its original fixed destination. The ordinary leader-side API authorization remains unchanged. This prevents a stale source address from choosing the initial reply destination; it neither relocates an issued request nor supplies the missing reply-recovery lease.
|
|
691
|
+
|
|
692
|
+
Structured refusal is evidence about one API attempt, not a delivery or command recovery grant. Current delivery propagates that refusal without a replacement send, without editing the target or reply anchor, and without replaying acknowledged chunks. Already acknowledged messages keep their original ownership; the refused message acquires none. This applies to interactive HTML menus, split native Markdown and single Rich Message sections.
|
|
693
|
+
|
|
694
|
+
Guarded follower delivery must use a recipient-scoped transport that enforces authority at the leader, not serialize a local callback through the ordinary proxy. The existing selected-menu channel is bound to an exact live-rebind operation, source cohort and recipient; a raw journaled command with no matching retained operation cannot borrow that grant. Any future reply-only recovery must establish its own exact authority, keep the destination consistent in delivery and menu ownership, clear stale reply metadata, and refuse relocation after partial publication or an unknown outcome. Handler-replay containment stays enabled until that boundary is proven; this is not an automatic recovery feature.
|
|
695
|
+
|
|
676
696
|
## Acceptance Criteria
|
|
677
697
|
|
|
678
698
|
- [x] The lock semantics are redesigned as Telegram bus leadership with liveness proof and stale takeover rules.
|
|
@@ -125,6 +125,8 @@ The file is global across Pi instances and contains configuration only. The per-
|
|
|
125
125
|
|
|
126
126
|
Threaded Mode Settings exposes **Thread display** as Letters (default), Names, `directory-snake`, or `directory-title`; retained `directories` is unsupported, resolves to Letters, and is never rewritten automatically. `threads.displayMode` is shared by every profile; absent and invalid values resolve to `letters`. The retired `profiles.<name>.threadDisplayMode` key is ignored, not migrated. Another profile's already-running leader applies a shared change at its next startup or display reconciliation. Settings previews the current binding set through the same projector used for fresh titles and reconciliation. A manual `/name` override appears as `custom`; choosing any automatic Thread display option clears that override for the current Thread and immediately reapplies the selected projection. Names projects the generated dictionary name for the slot; the two directory modes tokenize Unicode path segments deterministically and render snake case or humanized title case, with `_a` or ` A` slot suffixes when required. The leader serializes preference persistence and title reconciliation, while a follower sends an authenticated `follower.setThreadDisplayMode` request gated by `thread-display-mode-v1`, the additional `directory-display-format-v1` capability for either new directory mode, and its exact registration generation. Config writes check the originating authority inside the config transaction; mode changes preserve target IDs, slots, generated recovery names, manual overrides, and queue ownership. `/name` mutations carry their originating target through final binding validation. The caller confirms only after application succeeds. A partial failure may leave the preference saved and some titles updated; Settings reports that state and permits retry. Acknowledged follower titles arrive through heartbeat rather than a new read loop.
|
|
127
127
|
|
|
128
|
+
Manual cleanup is independent of the graceful-quit toggle. On the leader instance, Settings → Thread cleanup → Review inactive tabs prepares only exact inactive bindings with complete clear protection, including bindings without a letter. Review and Back delete nothing. The separate Clean action confirms deletion of those eligible Telegram tabs and all their messages, not Pi session history or project files. Confirmation reloads and revalidates the exact review under a one-shot manual fence. Changed or protected bindings are skipped; an issued unknown deletion is never retried automatically, and unfinished local commit retains recovery protection. Follower instances do not receive a remote cleanup grant from these controls.
|
|
129
|
+
|
|
128
130
|
Hidden/default semantics are represented by absence:
|
|
129
131
|
|
|
130
132
|
- `threads.automaticCleanup` defaults to `true`; graceful Pi quit deletes the instance's bound Threaded Mode tab without prompting but preserves the owner slot as independent restart intent. Set it to `false`, or use `🧹 Thread cleanup` in Telegram Settings, to preserve the tab too. A confirmed `/telegram-disconnect`, unlike quit, clears restart ownership. Settings views and cleanup reload shared config before evaluating this switch, so another live Pi instance's update takes effect without restarting. Confirmed leader/follower teardown persists an exact target/runtime-generation cleanup intent before Telegram deletion; an interrupted attempt remains retryable by the current or successor leader under current authority and clears only after confirmed deletion. A same-profile replacement leader first adopts any still-active binding and cancels its superseded cleanup, so startup never deletes and recreates a reusable thread. If a follower's graceful envelope is missed, the leader may create the same fenced cleanup only after its heartbeat is stale, the OS confirms the exact registered PID no longer exists, cleanup remains enabled, and no replacement registration can overtake deletion. Heartbeat loss alone, live/unknown process liveness, IPC failure, and auth failure remain non-destructive. Invalid-config recovery makes the setting unresolved and therefore skips destructive cleanup. Manual `/telegram-disconnect` keeps its confirmation and teardown behavior regardless of this setting.
|
|
@@ -240,7 +240,7 @@ A message (or All-tab command) held in an unbound or temporary tab gets one rout
|
|
|
240
240
|
- Cancel appears only at the root.
|
|
241
241
|
- An input sent from `All` moves into the routing tab opened for it: the tab starts with a silent forward of the input (Telegram shows it as forwarded from the operator), the chooser replies to it, and the original leaves `All` once the copy is confirmed. A restored Thread therefore keeps the prompt it answers.
|
|
242
242
|
- A chooser is one-shot: every final outcome deletes its message. A confirmed Cancel, a Forward or Reroute, and a successful Restore remove the menu; Restore keeps the tab as the Pi's Thread, while a temporary tab with no remaining unresolved input is removed. If Telegram refuses the delete, Cancel and Restore leave an inert bold notice (`⛔️ Routing cancelled.` / `✅ Message routed.`) with an empty keyboard, and Forward asks for one more tap to clear the menu.
|
|
243
|
-
- Old or expired controls answer `⌛ Routing choice expired.`
|
|
243
|
+
- Old or expired controls answer `⌛ Routing choice expired.` A newly committed expiry instead displays the fully bold `⌛ Routing expired; input was not delivered.` only for proven unissued work. Selected/issued or uncertain work displays fully bold `⌛ Routing expired; delivery is unconfirmed.` followed by `Previously accepted work may continue.` If the chooser is gone, or its temporary tab will be removed, send that notice without an old reply anchor to the leader's current owned Thread in the same private chat; never fall back to All. No owned Thread means diagnostics only.
|
|
244
244
|
- Tab name: every temporary routing tab is named `🚦 Routing`, whether the bot created it (menu command or threadless prompt) or adopted the `New Chat` tab Telegram opened for a typed input. It is never renamed after its first input.
|
|
245
245
|
- Status → `❌ Pending cancellations` lists one row per protected original: its text, or `📎` plus the caption, file name or the message field carrying Telegram's `file_id`.
|
|
246
246
|
|
|
@@ -453,7 +453,30 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker<
|
|
|
453
453
|
| GenerativeApps.GenerativeAppLiveSurface<GenerativeApps.TelegramBindLiveHandle>
|
|
454
454
|
| undefined;
|
|
455
455
|
try {
|
|
456
|
-
|
|
456
|
+
// Only buttons emitted by a Generative App carry a binding. An unbound button is ordinary model text
|
|
457
|
+
// (`std::map`, `a::b(c)`): malformed or uninstalled-app prompts fall through to the normal prompt plane.
|
|
458
|
+
if (!action.binding) {
|
|
459
|
+
try {
|
|
460
|
+
boundAction = GenerativeApps.parseGenerativeAppBoundAction(
|
|
461
|
+
action.prompt,
|
|
462
|
+
);
|
|
463
|
+
} catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (
|
|
467
|
+
boundAction &&
|
|
468
|
+
!(await GenerativeApps.isGenerativeAppInstalled(
|
|
469
|
+
deps.agentDir,
|
|
470
|
+
boundAction.app,
|
|
471
|
+
))
|
|
472
|
+
) {
|
|
473
|
+
boundAction = undefined;
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
boundAction = GenerativeApps.parseGenerativeAppBoundAction(
|
|
477
|
+
action.prompt,
|
|
478
|
+
);
|
|
479
|
+
}
|
|
457
480
|
if (!boundAction) return false;
|
|
458
481
|
deps.assertExecutionCurrent(query);
|
|
459
482
|
const chatId = query.message?.chat?.id;
|