@llblab/pi-telegram 0.53.2 → 0.54.1

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/dist/lib/locks.js CHANGED
@@ -959,6 +959,10 @@ function hasSameLockOwner(current, expected) {
959
959
  current.leaderEpoch === expected.leaderEpoch &&
960
960
  current.runtimeGeneration === expected.runtimeGeneration);
961
961
  }
962
+ /** Exact owner identity (pid, cwd, instance, leader epoch, runtime generation); absent entries never match. */
963
+ export function isSameTelegramLockOwner(current, expected) {
964
+ return hasSameLockOwner(current, expected);
965
+ }
962
966
  function canSupersedeSameProcessOwner(current, pid, ctx, instanceId, runtimeGeneration) {
963
967
  if (current.pid !== pid ||
964
968
  (current.cwd !== undefined && current.cwd !== ctx.cwd) ||
@@ -973,9 +977,7 @@ function createLockEntry(pid, ctx, options) {
973
977
  if (options.journalPath)
974
978
  lock.journalPath = options.journalPath;
975
979
  if (options.instanceId) {
976
- const nowMs = options.getNowMs?.();
977
980
  lock.instanceId = options.instanceId;
978
- lock.heartbeatMs = nowMs;
979
981
  lock.leaderEpoch = options.mintLeaderEpoch?.() ?? randomUUID();
980
982
  lock.runtimeGeneration = options.runtimeGeneration;
981
983
  }
@@ -1053,16 +1055,20 @@ export function createTelegramLockRuntime(options = {}) {
1053
1055
  if (!statePath)
1054
1056
  return readLocks(locksPath);
1055
1057
  // Read-only ownership queries fail closed (no owner); acquisition/publication still refuse malformed state.
1056
- try {
1057
- const transports = Object.fromEntries(Object.entries(readTelegramRuntimeState(statePath).profiles)
1058
- .filter(([, value]) => Object.hasOwn(value, "transport"))
1059
- .map(([profile, value]) => [profile, value.transport]));
1060
- assertTelegramStateTransport(transports[resolveEffectiveKey()]);
1061
- return transports;
1062
- }
1063
- catch {
1064
- return {};
1058
+ // A strict read racing a concurrent atomic replace fails transiently, so retry before failing closed.
1059
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1060
+ try {
1061
+ const transports = Object.fromEntries(Object.entries(readTelegramRuntimeState(statePath).profiles)
1062
+ .filter(([, value]) => Object.hasOwn(value, "transport"))
1063
+ .map(([profile, value]) => [profile, value.transport]));
1064
+ assertTelegramStateTransport(transports[resolveEffectiveKey()]);
1065
+ return transports;
1066
+ }
1067
+ catch {
1068
+ // Retry; a persistent failure falls through to no owner.
1069
+ }
1065
1070
  }
1071
+ return {};
1066
1072
  };
1067
1073
  const transactOwners = (mutate) => {
1068
1074
  if (!statePath)
@@ -1144,7 +1150,11 @@ export function createTelegramLockRuntime(options = {}) {
1144
1150
  changed: false,
1145
1151
  };
1146
1152
  const current = parseTelegramLockEntry(locks[effectiveKey]);
1147
- const state = getLockState(current, pid, isAlive, stateOptions());
1153
+ const observedState = getLockState(current, pid, isAlive, stateOptions());
1154
+ const state = observedState.kind === "active-elsewhere" &&
1155
+ hasSameLockOwner(current, acquireOptions.unresponsiveOwner)
1156
+ ? { kind: "stale", lock: observedState.lock }
1157
+ : observedState;
1148
1158
  const expectedOwned = adoptCompatibleOwnedLock(effectiveKey, current, ctx);
1149
1159
  if (state.kind === "active-here" &&
1150
1160
  hasSameLockOwner(current, expectedOwned) &&
@@ -1191,7 +1201,6 @@ export function createTelegramLockRuntime(options = {}) {
1191
1201
  instanceId: options.instanceId,
1192
1202
  busSocketPath: options.busSocketPath,
1193
1203
  busSecret: options.busSecret,
1194
- getNowMs,
1195
1204
  mintLeaderEpoch: options.mintLeaderEpoch,
1196
1205
  runtimeGeneration,
1197
1206
  });
@@ -1299,9 +1308,36 @@ export function createTelegramLockRuntime(options = {}) {
1299
1308
  };
1300
1309
  }, { ...publication, isCurrent });
1301
1310
  },
1302
- refresh: (ctx) => !deliveryRevoked &&
1303
- transactOwners((locks) => {
1304
- const effectiveKey = resolveEffectiveKey();
1311
+ refresh: (ctx) => {
1312
+ if (deliveryRevoked)
1313
+ return false;
1314
+ // Owner fields only, never a timestamp: liveness is proven over the bus, so an exact owner needs no write.
1315
+ const refreshedEntry = (lock) => {
1316
+ const busSecret = options.busSecret ?? lock.busSecret;
1317
+ return {
1318
+ pid: lock.pid,
1319
+ ...(lock.cwd ? { cwd: lock.cwd } : {}),
1320
+ instanceId: options.instanceId,
1321
+ leaderEpoch: lock.leaderEpoch ?? options.mintLeaderEpoch?.() ?? randomUUID(),
1322
+ runtimeGeneration: lock.runtimeGeneration ?? runtimeGeneration,
1323
+ ...(options.busSocketPath
1324
+ ? { busSocketPath: options.busSocketPath }
1325
+ : {}),
1326
+ ...(busSecret !== undefined ? { busSecret } : {}),
1327
+ ...(lock.journalPath ? { journalPath: lock.journalPath } : {}),
1328
+ };
1329
+ };
1330
+ // Parsed entries carry explicit `undefined` fields; compare only the published ones.
1331
+ const isCurrentEntry = (lock) => lock.leaderEpoch !== undefined &&
1332
+ isDeepStrictEqual(Object.fromEntries(Object.entries(lock).filter(([, value]) => value !== undefined)), refreshedEntry(lock));
1333
+ const effectiveKey = resolveEffectiveKey();
1334
+ const observed = readLock();
1335
+ if (observed &&
1336
+ hasSameLockOwner(observed, adoptCompatibleOwnedLock(effectiveKey, observed, ctx)) &&
1337
+ (!options.instanceId || isCurrentEntry(observed))) {
1338
+ return true;
1339
+ }
1340
+ return transactOwners((locks) => {
1305
1341
  const lock = parseTelegramLockEntry(locks[effectiveKey]);
1306
1342
  const expectedOwner = adoptCompatibleOwnedLock(effectiveKey, lock, ctx);
1307
1343
  if (!lock || !hasSameLockOwner(lock, expectedOwner)) {
@@ -1313,24 +1349,15 @@ export function createTelegramLockRuntime(options = {}) {
1313
1349
  }
1314
1350
  if (!options.instanceId)
1315
1351
  return { result: true, changed: false };
1316
- const refreshedLock = {
1317
- pid: lock.pid,
1318
- ...(lock.cwd ? { cwd: lock.cwd } : {}),
1319
- instanceId: options.instanceId,
1320
- heartbeatMs: getNowMs(),
1321
- leaderEpoch: lock.leaderEpoch ?? options.mintLeaderEpoch?.() ?? randomUUID(),
1322
- runtimeGeneration: lock.runtimeGeneration ?? runtimeGeneration,
1323
- ...(options.busSocketPath
1324
- ? { busSocketPath: options.busSocketPath }
1325
- : {}),
1326
- busSecret: options.busSecret ?? lock.busSecret,
1327
- ...(lock.journalPath ? { journalPath: lock.journalPath } : {}),
1328
- };
1352
+ if (isCurrentEntry(lock))
1353
+ return { result: true, changed: false };
1354
+ const refreshedLock = refreshedEntry(lock);
1329
1355
  locks[effectiveKey] = refreshedLock;
1330
1356
  ownedLockKey = effectiveKey;
1331
1357
  ownedLock = refreshedLock;
1332
1358
  return { result: true, changed: true };
1333
- }),
1359
+ });
1360
+ },
1334
1361
  };
1335
1362
  }
1336
1363
  export function createTelegramLockOwnershipGuard(lock) {
@@ -1417,7 +1444,8 @@ export function createTelegramLockedPollingRuntime(deps) {
1417
1444
  stopOwnershipWatcher();
1418
1445
  ownershipCheckInterval = setInterval(() => {
1419
1446
  try {
1420
- if (deps.lock.owns(owner))
1447
+ // A lock-free miss can be a strict read racing an atomic replace; refresh confirms through the serialized path.
1448
+ if (deps.lock.owns(owner) || deps.lock.refresh(owner))
1421
1449
  return;
1422
1450
  }
1423
1451
  catch (error) {
@@ -1551,6 +1579,9 @@ export function createTelegramLockedPollingRuntime(deps) {
1551
1579
  expectedOwner: options.election?.expectedOwner ??
1552
1580
  (options.force ? takeoverCandidate : undefined),
1553
1581
  election: options.election !== undefined,
1582
+ unresponsiveOwner: options.election?.unresponsive
1583
+ ? options.election.expectedOwner
1584
+ : undefined,
1554
1585
  });
1555
1586
  if (!acquired.ok && !options.election) {
1556
1587
  const currentState = deps.lock.getState();
@@ -1593,22 +1624,33 @@ export function createTelegramLockedPollingRuntime(deps) {
1593
1624
  });
1594
1625
  }
1595
1626
  if (failureMessage) {
1596
- const owner = formatTelegramLockEntry(acquired.lock);
1597
- return {
1598
- ok: false,
1599
- canTakeover: false,
1600
- owner,
1601
- message: `Telegram bridge is active in another Pi instance (${owner}); follower registration failed: ${formatTelegramFollowerRegistrationFailure(failureMessage)}.`,
1602
- };
1627
+ const unresponsiveOwner = acquired.lock;
1628
+ if (deps.proveOwnerUnresponsive &&
1629
+ (await deps.proveOwnerUnresponsive(unresponsiveOwner))) {
1630
+ if (!isCurrent())
1631
+ return cancelled;
1632
+ acquired = deps.lock.acquire(ctx, { unresponsiveOwner });
1633
+ }
1634
+ if (!acquired.ok) {
1635
+ const owner = formatTelegramLockEntry(acquired.lock);
1636
+ return {
1637
+ ok: false,
1638
+ canTakeover: false,
1639
+ owner,
1640
+ message: `Telegram bridge is active in another Pi instance (${owner}); follower registration failed: ${formatTelegramFollowerRegistrationFailure(failureMessage)}.`,
1641
+ };
1642
+ }
1603
1643
  }
1604
1644
  }
1605
- const owner = formatTelegramLockEntry(acquired.lock);
1606
- return {
1607
- ok: false,
1608
- canTakeover: true,
1609
- owner,
1610
- message: `Telegram bridge is active in another Pi instance (${owner}).`,
1611
- };
1645
+ if (!acquired.ok) {
1646
+ const owner = formatTelegramLockEntry(acquired.lock);
1647
+ return {
1648
+ ok: false,
1649
+ canTakeover: true,
1650
+ owner,
1651
+ message: `Telegram bridge is active in another Pi instance (${owner}).`,
1652
+ };
1653
+ }
1612
1654
  }
1613
1655
  takeoverCandidate = undefined;
1614
1656
  if (!(await runOwnedPollingStart(ctx, options, isCurrent))) {
@@ -433,6 +433,15 @@ export function createTelegramRuntimeProjectionStore(options) {
433
433
  if (!isRuntimeProjection({ ...semantic, writtenAtMs: 0 }))
434
434
  throw new Error("Telegram runtime projection is malformed.");
435
435
  delete semantic.diagnostics.recentRuntimeEvents;
436
+ // Poll-cycle timestamps change on every response; live status renders them from memory,
437
+ // so persisting them would rewrite state.json each poll for no reader.
438
+ const polling = semantic.runtime.polling;
439
+ if (polling && typeof polling === "object" && !Array.isArray(polling)) {
440
+ const volatile = polling;
441
+ delete volatile.phaseStartedAtMs;
442
+ delete volatile.lastSuccessfulResponseAtMs;
443
+ delete volatile.lastSuccessfulResponseUpdateCount;
444
+ }
436
445
  const isCurrent = () => getPath() === path && getProfile() === profile && authority() === true;
437
446
  const isUnchanged = (current) => {
438
447
  if (!isRuntimeProjection(current))
package/dist/lib/sync.js CHANGED
@@ -140,6 +140,7 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
140
140
  assertCurrent();
141
141
  const currentRecord = deps.getCurrentThreadRecord();
142
142
  let cleanupPending = false;
143
+ let followerThreadKept = false;
143
144
  if (currentRecord?.target.threadId) {
144
145
  const isManualFollower = currentRecord.owner?.kind === "manual-follower";
145
146
  const leaderEpoch = deps.getCurrentLeaderEpoch?.();
@@ -150,9 +151,8 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
150
151
  if (deps.disconnectFollowerThread) {
151
152
  const disconnected = await deps.disconnectFollowerThread();
152
153
  assertCurrent();
153
- if (!disconnected) {
154
- throw new Error("Telegram follower thread deletion requires a live leader registration.");
155
- }
154
+ // Without a live leader the Thread cannot be deleted; still stop locally and never claim deletion.
155
+ followerThreadKept = !disconnected;
156
156
  }
157
157
  }
158
158
  else {
@@ -223,6 +223,8 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
223
223
  }
224
224
  assertCurrent();
225
225
  const stopped = await deps.stopPolling();
226
+ if (followerThreadKept)
227
+ return "Telegram bridge disconnected. Thread kept: no live leader could delete it.";
226
228
  return cleanupPending
227
229
  ? `${stopped} Telegram thread cleanup remains pending for the next leader.`
228
230
  : stopped;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.53.2",
3
+ "version": "0.54.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -61,7 +61,7 @@ The repository uses a **Flat Domain DAG**:
61
61
 
62
62
  ### Live-Rebind Module Map
63
63
 
64
- Release `0.53.0` adds the [live-rebind channel](./multi-instance-bus.md#live-rebind-channel) and bounded old-Thread cleanup. It advertises live save/apply/settle, command-set and selected-menu-delivery through protocol v2, retaining Workspace Restore for legacy continuity. A loaded 0.53.0 build prefers the live path; an instance still running an older build keeps Restore until it is reloaded. Windows runs the same channel and strict reads, exercised by the CI matrix; there is no platform-specific alternate protocol.
64
+ Release `0.53.0` adds the [live-rebind channel](./multi-instance-bus.md#live-rebind-channel) and bounded old-Thread cleanup. It advertises live save/apply/settle, command-set and selected-menu-delivery through bus protocol v3, retaining Workspace Restore for legacy continuity. A loaded 0.53.0 build prefers the live path; an instance still running an older build keeps Restore until it is reloaded. Windows runs the same channel and strict reads, exercised by the CI matrix; there is no platform-specific alternate protocol.
65
65
 
66
66
  The channel reuses existing owners rather than adding a parallel command bus, queue or recovery store:
67
67
 
@@ -84,7 +84,7 @@ Only the live cleanup origin uses the approved historical-protection reduction.
84
84
  - `workspace-identity`: Exact session/CWD normalization, bounded directory/session keys and local instance-slot encoding. Owns `TelegramWorkspaceBindingIdentity`, the precomputed-key constructor and the shared key-length bound used by collision handling; it depends only on Node crypto/path and existing platform/CWD defaults. Threads, Sync, Routing and both Bus roles consume identity functions directly from this owner; Threads retains old identity exports as compatibility reexports. These value calls do not borrow Threads storage or effect authority. No store, target allocation, admission, callbacks or reverse dependency; constructing a key grants no storage-reference or custody authority.
85
85
  - `target`: Pure `{ chatId, threadId? }` address-value identity. `areTelegramTargetsEqual` compares both fields exactly; a missing Thread differs from Thread zero, and object identity is no shortcut. `getTelegramTargetKey` keeps the canonical `private` sentinel. Delivery, activity and Workspace domains reuse these functions; optional-target and differently keyed policies remain local. Address equality never grants transport, execution or deletion authority.
86
86
  - `process-identity`: PID liveness and stable process-birth identity: Linux `/proc` start ticks, macOS `ps` start time and Windows creation ticks read through PowerShell (cached only for the current process). Only an absent PID (`ESRCH`) or a mismatched birth proof yields `dead`; a live PID without a birth proof (inaccessible metadata, such as another user's process, or a generation fallback) is `unverifiable`. It is a leaf over Node child-process/crypto/fs/os so Locks, Bus, Journal, Workspace admission and Generative Apps share one proof without durable storage depending on the IPC protocol. Liveness never grants authority by itself.
87
- - `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 v2 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.
87
+ - `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.
88
88
  - `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.
89
89
  - `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.
90
90
  - `thread-cleanup-manager`: Disconnected proof-only admission planning for manual inactive-Workspace cleanup. It emits exact profile/binding/target snapshots only when durable inactivity exists, all live-owner/accepted-work/delivery evidence is `clear`, identities are unique, and no reservation, provision, or cleanup competes. Missing, malformed, duplicate, or `unknown` evidence returns no candidates; age and ordering never create authority. Its disconnected bounded profile/token-scoped work store atomically persists exact candidate snapshots, records one exact Workspace-issued deletion permit as outcome-unknown, rejects mismatched permits, and confirms deleted state idempotently; strict reads reject malformed/private-file ambiguity. A disconnected executor requires an injected exclusive Workspace deletion boundary across fresh planner evidence, exact retained-snapshot comparison, permit acquisition/recording, delete callback, and confirmation. The future fence owner must acquire and recheck in admission-ledger order; a retirement fence cannot be nested inside an active ordinary admission lease. Regressions prove drift stops before permit, unavailable/already-issued state cannot fabricate authority, and ambiguous delete remains outcome-unknown without replay. A production-shaped adapter preserves full Workspace records through external-protection resolution, then snapshots exact cleanup fields, reservations, provisions, and cleanup intents; protection exceptions become `unknown`, while source failures propagate fail-closed. Production Settings exposes **Review inactive tabs** only: profile-wide admission surrounds fresh evidence capture, one canonical 128-bit-digest work-set is retained, and a separate summary reports proven count, explicit no-deletion state, and Back navigation. The exact confirmation callback fits Telegram's 64-byte bound and accepts only canonical work-set IDs. **Clean inactive tabs** renders only when composition supplies a destructive port; production intentionally omits it, so malformed/stale callbacks fail closed and review remains non-destructive. Permit composition must not call `acquireRetirementFence()` because retirement remains pressure-only. The one admission-ledger fence now carries discriminated `pressure-retirement | manual-thread-cleanup` authority, treats legacy missing kind as pressure, exposes `acquireThreadCleanupFence()`, includes kind in exact comparison/permits, and remains profile-singleton. Cleanup-specific adopt/issue/absence/release/complete APIs preserve existing retirement callers and reject cross-kind use. Review admission releases before cleanup-fence acquisition; that fence then spans exact full-record/protection revalidation, sole permit issuance, work-set recording, one delete attempt, absence confirmation, binding/work-set commit, and fence completion. The v1-compatible schema and kind-specific acquire/adopt/issue/absence/release/complete methods are implemented; pressure methods reject manual fences and cleanup methods reject pressure fences. A disconnected permit runtime acquires the cleanup fence, revalidates under it, releases drifted unissued fences, refuses already-issued replay, and retains `commit-ready` until an injected durable commit succeeds. `threads.commitInactiveWorkspaceCleanup()` now removes only an exact full inactive binding after rechecking local records, claims, reservations, provisions, cleanups, and retirement intents; the retained cleanup candidate carries sufficient exact cwd/workspace/instance/global-slot/binding/target/inactivity/update commit identity, and under the retained fence exact absence closes commit-unknown retry without reconstructing the deleted full binding. Commit composition removes the binding before confirming the work-set, and failure retains `commit-ready`. A hidden coordinator validates canonical review identity, resolves the full binding, records the sole permit before one injected delete call, then commits binding, work-set, and fence. A `commit-ready` retry finishes without another delete; ambiguous deletion remains `deletion-issued` and is never replayed. A typed Settings-port adapter exposes this coordinator only when explicitly supplied and reports deleted, outcome-unknown, and blocked counts. Fake-port tests exercise the callback lifecycle and successor recovery: takeover requires injected proof, exact fence identity is preserved, live/unverifiable predecessors fail closed, and `commit-ready` resumes without deletion replay. Cross-process workers prove stale `prepared` contenders call fake transport once: deletion requires successful work-set permit CAS, and redundant fences over deleted entries settle without replay. Work-set pre-rename failure and lost post-rename acknowledgement retain recoverable fence truth; durable `deleted` completes that fence without another transport call. Binding-snapshot pre-rename ownership loss reloads and restores the exact binding, while post-rename acknowledgement loss reloads durable absence as successful commit. Cleanup runtime composition additionally requires candidate/work-set/runtime/fence profile equality before mutation and captures one stable successor owner snapshot for adoption. The optional Settings port reports only redacted exact-authority recovery classes: commit pending, deletion outcome unknown with no retry, or unavailable authority. It exposes no target, path, token, or transport details. Production still omits `cleanInactiveThreads`, so Bot API activation remains separate.
@@ -142,7 +142,7 @@ Mirrored domain regressions live in `/tests/*.test.ts`. Shared test fixtures sho
142
142
 
143
143
  Telegram configuration lives in `~/.pi/agent/telegram.json`. Bot/session identity (`botToken`, `botUsername`, `botId`, `allowedUserId`) persists only under `profiles.default` or `profiles.<name>`; shared handlers and assistant/voice/time settings stay top-level. Per-profile polling/admission state lives only in the durable update journal as `acceptedThroughUpdateId`. Authoritative transport ownership lives separately in the `transport` section of the pi-telegram-private `~/.pi/agent/tmp/pi-telegram/state.json`, one profile entry per `default` or validated named profile (see [Consolidated Runtime Root](#consolidated-runtime-root)); unrelated extensions never read or write this file.
144
144
 
145
- `telegram.json` is one global cross-instance configuration document. Ordinary reads rely on atomic publication and do not take the mutation guard. Every cooperating Pi instance persists only its recursive delta from the snapshot it loaded, merges that delta into the latest disk document inside `telegram.json.transaction`, and publishes atomically only when the semantic result differs; a no-op merge adopts the newer disk snapshot in memory without replacing the file. Unrelated global and profile changes therefore survive stale writers. Two serialized writers changing the same leaf use commit order, so the later local delta wins. A non-transactional external editor cannot participate in that conflict protocol: it should write through same-directory atomic replacement while Pi is idle, then let instances reload; an editor racing the transaction may lose its same-leaf change and must retry from the resulting file.
145
+ `telegram.json` is one global cross-instance configuration document. Ordinary reads rely on atomic publication and do not take the mutation guard. Only config writes and sender admission enter `telegram.json.transaction`; the update journal never uses it. Every cooperating Pi instance persists only its recursive delta from the snapshot it loaded, merges that delta into the latest disk document inside `telegram.json.transaction`, and publishes atomically only when the semantic result differs; a no-op merge adopts the newer disk snapshot in memory without replacing the file. Unrelated global and profile changes therefore survive stale writers. Two serialized writers changing the same leaf use commit order, so the later local delta wins. A non-transactional external editor cannot participate in that conflict protocol: it should write through same-directory atomic replacement while Pi is idle, then let instances reload; an editor racing the transaction may lose its same-leaf change and must retry from the resulting file.
146
146
 
147
147
  ### Setup Flow
148
148
 
@@ -216,11 +216,11 @@ The journal prerequisite now exists as the optional `withPairedAdmission` port.
216
216
 
217
217
  The source constraints are concrete. `createTelegramBusFollowerDurableAdmissionRuntime()` appends each authenticated delivery without a cursor; its receiver checks secret, registration generation, recipient binding and source ID, but production journal factories do not yet select the paired-only port. A synthetic store witness admits IDs `20 → 10` into v1 and retains both; substituting cursor-ordered v2 with a fabricated maximum cursor suppresses `10`. The lifecycle's `bind()` currently constructs the worker, resumes terminal entries, and performs dead-owner cleanup before `worker.start()`, so checking migration only at `start()` is too late.
218
218
 
219
- - **New deliveries:** Preserve transport authentication, exact generation/binding/delivery identity and current-session checks. Before append, acquire Workspace admission → config transaction → journal transaction. A config-owned paired-only seam must confirm the envelope sender is the exact persisted owner of the expected profile/token and refresh that authority in the receiving process before worker signaling. It must never create an owner. Boolean "profile is paired" alone is insufficient; a follower cache loaded before another process's grant may still be unpaired. The guard belongs inside the journal's already-Workspace-admitted synchronous append path, not around public `appendBatch()` or async `admit()`: those wrappers would reacquire Workspace under config or span the wrong boundary. Release these admission/transaction guards before worker signaling and ACK. The existing polling observation port selects v2, so neither it nor the automatic first-contact publisher substitutes for a paired-only v1 seam.
219
+ - **New deliveries:** Preserve transport authentication, exact generation/binding/delivery identity and current-session checks. Before append, acquire Workspace admission → config transaction (sender authority) → journal source serialization → journal transaction. A config-owned paired-only seam must confirm the envelope sender is the exact persisted owner of the expected profile/token and refresh that authority in the receiving process before worker signaling. It must never create an owner. Boolean "profile is paired" alone is insufficient; a follower cache loaded before another process's grant may still be unpaired. The guard belongs inside the journal's already-Workspace-admitted synchronous append path, not around public `appendBatch()` or async `admit()`: those wrappers would reacquire Workspace under config or span the wrong boundary. Release these admission/transaction guards before worker signaling and ACK. The existing polling observation port selects v2, so neither it nor the automatic first-contact publisher substitutes for a paired-only v1 seam.
220
220
  - **Source guarantee:** Only the updated leader's exclusion-gated, sender-authorized path may forward content. Followers cannot offer pairing candidates. Bind this contract to the negotiated producer/consumer capability and exact registration; an envelope assertion is not consent. Do not advertise the capability while using an ungated producer or receiver. Mixed/old runtimes remain an operator-coordinated activation prohibition, not a software-enforced downgrade claim.
221
221
  - **Legacy followers:** A configured profile preserves validated v1 accepted work, receipts, births/generations and handoffs without a schema rewrite. If the profile is unpaired, any retained follower source entry—pending, retry, terminal or queued—blocks source startup and a new UI grant until exact operator-authorized reconciliation. Preserve bytes; do not auto-clear, quarantine, assign a maximum cursor, or silently relabel the work. This deliberately narrows automatic legacy migration. Reconcile such state before manual preconfiguration too; no retroactive consent guarantee is inferred from an operator editing the config file.
222
- - **Readiness and migration:** Use complete, bounded, strict read-only inspection of the current profile's polling journal and discovered follower snapshots/segments, not recovery-capable `read()` as a preflight. Unknown identity, unreadable/corrupt evidence or incomplete inventory blocks readiness. Stop and await the old local worker, then re-resolve/revalidate profile/token/registration binding before preparation; the binding captured before the await cannot authorize successor startup. Place preparation before worker construction, terminal retry, queue-owner cleanup and handoff consumers, and publish lifecycle availability only after it succeeds. Also cover non-worker readers: Workspace journal protection capture currently calls recovery-capable `read()` and must not repair/reset evidence ahead of readiness. Discovery alone is insufficient: strict inspection must reject symlinks/unknown identities, bound the inventory, and close namespace creation, recovery and handoff paths through config serialization or explicit quiescence. Operator-coordinated quiescence of other legacy consumers remains necessary. Preserve fixed recovery identities; no "ready" sidecar or cached boolean grants authority.
223
- - **Final grant:** Recheck source readiness inside the queued publication's existing config transaction, under exact transport ownership, only when creating a previously absent owner. New follower writers must use the same config serialization. The check cannot reacquire config or await; journal locks follow config. Recheck candidate/session/profile/token/deadline immediately before rename after inspection. A startup-only inventory or checks outside that transaction do not close the append/grant race.
222
+ - **Readiness and migration:** Use complete, bounded, strict read-only inspection of the current profile's polling journal and discovered follower snapshots/segments, not recovery-capable `read()` as a preflight. Unknown identity, unreadable/corrupt evidence or incomplete inventory blocks readiness. Stop and await the old local worker, then re-resolve/revalidate profile/token/registration binding before preparation; the binding captured before the await cannot authorize successor startup. Place preparation before worker construction, terminal retry, queue-owner cleanup and handoff consumers, and publish lifecycle availability only after it succeeds. Also cover non-worker readers: Workspace journal protection capture currently calls recovery-capable `read()` and must not repair/reset evidence ahead of readiness. Discovery alone is insufficient: strict inspection must reject symlinks/unknown identities, bound the inventory, and close namespace creation, recovery and handoff paths through journal source serialization or explicit quiescence. Operator-coordinated quiescence of other legacy consumers remains necessary. Preserve fixed recovery identities; no "ready" sidecar or cached boolean grants authority.
223
+ - **Final grant:** Recheck source readiness inside the queued publication's existing config transaction, under exact transport ownership, only when creating a previously absent owner. New paired follower writers must use the same config admission. The check cannot reacquire config or await; journal locks follow config. Recheck candidate/session/profile/token/deadline immediately before rename after inspection. A startup-only inventory or checks outside that transaction do not close the append/grant race.
224
224
 
225
225
  Required witnesses before accepting this policy: out-of-order first deliveries survive; a legitimate forwarded message after a peer grant refreshes a stale follower cache without writing a grant; unpaired/different-owner/stale-generation admission has no publication or wakeup; unpaired retained follower data survives restart and blocks UI publication; configured legacy receipts remain intact; preparation precedes every recovery consumer; inventory/append/grant contention cannot produce a ready-but-unexcluded path. If these cannot be proved without another durable authority, reopen the source design rather than widening v2 by assumption.
226
226
 
@@ -268,12 +268,12 @@ Source audit confirms that canonical inventory is not yet usable as a production
268
268
 
269
269
  #### Source Operation Serialization
270
270
 
271
- `config.withSourceSerialization(operation)` is a lock-only prerequisite with completed isolated implementation review. Tests cover unchanged config/cache across absent, corrupt and changed-authority states, callback result/error preservation, release/retry, and actual cross-process contention with observation, paired admission and owner-fenced grant. It invokes trusted synchronous code under the existing private config transaction without reading/adopting config, validating the active profile/token/sender, or exposing credentials or a lock capability. The callback's own result is not authority. Acquire required Workspace admission first; never nest config admission or acquire owners inside it. Promise-returning callbacks are outside the contract: their continuations run after lock release, not as protected asynchronous transactions.
271
+ `Journal.createTelegramJournalSourceSerialization()` is the journal-owned lock-only source serializer at `tmp/pi-telegram/runtime/journals.transaction`; it never touches `telegram.json` or its transaction. Tests cover callback result/error preservation, release/retry, an unchanged (even unreadable) config, and that holding it never blocks pairing observation, paired admission or owner-fenced grant in another process. It reads no config, validates no profile/token/sender, and exposes no lock capability. The callback's own result is not authority. Acquire required Workspace admission and any sender (config) admission first; never acquire owners inside it. Promise-returning callbacks are outside the contract: their continuations run after lock release, not as protected asynchronous transactions.
272
272
 
273
273
  The opt-in journal-store composition now separates continuations rather than adding a second prepared-worker projection. Independent review covers all 12 stateful methods, source-callback rejection before journal locking/read/recovery, actual contention, publication-failure release/retry and unchanged receipt CAS:
274
274
 
275
275
  - `Append`: Canonicalize once → Workspace admission → choose polling observation or paired-human admission → private journal transaction. Without a sender-admission mode, use the lock-only continuation when configured. Do not wrap an admission callback in another config transaction.
276
- - `Other store operations`: Lock-only config serialization → opt-in `sourceAccess` family acquisition → private journal transaction → direct consumption of that operation-local evidence. The same ordering applies after append's selected admission continuation. Acquiring before journal staging is valid only when source serialization excludes all participating family writers through consumption. Without `sourceAccess`, ordinary recovery-capable `readCurrent()` remains unchanged. Ordinary `read()` is optimistic: it first runs the same strict, repair-free evidence read without config or journal guards, because atomic publication makes it safe; only a refused or reconciliation-requiring read retries once through this serialized path, which owns repair. Receipt/owner/handoff CAS and process-birth proof stay authoritative; no caller-supplied lock-held bypass or ambient reentrancy depth is exposed.
276
+ - `Other store operations`: Lock-only journal source serialization → opt-in `sourceAccess` family acquisition → private journal transaction → direct consumption of that operation-local evidence. The same ordering applies after append's selected admission continuation. Acquiring before journal staging is valid only when source serialization excludes all participating family writers through consumption. Without `sourceAccess`, ordinary recovery-capable `readCurrent()` remains unchanged. Ordinary `read()` is optimistic: it first runs the same strict, repair-free evidence read without config or journal guards, because atomic publication makes it safe; only a refused or reconciliation-requiring read retries once through this serialized path, which owns repair. Receipt/owner/handoff CAS and process-birth proof stay authoritative; no caller-supplied lock-held bypass or ambient reentrancy depth is exposed.
277
277
  - `Resource binding`: Participating continuations must use the same actual config resource. Arbitrary injected callbacks cannot prove that themselves; real factory/operation tests must establish one acquisition per operation. The dispatcher is implemented and locally reviewed. `sourceAccess` requires the lock-only continuation and captures an independently approved directory plus physical inspection limits; its store integration is independently reviewed, including generated-state refusal remediation. Production wiring remains absent.
278
278
  - `Evidence`: Real isolated probes show an outer observation plus existing admitted append times out on nested config acquisition. Current-token admission can reject an otherwise valid old-source receipt completion after token rotation; a lock-only transaction still permits exact receipt CAS while rejecting a wrong acquisition ID. Current observation also permits profile-switch/revocation cases, so it is not a lifetime fence. A journal `read()` can repair an empty foreign snapshot while another process holds config, proving append-only coverage is insufficient. These are storage/order witnesses, not permission for obsolete handlers to execute.
279
279
  - `Remaining gates`: Serialization does not provide exact reference reconciliation, old-token migration policy, settled-handler or legacy-snapshot barriers, executable receipt freshness, whole-root quiescence, all-writer closure or final-grant readiness. The whole-root census cannot run inside a journal transaction. Final grant requires an internal already-in-config-transaction check, not reacquisition of this wrapper. Production stores, inspectors and UI remain disconnected from this candidate.
@@ -308,7 +308,7 @@ Inline counterexamples reject two standalone remedies: follower completion reten
308
308
 
309
309
  A further owned probe uses the actual runtime binding and owner/lifecycle/worker assembly, rather than changing an existing routing runtime's identity. After one lost ACK, the recipient finishes its follower source; the old leader stops; registration becomes false and leader authority becomes true for the same recipient instance/context. Starting the assembly's leader lifecycle executes the retained primary source locally. Both journals finish empty after two sink invocations and only one forwarding request. Transport/election authority remains fixture-supplied: this is not OS election, live promotion, Pi model or Telegram API evidence. The inspected production promotion path stops registration and starts locked polling; the assembly independently binds the two journals and does not reconcile their delivery custody.
310
310
 
311
- The correction therefore needs durable delivery custody, not merely a completion cache or an origin-presence predicate. Existing queue handoff already changes exact ownership in one journal: offer freezes the donor, acceptance CAS installs the recipient, and an ambiguous ACK cannot restore donor authority. Its API requires an already queued receipt; raw input must not masquerade as a queued Pi prompt/control. The operator approved continuing with one authoritative input record and explicit execution ownership, with bus delivery transferring/reference-waking that authority instead of creating a second executable copy. This is logical uniqueness per input, not a requirement to combine every profile into one physical file. Legacy journals and their receipts remain intact until separately authorized reconciliation. The ownership contract below governs the next local implementation slice; storage encoding, raw-owner recovery and lifecycle integration remain separately evidenced, and no production activation is implied. A disconnected worker-facing adapter now proves the first integration seam: acquire and start precede handler execution; complete removes exact custody; a single-input queued outcome atomically becomes its durable queue receipt; deferred returns the running receipt; and an ambiguous running claim never executes again. A session-scoped extension retains deferred receipts and atomically groups them into one queue receipt; consumed raw receipts cannot later settle. Late deferred complete/grouped-queue outcomes require that same retained session receipt, so stale or missing callbacks fail closed. Handler failure and fresh-session observation of retained `running` discard local settlement capability, preventing synthetic completion of outcome-unknown work. A disconnected custodied admission handle now runs the existing registry/default routing inside that session; immediate and late reports settle through the same exact receipt authority. Lifecycle bindings may opt into a strict v3 worker port: v3 snapshots require custodied execution, legacy raw completion/queue/failure methods throw, and custodied handler failure remains blocked running work rather than legacy retry state. A gated resolver reads no v3 dependency while disabled; when enabled, its runtime identity binds both source runtime and recipient execution binding so either change forces lifecycle replacement. Real-v3 assembly evidence preserves retained running input across follower registration-generation replacement and follower-to-leader promotion without another handler call or settlement. Worker selection skips running, foreign-ready, and handoff-frozen inputs while draining independent tail; each custodied commit yields and rereads durable state before unresolved work reports `execution` or `input-custody`. Diagnostics expose only update ID and `running-outcome-unknown`, `foreign-ready`, `handoff-frozen`, or `legacy-retry-state`, never owner, acquisition, binding, payload, path, or token details. Running outcome-unknown wins mixed-class priority. Legacy retry/failed state under v3 is quarantined while independent pending tail drains; it is never replayed through the legacy failure policy. A standalone disposition authority now hashes immutable failure metadata without retaining update payload and permits only exact `requeue-v3|discard` operator intent. Claimed/provenanced entries and generic terminal `retry` are excluded. The bounded segmented operator-disposition audit now accepts a discriminated legacy-custody record, avoiding a second log. Its v3-only mutation is reachable only through an injected operator authorization seam and profile admission: exact evidence atomically becomes unclaimed pending for `requeue-v3` or is removed for `discard`; duplicate authority is idempotent and collisions fail closed. Production omits authorization. The strict worker port now constructs an explicit four-method custody projection rather than returning the structurally narrowed backing store; runtime reflection proves operator listing and disposition are absent. This closes a former capability leak hidden by TypeScript's width subtyping. The disconnected operator runtime requires a caller-owned exact binding-reference scope around every list/apply call and retains no store reference. The scope must span the operation, not only lookup, so retirement/pruning cannot race a stale binding; removal, replacement or recovery mismatch fails closed. The existing bounded reference registry now has an `operator-disposition` class for this composition, but remains process-local evidence only. No production or Telegram command owns this runtime. A source invariant forbids the composition root from acquiring writer closure, installing protocol mode, executing cutover or migration publication, supplying legacy disposition authorization, or constructing the operator runtime. A separate strict non-repairing source-inspection port returns only update ID, retry/failed state, attempt count, bounded failure class and evidence SHA-256; it does not enter writer/source mutation serialization; update payload, failure summary and execution authority remain inside the journal owner. Duplicate reconciliation normalizes exact retained authority before invoking the injected authorizer; malformed extra fields never cross that boundary. If snapshot publication is commit-unknown, exact retry finds the durable audit and performs no second transition; a new disposition cannot target the resulting pending entry. Running custody still has no admissible legacy evidence. A real-v3 replacement regression begins with retained `running` and proves neither the old nor replacement session can execute or settle it after recipient identity changes. Source `recoveryKey` and recipient execution binding are distinct mandatory identities. An assembled regression proves late reports from two deferred sources publish one grouped durable queue receipt. Grouped late reports retain one exact duplicate result for each remaining source callback; the worker accepts the same already-published queue authority idempotently and rejects conflicts. Lifecycle assembly selects this path only for an optional binding-owned custody port. Its strict v3 worker adapter permits exact queued completion but throws before legacy raw completion, queue, or failure mutation; handler errors remain running outcome-unknown. The worker consumes already-durable results without legacy completion/queue writes, synchronizes late claims, and lifecycle assembly selects this path only from an optional binding-owned custody port plus exact binding key. The strict v3 binding also exposes queued receipt offer/accept/cancel through serialized admission wrappers; lifecycle lookup now proves exact recipient acceptance and exact duplicate retries return the same owner. Recipient worker projection is queried only with the matching journal binding key and survives lifecycle replacement. These APIs transfer Pi queue authority and remain distinct from raw input handoff. Cancelling a frozen raw handoff and signaling the worker makes the input executable and clears its redacted blocked projection. A legacy journal writer cannot clear quarantined v3 retry evidence because strict source-version selection rejects that mutation; any future operator disposition therefore needs explicit v3 authority separate from worker settlement. A disconnected bus admission mode validates delivery, source and recipient identity, then wakes the existing durable source reference without journaling the forwarded carrier as a second executable copy. Duplicate delivery can repeat only the wake; stale registration generation cannot wake, and selection fails closed without wake authority rather than falling back to copy admission. The gated delivery identity carries an optional bounded source `recoveryKey` outside the unchanged legacy delivery hash. Reference mode requires that key, bounded exact acquisition/handoff IDs, and explicit authenticated-transport proof; missing or mixed legacy evidence is rejected before wake. Legacy copy mode does not require or infer these fields. Wake carries the complete reference into a disconnected binding-owned resolver. It requires exact recovery key, recipient binding/live owner, update, acquisition and accepted handoff on one unfrozen `ready` claim before signaling; stale, running, missing or mismatched evidence cannot wake or mutate. An authenticated receiver composition against a real shared v3 journal now proves exact and duplicate delivery only signal the accepted ready claim; stale acquisition or registration generation cannot wake or fall back to copy admission. A disconnected sender selector requires mutual `input-custody-reference-v1` capability, and its constructor accepts only an exact accepted handoff when producing recovery/acquisition/handoff evidence. The dedicated `leader.wakeInputCustody` envelope carries no executable Telegram carrier and is forced through authenticated reference admission; legacy durable admission rejects it. A shared-v3 regression composes accepted handoff, leader resolver, mutually capable forwarder, authenticated payload-free receiver and exact wake; exact request replay reuses the cached ACK without another signal. A recipient adapter resolves recovery/binding/live-owner authority, performs exact acceptance CAS, verifies the returned handoff, and only then exposes the reference and signals. The payload-free `leader.offerInputCustodyHandoff` envelope carries exact source/handoff and recipient lifetime evidence into it; parsing, authentication, capability, binding and handler gates fail closed, while duplicate acceptance returns durable duplicate evidence. A real-v3 composition runs donor offer-before-send through the authenticated handoff receiver; acceptance CAS signals once, and client replay reconciles locally without another request. No immediate wake follows acceptance because it would duplicate signaling; payload-free wake remains only an idempotent recovery nudge for already accepted custody. Unknown outcomes remain frozen; after recipient acceptance, retry first reconciles the accepted shared-journal reference and returns duplicate settlement without re-offer, transport, or new authority. Full handoff-then-wake request composition remains disconnected. Frozen pre-accept lookup yields no reference, preventing transport. Missing reference fails retryably before transport; mixed peers retain legacy carrier delivery. A disconnected leader resolver reads shared v3 state without mutation and returns a forward reference only for the exact recipient-owned unfrozen `ready` claim; donor-frozen, running, stale binding or owner mismatch returns no reference. A single optional custody bus bundle derives acceptance, wake and forward-reference resolution from one recovery-key lookup carrying the same recipient binding/live owner, journal and signal authority. Lifecycle assembly/runtime binding exposes that bundle only when supplied; losing its active authority disables lookup and makes acceptance/wake fail closed together. A follower transport adapter rereads the optional bundle for every capability check, acceptance, wake and forward lookup. Removing the bundle on downgrade/reconnect simultaneously disables capability/resolution and makes stale operations fail closed without cached authority. A disconnected receiver assembly rereads these ports across downgrade/reconnect: bundle removal rejects before handlers, replacement generation uses only the new bundle, and stale generation cannot call either bundle or legacy admission. Live target ownership now carries the exact current registration protocol identity into forwarder selection: capability upgrade enables reference mode and downgrade returns to legacy, while persisted records grant neither mode. Forwarders may additionally revalidate current ownership after reference resolution and immediately before transport. If generation, binding, or protocol changed, they return `recipient-ownership-stale` without sending, preventing an old capable snapshot from crossing reconnect. Production follower-client composition uses one canonical live-registry validator immediately before transport. Removal, same-generation protocol replacement, or generation replacement invalidates old ownership; only a fresh current projection passes. The receiver generation check remains an independent second fence. A post-accept retry using stale ownership is rejected before transport after registry replacement, so it cannot reach either receiver execution or its cached ACK. Production still omits the custody bundle and capability; A disconnected readiness evaluator requires requested absent/v3 source, proven legacy-writer exclusion, completed historical migration and capability-ready peers; all mixed, legacy, unsupported, ambiguous or unknown evidence returns a bounded blocker. A disconnected lazy resolver reads no source/peer evidence while disabled or blocked by writer/migration proof, maps inspection loss to source-unready and inventory loss to capability mismatch, and creates no journal/lifecycle binding. Read-only adapters now normalize real strict family inspection as absent/v3/legacy/ambiguous and live follower generation/protocol inventory as ready/legacy/unknown; corrupt source and missing live identity fail closed without mutation. A real disk/registry composition rereads on every call: absent/v3 plus peers advertising durable admission and custody reference enables; peer downgrade, source corruption, or missing identity blocks; replacement upgrade re-enables. Migration and writer exclusion now enter through typed versioned evidence bound to exact profile/recovery identity; missing, unknown, incomplete, present-writer or mismatched evidence blocks before source/peer inspection. A disconnected retained-evidence store adds a bounded strict v1 codec, revision CAS under injected serialization, kind-specific validation and injected publication authority. Malformed, stale, unauthorized or cross-kind evidence never reaches the persistence port. Writer and migration records in one snapshot must share exact profile/recovery identity; even authorized mixed-identity publication fails before persistence. No current component may mint writer exclusion because existing census/serialization cannot prove arbitrary writer or consumer absence. Lifecycle runtime now admits an injected logical source lease: bind acquires, replacement releases/reacquires, shutdown releases despite retaining a reusable binding object, and restart reacquires. Release failure cannot retain runtime authority. One bounded exact-release process-local registry now backs production leader/follower lifecycle leases; capacity and stale release fail closed, and shutdown/restart/replacement update inventory. It is neither durable nor whole-root proof. The registry also scopes sync/async operations and releases on return, throw or rejection. Production status/polling cursor reads, cursor cutover and bootstrap entry reads now use short-lived leader leases; missing binding acquires none. Workspace-retirement protection now scopes shared, retained-binding and discovered journal reads through the same registry and releases before planning continues, including read failure. A composition invariant now rejects direct resolver journal reads in the entrypoint. Remaining production resolver uses are leased lifecycle bindings, scoped polling/retirement operations, or queue-handoff recovery-key computation without journal I/O. Composition invariants reject raw store construction in `index.ts`, require follower durable writes through the leased lifecycle, scope the sole cursor append, and prove current root/API exports expose no journal mutation factory. Old-process exclusion now has a pure evaluator, but not an inventory authority: exact profile/recovery identity, caller-proven complete writer list and process-birth liveness are mandatory. Alive means present; mismatch/incomplete/unverifiable means unknown; only every listed birth proven dead means excluded. Registry absence is never death. The journal transaction owner now exposes an optional outer writer-admission seam through all binding factories. It gates append, mutation and ordinary read (which may repair) before source serialization/journal locking; denial writes nothing, while strict inspection remains read-only. Production leaves it unset. The approved preparation direction is one third Workspace-ledger destructive kind, `journal-writer-closure`, not another ledger and not a cleanup/retirement alias. Its discriminated payload binds profile/recovery identity and closure operation ID but has no target, deletion permit or absence phase. Acquisition requires zero ordinary leases; while held, ordinary profile admission fails. The journal writer seam will acquire/release one ordinary profile lease before config/journal locking, so closure and writers serialize through ledger transactions without nesting the config transaction. Current lock-order regression proves denial never enters pairing/source serialization and allowed append enters writer admission first. Legacy missing-kind fences remain pressure retirement. The third kind and strict identity-only payload codec reject deletion fields and identity drift while retirement/cleanup fence types remain narrowed. The top-level ledger union is integrated across admission, retirement coordination, and cleanup ports: closure blocks ordinary admission and competing destructive work globally, owns no Thread slot, and every deletion operation narrows the kind before accessing deletion authority. Compile-time deletion fence/permit authority is also a dedicated retirement-or-cleanup union; `journal-writer-closure` cannot inhabit it. Exact closure acquisition/release is now available on the ledger but remains disconnected from production. Acquisition requires zero leases, resumes the exact durable authority after lost ACK without republishing, rejects same-operation drift, and blocks other destructive work; release requires exact current-owner authority. The optional journal-writer adapter acquires a profile lease before entering the journal seam and retains its ID after ambiguous acquisition. Release failure is diagnostic-only after the journal operation settles: retaining a possible lease blocks closure safely, while throwing from `finally` would falsely erase the known operation outcome. The existing binding runtime propagates one supplied adapter to leader, active follower, recipient and path-discovered journals; regression coverage closes all four classes. Production omission still means no writer closure activation. Writer-exclusion evidence cannot yet be minted truthfully inside this fence: releasing it allows a newly starting legacy writer, retaining it blocks v3 writers too, and retained v1 `excluded` evidence records neither closure operation nor startup authority. Closure publication therefore requires a prior durable operator-authorized startup-exclusion authority plus protocol-class admission semantics. A strict standalone authority schema now binds profile/recovery, closure operation, inventory SHA-256, operator authority ID, explicit enforced/revoked status, authorization time, and `custody-v3` as the sole allowed protocol. It is retained by the readiness store through strict revisioned CAS, injected publication authorization and cross-kind identity checks. The full candidate is decoded and normalized before authorization, so malformed/extra evidence never crosses the authority callback; enforced and revoked states are retained exactly. Proven readiness now consumes it fail-closed: `excluded` writer evidence must link the same enforced authority ID, closure operation and inventory digest. Legacy unlinked evidence, revocation or any link drift blocks activation, and the store rejects conflicting linked authorities. Proven readiness also requires the durable installed protocol mode to match the same profile/recovery, startup authority, closure and inventory digest. A crash before mode installation or before linked exclusion publication therefore remains disabled. The non-nesting operator coordinator now enforces the safe order: verify retained enforced authority → evaluate exact inventory/liveness under closure → atomically install mode → reread authority → CAS-publish linked exclusion. A pre-publication revocation or race leaves mode installed but no readiness evidence, so activation remains disabled; exact retry reconciles installed mode and existing evidence without duplicate authority. Production has no caller. Migration completion now has a standalone strict authority binding the same cutover identities plus a complete historical source/disposition digest, resulting `absent|v3` family, operator authorization time and explicit revocation. It is now retained through authorized revisioned CAS and consumed by proven readiness only when migration evidence links the same migration/startup/closure/inventory authority. Revocation, legacy unlinked evidence, or a live source family different from the authorized `absent|v3` result blocks activation on every resolution. The disconnected migration publisher rereads retained authority, compares the exact historical inventory digest and inspects the live source before CAS-publishing linked completion. Exact retry is revision-stable; it performs no migration or source mutation. Its strict standalone Workspace mode codec now binds `custody-v3` to exact profile/recovery, startup authority, closure operation, inventory digest, installer owner and install time. The ledger now retains it by atomically replacing the matching zero-lease closure; fence+mode state is invalid and exact lost-ACK installation resumes without republish. While mode exists, generic `journal-write` and new closure acquisition fail closed. Dedicated v3 writer admission must present matching recovery, startup authority, closure and inventory digest; operation kind alone never grants access. While mode is active, generic `journal-write` and `journal.*` admission requires an already-held same-owner dedicated v3 profile lease. This permits nested append/input admission under the outer writer seam but rejects caller-chosen journal operation names as authority. Production installs no mode. Mode→closure publication is crash-proven: after an acknowledged-unknown atomic rename, a fresh ledger resumes the exact closure without invoking authorization again or restoring mode. Re-closing an installed mode is possible only through an injected production-unset authorizer: with zero leases, one ledger transaction replaces the exact recovery-bound mode with a new closure; active writers block before authorization and denied callbacks leave mode unchanged. Exact closure retry handles lost publication ACK. A compile probe established that this must be one compatibility cohort across the ledger, retirement coordinator, and cleanup-manager ledger port; publishing a wider snapshot before those consumers narrow by kind is forbidden. Required slices are: generalize fence payload codec without changing existing kinds; add closure acquire/exact release only; wire writer admission; then allow complete inventory capture and evidence publication while the same fence remains held. No step may infer external writer absence. Exported legacy factories and arbitrary installed-package consumers remain outside process-local proof; writer-exclusion minting therefore requires package/API closure or explicit retirement, and rollout stays gated. Optional lifecycle exposure and replacement invalidation remain disconnected. Production bindings expose neither port. Its optional late-settlement callback lets the worker clear an exact deferred projection or register already-durable queue authority without repeating a legacy journal mutation. The real worker drain accepts an optional already-settled custody result: completion rereads durable state without legacy removal, while outcome-unknown blocks without creating retry authority. Production still selects only the legacy path.
311
+ The correction therefore needs durable delivery custody, not merely a completion cache or an origin-presence predicate. Existing queue handoff already changes exact ownership in one journal: offer freezes the donor, acceptance CAS installs the recipient, and an ambiguous ACK cannot restore donor authority. Its API requires an already queued receipt; raw input must not masquerade as a queued Pi prompt/control. The operator approved continuing with one authoritative input record and explicit execution ownership, with bus delivery transferring/reference-waking that authority instead of creating a second executable copy. This is logical uniqueness per input, not a requirement to combine every profile into one physical file. Legacy journals and their receipts remain intact until separately authorized reconciliation. The ownership contract below governs the next local implementation slice; storage encoding, raw-owner recovery and lifecycle integration remain separately evidenced, and no production activation is implied. A disconnected worker-facing adapter now proves the first integration seam: acquire and start precede handler execution; complete removes exact custody; a single-input queued outcome atomically becomes its durable queue receipt; deferred returns the running receipt; and an ambiguous running claim never executes again. A session-scoped extension retains deferred receipts and atomically groups them into one queue receipt; consumed raw receipts cannot later settle. Late deferred complete/grouped-queue outcomes require that same retained session receipt, so stale or missing callbacks fail closed. Handler failure and fresh-session observation of retained `running` discard local settlement capability, preventing synthetic completion of outcome-unknown work. A disconnected custodied admission handle now runs the existing registry/default routing inside that session; immediate and late reports settle through the same exact receipt authority. Lifecycle bindings may opt into a strict v3 worker port: v3 snapshots require custodied execution, legacy raw completion/queue/failure methods throw, and custodied handler failure remains blocked running work rather than legacy retry state. A gated resolver reads no v3 dependency while disabled; when enabled, its runtime identity binds both source runtime and recipient execution binding so either change forces lifecycle replacement. Real-v3 assembly evidence preserves retained running input across follower registration-generation replacement and follower-to-leader promotion without another handler call or settlement. Worker selection skips running, foreign-ready, and handoff-frozen inputs while draining independent tail; each custodied commit yields and rereads durable state before unresolved work reports `execution` or `input-custody`. Diagnostics expose only update ID and `running-outcome-unknown`, `foreign-ready`, `handoff-frozen`, or `legacy-retry-state`, never owner, acquisition, binding, payload, path, or token details. Running outcome-unknown wins mixed-class priority. Legacy retry/failed state under v3 is quarantined while independent pending tail drains; it is never replayed through the legacy failure policy. A standalone disposition authority now hashes immutable failure metadata without retaining update payload and permits only exact `requeue-v3|discard` operator intent. Claimed/provenanced entries and generic terminal `retry` are excluded. The bounded segmented operator-disposition audit now accepts a discriminated legacy-custody record, avoiding a second log. Its v3-only mutation is reachable only through an injected operator authorization seam and profile admission: exact evidence atomically becomes unclaimed pending for `requeue-v3` or is removed for `discard`; duplicate authority is idempotent and collisions fail closed. Production omits authorization. The strict worker port now constructs an explicit four-method custody projection rather than returning the structurally narrowed backing store; runtime reflection proves operator listing and disposition are absent. This closes a former capability leak hidden by TypeScript's width subtyping. The disconnected operator runtime requires a caller-owned exact binding-reference scope around every list/apply call and retains no store reference. The scope must span the operation, not only lookup, so retirement/pruning cannot race a stale binding; removal, replacement or recovery mismatch fails closed. The existing bounded reference registry now has an `operator-disposition` class for this composition, but remains process-local evidence only. No production or Telegram command owns this runtime. A source invariant forbids the composition root from acquiring writer closure, installing protocol mode, executing cutover or migration publication, supplying legacy disposition authorization, or constructing the operator runtime. A separate strict non-repairing source-inspection port returns only update ID, retry/failed state, attempt count, bounded failure class and evidence SHA-256; it does not enter writer/source mutation serialization; update payload, failure summary and execution authority remain inside the journal owner. Duplicate reconciliation normalizes exact retained authority before invoking the injected authorizer; malformed extra fields never cross that boundary. If snapshot publication is commit-unknown, exact retry finds the durable audit and performs no second transition; a new disposition cannot target the resulting pending entry. Running custody still has no admissible legacy evidence. A real-v3 replacement regression begins with retained `running` and proves neither the old nor replacement session can execute or settle it after recipient identity changes. Source `recoveryKey` and recipient execution binding are distinct mandatory identities. An assembled regression proves late reports from two deferred sources publish one grouped durable queue receipt. Grouped late reports retain one exact duplicate result for each remaining source callback; the worker accepts the same already-published queue authority idempotently and rejects conflicts. Lifecycle assembly selects this path only for an optional binding-owned custody port. Its strict v3 worker adapter permits exact queued completion but throws before legacy raw completion, queue, or failure mutation; handler errors remain running outcome-unknown. The worker consumes already-durable results without legacy completion/queue writes, synchronizes late claims, and lifecycle assembly selects this path only from an optional binding-owned custody port plus exact binding key. The strict v3 binding also exposes queued receipt offer/accept/cancel through serialized admission wrappers; lifecycle lookup now proves exact recipient acceptance and exact duplicate retries return the same owner. Recipient worker projection is queried only with the matching journal binding key and survives lifecycle replacement. These APIs transfer Pi queue authority and remain distinct from raw input handoff. Cancelling a frozen raw handoff and signaling the worker makes the input executable and clears its redacted blocked projection. A legacy journal writer cannot clear quarantined v3 retry evidence because strict source-version selection rejects that mutation; any future operator disposition therefore needs explicit v3 authority separate from worker settlement. A disconnected bus admission mode validates delivery, source and recipient identity, then wakes the existing durable source reference without journaling the forwarded carrier as a second executable copy. Duplicate delivery can repeat only the wake; stale registration generation cannot wake, and selection fails closed without wake authority rather than falling back to copy admission. The gated delivery identity carries an optional bounded source `recoveryKey` outside the unchanged legacy delivery hash. Reference mode requires that key, bounded exact acquisition/handoff IDs, and explicit authenticated-transport proof; missing or mixed legacy evidence is rejected before wake. Legacy copy mode does not require or infer these fields. Wake carries the complete reference into a disconnected binding-owned resolver. It requires exact recovery key, recipient binding/live owner, update, acquisition and accepted handoff on one unfrozen `ready` claim before signaling; stale, running, missing or mismatched evidence cannot wake or mutate. An authenticated receiver composition against a real shared v3 journal now proves exact and duplicate delivery only signal the accepted ready claim; stale acquisition or registration generation cannot wake or fall back to copy admission. A disconnected sender selector requires mutual `input-custody-reference-v1` capability, and its constructor accepts only an exact accepted handoff when producing recovery/acquisition/handoff evidence. The dedicated `leader.wakeInputCustody` envelope carries no executable Telegram carrier and is forced through authenticated reference admission; legacy durable admission rejects it. A shared-v3 regression composes accepted handoff, leader resolver, mutually capable forwarder, authenticated payload-free receiver and exact wake; exact request replay reuses the cached ACK without another signal. A recipient adapter resolves recovery/binding/live-owner authority, performs exact acceptance CAS, verifies the returned handoff, and only then exposes the reference and signals. The payload-free `leader.offerInputCustodyHandoff` envelope carries exact source/handoff and recipient lifetime evidence into it; parsing, authentication, capability, binding and handler gates fail closed, while duplicate acceptance returns durable duplicate evidence. A real-v3 composition runs donor offer-before-send through the authenticated handoff receiver; acceptance CAS signals once, and client replay reconciles locally without another request. No immediate wake follows acceptance because it would duplicate signaling; payload-free wake remains only an idempotent recovery nudge for already accepted custody. Unknown outcomes remain frozen; after recipient acceptance, retry first reconciles the accepted shared-journal reference and returns duplicate settlement without re-offer, transport, or new authority. Full handoff-then-wake request composition remains disconnected. Frozen pre-accept lookup yields no reference, preventing transport. Missing reference fails retryably before transport; mixed peers retain legacy carrier delivery. A disconnected leader resolver reads shared v3 state without mutation and returns a forward reference only for the exact recipient-owned unfrozen `ready` claim; donor-frozen, running, stale binding or owner mismatch returns no reference. A single optional custody bus bundle derives acceptance, wake and forward-reference resolution from one recovery-key lookup carrying the same recipient binding/live owner, journal and signal authority. Lifecycle assembly/runtime binding exposes that bundle only when supplied; losing its active authority disables lookup and makes acceptance/wake fail closed together. A follower transport adapter rereads the optional bundle for every capability check, acceptance, wake and forward lookup. Removing the bundle on downgrade/reconnect simultaneously disables capability/resolution and makes stale operations fail closed without cached authority. A disconnected receiver assembly rereads these ports across downgrade/reconnect: bundle removal rejects before handlers, replacement generation uses only the new bundle, and stale generation cannot call either bundle or legacy admission. Live target ownership now carries the exact current registration protocol identity into forwarder selection: capability upgrade enables reference mode and downgrade returns to legacy, while persisted records grant neither mode. Forwarders may additionally revalidate current ownership after reference resolution and immediately before transport. If generation, binding, or protocol changed, they return `recipient-ownership-stale` without sending, preventing an old capable snapshot from crossing reconnect. Production follower-client composition uses one canonical live-registry validator immediately before transport. Removal, same-generation protocol replacement, or generation replacement invalidates old ownership; only a fresh current projection passes. The receiver generation check remains an independent second fence. A post-accept retry using stale ownership is rejected before transport after registry replacement, so it cannot reach either receiver execution or its cached ACK. Production still omits the custody bundle and capability; A disconnected readiness evaluator requires requested absent/v3 source, proven legacy-writer exclusion, completed historical migration and capability-ready peers; all mixed, legacy, unsupported, ambiguous or unknown evidence returns a bounded blocker. A disconnected lazy resolver reads no source/peer evidence while disabled or blocked by writer/migration proof, maps inspection loss to source-unready and inventory loss to capability mismatch, and creates no journal/lifecycle binding. Read-only adapters now normalize real strict family inspection as absent/v3/legacy/ambiguous and live follower generation/protocol inventory as ready/legacy/unknown; corrupt source and missing live identity fail closed without mutation. A real disk/registry composition rereads on every call: absent/v3 plus peers advertising durable admission and custody reference enables; peer downgrade, source corruption, or missing identity blocks; replacement upgrade re-enables. Migration and writer exclusion now enter through typed versioned evidence bound to exact profile/recovery identity; missing, unknown, incomplete, present-writer or mismatched evidence blocks before source/peer inspection. A disconnected retained-evidence store adds a bounded strict v1 codec, revision CAS under injected serialization, kind-specific validation and injected publication authority. Malformed, stale, unauthorized or cross-kind evidence never reaches the persistence port. Writer and migration records in one snapshot must share exact profile/recovery identity; even authorized mixed-identity publication fails before persistence. No current component may mint writer exclusion because existing census/serialization cannot prove arbitrary writer or consumer absence. Lifecycle runtime now admits an injected logical source lease: bind acquires, replacement releases/reacquires, shutdown releases despite retaining a reusable binding object, and restart reacquires. Release failure cannot retain runtime authority. One bounded exact-release process-local registry now backs production leader/follower lifecycle leases; capacity and stale release fail closed, and shutdown/restart/replacement update inventory. It is neither durable nor whole-root proof. The registry also scopes sync/async operations and releases on return, throw or rejection. Production status/polling cursor reads, cursor cutover and bootstrap entry reads now use short-lived leader leases; missing binding acquires none. Workspace-retirement protection now scopes shared, retained-binding and discovered journal reads through the same registry and releases before planning continues, including read failure. A composition invariant now rejects direct resolver journal reads in the entrypoint. Remaining production resolver uses are leased lifecycle bindings, scoped polling/retirement operations, or queue-handoff recovery-key computation without journal I/O. Composition invariants reject raw store construction in `index.ts`, require follower durable writes through the leased lifecycle, scope the sole cursor append, and prove current root/API exports expose no journal mutation factory. Old-process exclusion now has a pure evaluator, but not an inventory authority: exact profile/recovery identity, caller-proven complete writer list and process-birth liveness are mandatory. Alive means present; mismatch/incomplete/unverifiable means unknown; only every listed birth proven dead means excluded. Registry absence is never death. The journal transaction owner now exposes an optional outer writer-admission seam through all binding factories. It gates append, mutation and ordinary read (which may repair) before source serialization/journal locking; denial writes nothing, while strict inspection remains read-only. Production leaves it unset. The approved preparation direction is one third Workspace-ledger destructive kind, `journal-writer-closure`, not another ledger and not a cleanup/retirement alias. Its discriminated payload binds profile/recovery identity and closure operation ID but has no target, deletion permit or absence phase. Acquisition requires zero ordinary leases; while held, ordinary profile admission fails. The journal writer seam will acquire/release one ordinary profile lease before config/journal locking, so closure and writers serialize through ledger transactions without nesting the config transaction. Current lock-order regression proves denial never enters pairing/source serialization and allowed append enters writer admission, then pairing (config) admission, then source serialization. Legacy missing-kind fences remain pressure retirement. The third kind and strict identity-only payload codec reject deletion fields and identity drift while retirement/cleanup fence types remain narrowed. The top-level ledger union is integrated across admission, retirement coordination, and cleanup ports: closure blocks ordinary admission and competing destructive work globally, owns no Thread slot, and every deletion operation narrows the kind before accessing deletion authority. Compile-time deletion fence/permit authority is also a dedicated retirement-or-cleanup union; `journal-writer-closure` cannot inhabit it. Exact closure acquisition/release is now available on the ledger but remains disconnected from production. Acquisition requires zero leases, resumes the exact durable authority after lost ACK without republishing, rejects same-operation drift, and blocks other destructive work; release requires exact current-owner authority. The optional journal-writer adapter acquires a profile lease before entering the journal seam and retains its ID after ambiguous acquisition. Release failure is diagnostic-only after the journal operation settles: retaining a possible lease blocks closure safely, while throwing from `finally` would falsely erase the known operation outcome. The existing binding runtime propagates one supplied adapter to leader, active follower, recipient and path-discovered journals; regression coverage closes all four classes. Production omission still means no writer closure activation. Writer-exclusion evidence cannot yet be minted truthfully inside this fence: releasing it allows a newly starting legacy writer, retaining it blocks v3 writers too, and retained v1 `excluded` evidence records neither closure operation nor startup authority. Closure publication therefore requires a prior durable operator-authorized startup-exclusion authority plus protocol-class admission semantics. A strict standalone authority schema now binds profile/recovery, closure operation, inventory SHA-256, operator authority ID, explicit enforced/revoked status, authorization time, and `custody-v3` as the sole allowed protocol. It is retained by the readiness store through strict revisioned CAS, injected publication authorization and cross-kind identity checks. The full candidate is decoded and normalized before authorization, so malformed/extra evidence never crosses the authority callback; enforced and revoked states are retained exactly. Proven readiness now consumes it fail-closed: `excluded` writer evidence must link the same enforced authority ID, closure operation and inventory digest. Legacy unlinked evidence, revocation or any link drift blocks activation, and the store rejects conflicting linked authorities. Proven readiness also requires the durable installed protocol mode to match the same profile/recovery, startup authority, closure and inventory digest. A crash before mode installation or before linked exclusion publication therefore remains disabled. The non-nesting operator coordinator now enforces the safe order: verify retained enforced authority → evaluate exact inventory/liveness under closure → atomically install mode → reread authority → CAS-publish linked exclusion. A pre-publication revocation or race leaves mode installed but no readiness evidence, so activation remains disabled; exact retry reconciles installed mode and existing evidence without duplicate authority. Production has no caller. Migration completion now has a standalone strict authority binding the same cutover identities plus a complete historical source/disposition digest, resulting `absent|v3` family, operator authorization time and explicit revocation. It is now retained through authorized revisioned CAS and consumed by proven readiness only when migration evidence links the same migration/startup/closure/inventory authority. Revocation, legacy unlinked evidence, or a live source family different from the authorized `absent|v3` result blocks activation on every resolution. The disconnected migration publisher rereads retained authority, compares the exact historical inventory digest and inspects the live source before CAS-publishing linked completion. Exact retry is revision-stable; it performs no migration or source mutation. Its strict standalone Workspace mode codec now binds `custody-v3` to exact profile/recovery, startup authority, closure operation, inventory digest, installer owner and install time. The ledger now retains it by atomically replacing the matching zero-lease closure; fence+mode state is invalid and exact lost-ACK installation resumes without republish. While mode exists, generic `journal-write` and new closure acquisition fail closed. Dedicated v3 writer admission must present matching recovery, startup authority, closure and inventory digest; operation kind alone never grants access. While mode is active, generic `journal-write` and `journal.*` admission requires an already-held same-owner dedicated v3 profile lease. This permits nested append/input admission under the outer writer seam but rejects caller-chosen journal operation names as authority. Production installs no mode. Mode→closure publication is crash-proven: after an acknowledged-unknown atomic rename, a fresh ledger resumes the exact closure without invoking authorization again or restoring mode. Re-closing an installed mode is possible only through an injected production-unset authorizer: with zero leases, one ledger transaction replaces the exact recovery-bound mode with a new closure; active writers block before authorization and denied callbacks leave mode unchanged. Exact closure retry handles lost publication ACK. A compile probe established that this must be one compatibility cohort across the ledger, retirement coordinator, and cleanup-manager ledger port; publishing a wider snapshot before those consumers narrow by kind is forbidden. Required slices are: generalize fence payload codec without changing existing kinds; add closure acquire/exact release only; wire writer admission; then allow complete inventory capture and evidence publication while the same fence remains held. No step may infer external writer absence. Exported legacy factories and arbitrary installed-package consumers remain outside process-local proof; writer-exclusion minting therefore requires package/API closure or explicit retirement, and rollout stays gated. Optional lifecycle exposure and replacement invalidation remain disconnected. Production bindings expose neither port. Its optional late-settlement callback lets the worker clear an exact deferred projection or register already-durable queue authority without repeating a legacy journal mutation. The real worker drain accepts an optional already-settled custody result: completion rereads durable state without legacy removal, while outcome-unknown blocks without creating retry authority. Production still selects only the legacy path.
312
312
 
313
313
  ##### Input Custody Contract
314
314
 
@@ -362,7 +362,7 @@ Read-only migration probes constrain that comparison. Publishing a v2 snapshot o
362
362
  - Session replacement through `reload`, `new`, `resume`, or `fork` suspends polling/watchers without releasing ownership or publishing inactivity so the next session in the same process can resume. Late shutdown cannot clear a replacement context even when its identity is reused: the captured session generation must still match. For replacements other than `/resume`, a registered follower snapshots its assigned target into a short-lived same-process handoff, stops the old receiver/heartbeat, and re-registers through the live leader without marking or replacing its Telegram thread. Local `/resume` suppresses that source-target handoff and selects the destination session's binding instead. Hard process termination cannot run graceful teardown, so stale recovery retains its restart-hint path.
363
363
  - Live external owners require explicit takeover confirmation. Long-lived timers compare against snapshotted owner identity and stop local transport work when the slot no longer matches.
364
364
  - The profile's `transport` section owns only Telegram transport control. Local extension and accepted queue state remain per Pi instance when ownership moves, but previews, final delivery, dispatch transport mutations, and delayed Bot API work fail closed until exact direct or follower authority becomes valid again.
365
- - Exact ownership remains checked every second, while the durable owner heartbeat refresh runs every two seconds and becomes stale after eight seconds. Every acquisition, refresh, release, takeover, and stale recovery is one `transport`-section transaction through the shared `runtime/state.json.transaction` guard (see [Consolidated Runtime Root](#consolidated-runtime-root)); it fences stale recovery and delayed release against replacement-owner ABA and fails closed on malformed state, unverifiable ownership, contention timeout, or unsupported filesystem behavior. Publication uses private staging below `runtime/` and atomic rename.
365
+ - Exact ownership remains checked every second and re-confirmed every two seconds by lock-free reads; the leader writes no heartbeat, and a live owner is replaceable only by dead-PID or bus liveness proof (see [Leader Election](./multi-instance-bus.md)). Every acquisition, entry migration, release, takeover, and stale recovery is one `transport`-section transaction through the shared `runtime/state.json.transaction` guard (see [Consolidated Runtime Root](#consolidated-runtime-root)); it fences stale recovery and delayed release against replacement-owner ABA and fails closed on malformed state, unverifiable ownership, contention timeout, or unsupported filesystem behavior. Publication uses private staging below `runtime/` and atomic rename.
366
366
  - `state.json` is authoritative and private: `transport` (ownership), `workspace` (canonical Workspace/Restore evidence) and `admission` per profile. The non-canonical runtime/roster/diagnostics projection is the `runtime` section, published only by the transport owner without touching canonical sections; it and `logs.jsonl` never grant routing authority. The leader reads optional follower slot hints from it; a missing or malformed projection is ignored. Followers remain authenticated bus registrations rather than transport/Workspace/runtime writers; they may publish their process-owned admission leases.
367
367
  - Ordinary ownership and state mutations fail closed on a malformed or foreign `state.json`; read-only ownership queries report no owner, not proof of process absence. The legacy whole-file reset is not wired for the shared envelope. A non-election leader start performs the [damaged-state reset](#damaged-state-reset-operator-approved); section-scoped recovery is not planned. Startup best-effort removes obsolete current-runtime root/session `recovery/` directories, never the pre-0.52.0 tree.
368
368
 
@@ -390,7 +390,7 @@ This bounded structure design is applied locally: Workspace identity has a reusa
390
390
 
391
391
  **Kernel (Locks).** `readTelegramRuntimeState` and `mutateTelegramRuntimeStateSection` own physical authority. Reads neither create nor repair files and refuse unsupported/malformed envelopes, non-private/non-regular sources and changed physical observations; missing state is an empty envelope only when the initial observation positively reports absence. A publisher acquires one shared `runtime/<state basename>.transaction`, reads the latest envelope, supplies detached current-section/sibling observations to one synchronous reducer and replaces only its named profile/section; semantic no-ops keep file identity. Authority is rechecked before mutation, after the reducer, before writing, immediately before atomic rename and before a positive acknowledgement. Nested transactions for the same path are refused. A lost rename reply or post-publication authority loss returns `publication-unknown`; the committed fact remains and never grants replay or rollback.
392
392
 
393
- **Transport.** `createTelegramLockRuntime({ statePath })` keeps acquire/election/expected-owner/heartbeat/release semantics in its profile's `transport` section, preserving sibling fields and the polling pointer through release and succession. Read-only ownership queries fail closed (no owner) on unreadable state so unrelated Pi hooks never crash; acquisition/publication still validate and refuse without replacing it. `publishStateSectionIfOwned` publishes only Workspace or runtime data under exact retained owner and caller authority with an optional `expectedScope`; it never publishes transport/admission or borrows a surrounding `commitIfOwned` grant.
393
+ **Transport.** `createTelegramLockRuntime({ statePath })` keeps acquire/election/expected-owner/unresponsive-owner/release semantics in its profile's `transport` section, preserving sibling fields and the polling pointer through release and succession. Read-only ownership queries fail closed (no owner) on unreadable state so unrelated Pi hooks never crash; acquisition/publication still validate and refuse without replacing it. `publishStateSectionIfOwned` publishes only Workspace or runtime data under exact retained owner and caller authority with an optional `expectedScope`; it never publishes transport/admission or borrows a surrounding `commitIfOwned` grant.
394
394
 
395
395
  **Session-bound grant.** `Locks.createTelegramOwnedStateAuthorityCapture(lock, session)` captures the exact current Pi context, session generation and owned leader epoch before the caller's first await through a structural session port (Locks does not import Lifecycle). The grant stays current only while that context/generation is current, the lock is owned for that context and the epoch is unchanged. Session replacement, same-context restart, clear, release and release-then-reacquire (a new epoch, as disconnect/connect does) revoke it; a successor captures a fresh grant and never renews the old one. Through the real Workspace store, a persist captured before a new session refuses without changing bytes while the successor publishes; an epoch-only capture would have committed that stale write.
396
396
 
@@ -549,7 +549,7 @@ Queued semantic authority has no elapsed-time lease. A timeout cannot prove eith
549
549
 
550
550
  The initial `offset: -1` cursor bootstrap is allowed only when both cursor and journal are absent or empty. Thereafter process-level ordering is journal atomic rename → one monotonic offset atomic rename → worker signal. Failure before journal publication leaves the offset unchanged; failure after journal publication but before offset publication permits Telegram redelivery and journal dedupe; failure after offset publication but before worker signal replays from the journal on restart. Queue-owner, retry, terminal, handoff, and completion transitions use the same journal publication primitive and therefore share this process-crash boundary. The final completion window is at-least-once, so replay-sensitive external effects must use `update_id` or the stable delivery id as an idempotency key.
551
551
 
552
- Threaded Mode forwarding is a two-journal handoff. Only peers that mutually advertise protocol v1 and `durable-follower-admission-v1` may route or become election-eligible. The follower validates its exact binding and registration generation, durably appends the source-bound delivery, and only then returns the exact receipt. The leader classifies each attempt as `accepted`, `retryable`, or `terminal-rejected` with its delivery identity and failure class; only `accepted` with the expected `deliveryId` and `sourceUpdateId` may complete leader journal authority. Missing, negative, stale-generation, or mismatched-receipt acknowledgements remain durable, and a callback error answer is only an operator-facing side effect. The follower's bounded [delivery replay window](./multi-instance-bus.md) acknowledges a repeated `deliveryId` without re-execution.
552
+ Threaded Mode forwarding is a two-journal handoff. Only peers that share the current base protocol and mutually advertise `durable-follower-admission-v1` may route or become election-eligible. The follower validates its exact binding and registration generation, durably appends the source-bound delivery, and only then returns the exact receipt. The leader classifies each attempt as `accepted`, `retryable`, or `terminal-rejected` with its delivery identity and failure class; only `accepted` with the expected `deliveryId` and `sourceUpdateId` may complete leader journal authority. Missing, negative, stale-generation, or mismatched-receipt acknowledgements remain durable, and a callback error answer is only an operator-facing side effect. The follower's bounded [delivery replay window](./multi-instance-bus.md) acknowledges a repeated `deliveryId` without re-execution.
553
553
 
554
554
  Delivery ids derive only from envelope kind, source `update_id`, and stable recipient binding. Live registration generation remains a separate attempt fence. Message ownership carries the stable binding and rebinds to its current authenticated follower registration after replacement. `ownership.getForwardOwnership()` projects the protocol identity from the exact matching live instance, registration generation, and binding on every lookup; protocol is not cached with message history. The same port serves messages, edits, callbacks, and reactions, including callbacks without a Thread ID. If a cache lookup returns a foreign record but no matching durable-capable protocol can be projected, that record remains foreign and fails forwarding validation rather than falling through to local execution. The final bus validator independently rechecks generation, binding, and complete protocol identity before transport. Lost acknowledgements therefore retain the same delivery identity; only the exact durable receipt completes the source. Package build skew is allowed only while protocol version and capabilities remain compatible.
555
555