@llblab/pi-telegram 0.52.2 → 0.53.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.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +65 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +252 -127
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +128 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +516 -301
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
@@ -8,49 +8,86 @@ import { createHash, randomUUID } from "node:crypto";
8
8
  import { chmodSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
9
9
  import { dirname } from "node:path";
10
10
  import { isDeepStrictEqual } from "node:util";
11
- import { getTelegramProcessLiveness, } from "./bus.js";
12
- import { renameTelegramPathWithRetry, withTelegramFileTransaction, readTelegramRuntimeState, mutateTelegramRuntimeStateSection, TelegramRuntimeStateError, } from "./locks.js";
13
- import { areTelegramTargetsEqual as areTargetsEqual } from "./target.js";
14
- import { isWireRecord as isRecord, isNonNegativeWireInteger as isSafeTimestamp } from "./wire.js";
11
+ import { mutateTelegramRuntimeStateSection, readTelegramRuntimeState, renameTelegramPathWithRetry, TelegramRuntimeStateError, withTelegramFileTransaction, } from "./locks.js";
12
+ import { getTelegramProcessLiveness, } from "./process-identity.js";
13
+ import { areTelegramTargetsEqual as areTargetsEqual, } from "./target.js";
14
+ import { isWireRecord as isRecord, isNonNegativeWireInteger as isSafeTimestamp, } from "./wire.js";
15
15
  const TELEGRAM_WORKSPACE_ADMISSION_VERSION = 1;
16
16
  const TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES = 4096;
17
17
  const TELEGRAM_WORKSPACE_ADMISSION_MAX_TEXT = 512;
18
18
  const activeTelegramWorkspaceAdmissionOperationIds = new Set();
19
19
  export function normalizeTelegramWorkspaceJournalWriterClosureFence(value, profileKey) {
20
- if (!isRecord(value) || value.destructiveKind !== "journal-writer-closure" ||
21
- value.phase !== "fenced" || !isBoundedText(value.operationId) ||
22
- value.profileKey !== profileKey || !isBoundedText(value.recoveryKey) ||
23
- !isSafeTimestamp(value.requestedAtMs) || !isSafeTimestamp(value.acquiredAtMs))
20
+ if (!isRecord(value) ||
21
+ value.destructiveKind !== "journal-writer-closure" ||
22
+ value.phase !== "fenced" ||
23
+ !isBoundedText(value.operationId) ||
24
+ value.profileKey !== profileKey ||
25
+ !isBoundedText(value.recoveryKey) ||
26
+ !isSafeTimestamp(value.requestedAtMs) ||
27
+ !isSafeTimestamp(value.acquiredAtMs))
24
28
  return undefined;
25
29
  const owner = normalizeOwner(value.owner);
26
- if (!owner || Object.keys(value).some(key => ![
27
- "destructiveKind", "phase", "operationId", "profileKey", "recoveryKey",
28
- "owner", "requestedAtMs", "acquiredAtMs",
29
- ].includes(key)))
30
+ if (!owner ||
31
+ Object.keys(value).some((key) => ![
32
+ "destructiveKind",
33
+ "phase",
34
+ "operationId",
35
+ "profileKey",
36
+ "recoveryKey",
37
+ "owner",
38
+ "requestedAtMs",
39
+ "acquiredAtMs",
40
+ ].includes(key)))
30
41
  return undefined;
31
- return { destructiveKind: "journal-writer-closure", phase: "fenced",
32
- operationId: value.operationId, profileKey, recoveryKey: value.recoveryKey,
33
- owner, requestedAtMs: value.requestedAtMs, acquiredAtMs: value.acquiredAtMs };
42
+ return {
43
+ destructiveKind: "journal-writer-closure",
44
+ phase: "fenced",
45
+ operationId: value.operationId,
46
+ profileKey,
47
+ recoveryKey: value.recoveryKey,
48
+ owner,
49
+ requestedAtMs: value.requestedAtMs,
50
+ acquiredAtMs: value.acquiredAtMs,
51
+ };
34
52
  }
35
53
  export function normalizeTelegramWorkspaceJournalWriterProtocolMode(value, profileKey) {
36
54
  if (!isRecord(value))
37
55
  return undefined;
38
- const keys = ["version", "protocol", "profileKey", "recoveryKey", "startupAuthorityId",
39
- "closureOperationId", "writerInventorySha256", "installedBy", "installedAtMs"];
56
+ const keys = [
57
+ "version",
58
+ "protocol",
59
+ "profileKey",
60
+ "recoveryKey",
61
+ "startupAuthorityId",
62
+ "closureOperationId",
63
+ "writerInventorySha256",
64
+ "installedBy",
65
+ "installedAtMs",
66
+ ];
40
67
  const installedBy = normalizeOwner(value.installedBy);
41
- if (Object.keys(value).some(key => !keys.includes(key)) || value.version !== 1 ||
42
- value.protocol !== "custody-v3" || value.profileKey !== profileKey ||
43
- !isBoundedText(value.recoveryKey) || !isBoundedText(value.startupAuthorityId) ||
68
+ if (Object.keys(value).some((key) => !keys.includes(key)) ||
69
+ value.version !== 1 ||
70
+ value.protocol !== "custody-v3" ||
71
+ value.profileKey !== profileKey ||
72
+ !isBoundedText(value.recoveryKey) ||
73
+ !isBoundedText(value.startupAuthorityId) ||
44
74
  !isBoundedText(value.closureOperationId) ||
45
75
  typeof value.writerInventorySha256 !== "string" ||
46
- !/^[a-f0-9]{64}$/u.test(value.writerInventorySha256) || !installedBy ||
76
+ !/^[a-f0-9]{64}$/u.test(value.writerInventorySha256) ||
77
+ !installedBy ||
47
78
  !isSafeTimestamp(value.installedAtMs))
48
79
  return undefined;
49
- return { version: 1, protocol: "custody-v3", profileKey,
50
- recoveryKey: value.recoveryKey, startupAuthorityId: value.startupAuthorityId,
80
+ return {
81
+ version: 1,
82
+ protocol: "custody-v3",
83
+ profileKey,
84
+ recoveryKey: value.recoveryKey,
85
+ startupAuthorityId: value.startupAuthorityId,
51
86
  closureOperationId: value.closureOperationId,
52
87
  writerInventorySha256: value.writerInventorySha256,
53
- installedBy, installedAtMs: value.installedAtMs };
88
+ installedBy,
89
+ installedAtMs: value.installedAtMs,
90
+ };
54
91
  }
55
92
  export function isTelegramWorkspaceRetirementFence(fence) {
56
93
  return fence.destructiveKind !== "journal-writer-closure";
@@ -166,11 +203,14 @@ function normalizeFence(value, profileKey) {
166
203
  !isSafeTimestamp(value.acquiredAtMs)) {
167
204
  return undefined;
168
205
  }
169
- if (value.destructiveKind !== undefined && value.destructiveKind !== "pressure-retirement" &&
206
+ if (value.destructiveKind !== undefined &&
207
+ value.destructiveKind !== "pressure-retirement" &&
170
208
  value.destructiveKind !== "manual-thread-cleanup")
171
209
  return undefined;
172
210
  const base = {
173
- ...(value.destructiveKind === undefined ? {} : { destructiveKind: value.destructiveKind }),
211
+ ...(value.destructiveKind === undefined
212
+ ? {}
213
+ : { destructiveKind: value.destructiveKind }),
174
214
  operationId: value.operationId,
175
215
  retirementIntentId: value.retirementIntentId,
176
216
  profileKey,
@@ -193,11 +233,20 @@ function normalizeFence(value, profileKey) {
193
233
  };
194
234
  }
195
235
  if (value.phase === "deletion-rejected" &&
196
- (value.destructiveKind === undefined || value.destructiveKind === "pressure-retirement") &&
197
- isSafeTimestamp(value.deletionIssuedAtMs) && isSafeTimestamp(value.rejectionConfirmedAtMs)) {
236
+ (value.destructiveKind === undefined ||
237
+ value.destructiveKind === "pressure-retirement") &&
238
+ isSafeTimestamp(value.deletionIssuedAtMs) &&
239
+ isSafeTimestamp(value.rejectionConfirmedAtMs)) {
198
240
  const { destructiveKind: _kind, ...pressureBase } = base;
199
- return { ...pressureBase, ...(value.destructiveKind === "pressure-retirement" ? { destructiveKind: "pressure-retirement" } : {}),
200
- phase: "deletion-rejected", deletionIssuedAtMs: value.deletionIssuedAtMs, rejectionConfirmedAtMs: value.rejectionConfirmedAtMs };
241
+ return {
242
+ ...pressureBase,
243
+ ...(value.destructiveKind === "pressure-retirement"
244
+ ? { destructiveKind: "pressure-retirement" }
245
+ : {}),
246
+ phase: "deletion-rejected",
247
+ deletionIssuedAtMs: value.deletionIssuedAtMs,
248
+ rejectionConfirmedAtMs: value.rejectionConfirmedAtMs,
249
+ };
201
250
  }
202
251
  if (value.phase === "commit-ready" &&
203
252
  isSafeTimestamp(value.absenceConfirmedAtMs) &&
@@ -220,7 +269,11 @@ function cloneScope(scope) {
220
269
  : { ...scope };
221
270
  }
222
271
  function cloneLease(lease) {
223
- return { ...lease, scope: cloneScope(lease.scope), owner: { ...lease.owner } };
272
+ return {
273
+ ...lease,
274
+ scope: cloneScope(lease.scope),
275
+ owner: { ...lease.owner },
276
+ };
224
277
  }
225
278
  export function resolveTelegramWorkspaceDestructiveFenceKind(fence) {
226
279
  return fence.destructiveKind ?? "pressure-retirement";
@@ -259,8 +312,14 @@ function isConsolidatedAdmissionKey(value, profile) {
259
312
  return false;
260
313
  try {
261
314
  const key = JSON.parse(value);
262
- return isRecord(key) && Object.keys(key).length === 3 && key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION && key.profile === profile &&
263
- isRecord(key.bot) && Object.keys(key.bot).length === 1 && typeof key.bot.tokenSha256 === "string" && /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256);
315
+ return (isRecord(key) &&
316
+ Object.keys(key).length === 3 &&
317
+ key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION &&
318
+ key.profile === profile &&
319
+ isRecord(key.bot) &&
320
+ Object.keys(key.bot).length === 1 &&
321
+ typeof key.bot.tokenSha256 === "string" &&
322
+ /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256));
264
323
  }
265
324
  catch {
266
325
  return false;
@@ -270,15 +329,28 @@ function isConsolidatedAdmissionKey(value, profile) {
270
329
  export function assertTelegramConsolidatedAdmissionSection(value, stateProfile) {
271
330
  if (value === undefined)
272
331
  return;
273
- normalizeAdmissionState(value, isRecord(value) && typeof value.profileKey === "string" ? value.profileKey : "", stateProfile);
332
+ normalizeAdmissionState(value, isRecord(value) && typeof value.profileKey === "string"
333
+ ? value.profileKey
334
+ : "", stateProfile);
274
335
  }
275
336
  function normalizeAdmissionState(parsed, profileKey, stateProfile) {
276
337
  if (parsed === undefined)
277
- return { version: TELEGRAM_WORKSPACE_ADMISSION_VERSION, profileKey, leases: [] };
338
+ return {
339
+ version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
340
+ profileKey,
341
+ leases: [],
342
+ };
278
343
  if (!isRecord(parsed))
279
344
  invalidState("Telegram Workspace admission state is invalid.");
280
- if (stateProfile !== undefined && (Object.keys(parsed).some(key => !["version", "profileKey", "leases", "fence", "writerProtocolMode"].includes(key)) ||
281
- !isConsolidatedAdmissionKey(parsed.profileKey, stateProfile)))
345
+ if (stateProfile !== undefined &&
346
+ (Object.keys(parsed).some((key) => ![
347
+ "version",
348
+ "profileKey",
349
+ "leases",
350
+ "fence",
351
+ "writerProtocolMode",
352
+ ].includes(key)) ||
353
+ !isConsolidatedAdmissionKey(parsed.profileKey, stateProfile)))
282
354
  invalidState("Telegram consolidated admission section identity is invalid.");
283
355
  if (parsed.version !== TELEGRAM_WORKSPACE_ADMISSION_VERSION ||
284
356
  !Array.isArray(parsed.leases) ||
@@ -286,7 +358,8 @@ function normalizeAdmissionState(parsed, profileKey, stateProfile) {
286
358
  invalidState("Telegram Workspace admission state authority is invalid.");
287
359
  }
288
360
  if (parsed.profileKey !== profileKey) {
289
- if (parsed.leases.length === 0 && parsed.fence === undefined &&
361
+ if (parsed.leases.length === 0 &&
362
+ parsed.fence === undefined &&
290
363
  parsed.writerProtocolMode === undefined) {
291
364
  return {
292
365
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
@@ -301,21 +374,27 @@ function normalizeAdmissionState(parsed, profileKey, stateProfile) {
301
374
  invalidState("Telegram Workspace admission lease is invalid.");
302
375
  }
303
376
  const normalizedLeases = leases;
304
- if (new Set(normalizedLeases.map((lease) => lease.operationId)).size !== normalizedLeases.length) {
377
+ if (new Set(normalizedLeases.map((lease) => lease.operationId)).size !==
378
+ normalizedLeases.length) {
305
379
  invalidState("Telegram Workspace admission lease identity is duplicated.");
306
380
  }
307
- const fence = parsed.fence === undefined ? undefined : normalizeFence(parsed.fence, profileKey);
381
+ const fence = parsed.fence === undefined
382
+ ? undefined
383
+ : normalizeFence(parsed.fence, profileKey);
308
384
  if (parsed.fence !== undefined && !fence) {
309
385
  invalidState("Telegram Workspace retirement fence is invalid.");
310
386
  }
311
- const writerProtocolMode = parsed.writerProtocolMode === undefined ? undefined :
312
- normalizeTelegramWorkspaceJournalWriterProtocolMode(parsed.writerProtocolMode, profileKey);
387
+ const writerProtocolMode = parsed.writerProtocolMode === undefined
388
+ ? undefined
389
+ : normalizeTelegramWorkspaceJournalWriterProtocolMode(parsed.writerProtocolMode, profileKey);
313
390
  if (parsed.writerProtocolMode !== undefined && !writerProtocolMode)
314
391
  invalidState("Telegram Workspace writer protocol mode is invalid.");
315
392
  if (fence && writerProtocolMode)
316
393
  invalidState("Telegram Workspace destructive fence conflicts with writer protocol mode.");
317
- if (stateProfile !== undefined && (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
318
- !isDeepStrictEqual(parsed.fence, fence) || !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode)))
394
+ if (stateProfile !== undefined &&
395
+ (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
396
+ !isDeepStrictEqual(parsed.fence, fence) ||
397
+ !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode)))
319
398
  invalidState("Telegram consolidated admission evidence cannot drop unknown fields.");
320
399
  return {
321
400
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
@@ -403,30 +482,35 @@ function areFenceBasesEqual(left, right) {
403
482
  left.acquiredAtMs === right.acquiredAtMs);
404
483
  }
405
484
  function areWriterProtocolModesEqual(left, right) {
406
- return left.protocol === right.protocol && left.profileKey === right.profileKey &&
485
+ return (left.protocol === right.protocol &&
486
+ left.profileKey === right.profileKey &&
407
487
  left.recoveryKey === right.recoveryKey &&
408
488
  left.startupAuthorityId === right.startupAuthorityId &&
409
489
  left.closureOperationId === right.closureOperationId &&
410
490
  left.writerInventorySha256 === right.writerInventorySha256 &&
411
- areOwnersEqual(left.installedBy, right.installedBy) && left.installedAtMs === right.installedAtMs;
491
+ areOwnersEqual(left.installedBy, right.installedBy) &&
492
+ left.installedAtMs === right.installedAtMs);
412
493
  }
413
494
  function areWriterClosureFencesEqual(left, right) {
414
- return left.operationId === right.operationId && left.profileKey === right.profileKey &&
415
- left.recoveryKey === right.recoveryKey && areOwnersEqual(left.owner, right.owner) &&
416
- left.requestedAtMs === right.requestedAtMs && left.acquiredAtMs === right.acquiredAtMs;
495
+ return (left.operationId === right.operationId &&
496
+ left.profileKey === right.profileKey &&
497
+ left.recoveryKey === right.recoveryKey &&
498
+ areOwnersEqual(left.owner, right.owner) &&
499
+ left.requestedAtMs === right.requestedAtMs &&
500
+ left.acquiredAtMs === right.acquiredAtMs);
417
501
  }
418
502
  function areFencesEqual(left, right) {
419
503
  if (!areFenceBasesEqual(left, right) || left.phase !== right.phase)
420
504
  return false;
421
505
  if (left.phase === "fenced" && right.phase === "fenced")
422
506
  return true;
423
- if (left.phase === "deletion-issued" &&
424
- right.phase === "deletion-issued") {
507
+ if (left.phase === "deletion-issued" && right.phase === "deletion-issued") {
425
508
  return left.deletionIssuedAtMs === right.deletionIssuedAtMs;
426
509
  }
427
- if (left.phase === "deletion-rejected" && right.phase === "deletion-rejected") {
428
- return left.deletionIssuedAtMs === right.deletionIssuedAtMs &&
429
- left.rejectionConfirmedAtMs === right.rejectionConfirmedAtMs;
510
+ if (left.phase === "deletion-rejected" &&
511
+ right.phase === "deletion-rejected") {
512
+ return (left.deletionIssuedAtMs === right.deletionIssuedAtMs &&
513
+ left.rejectionConfirmedAtMs === right.rejectionConfirmedAtMs);
430
514
  }
431
515
  return (left.phase === "commit-ready" &&
432
516
  right.phase === "commit-ready" &&
@@ -443,7 +527,9 @@ function validateOptions(options) {
443
527
  if (!normalizeOwner(options.owner)) {
444
528
  invalidInput("Telegram Workspace admission owner is invalid.");
445
529
  }
446
- if (options.stateProfile !== undefined && (!isBoundedText(options.stateProfile) || !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile)))
530
+ if (options.stateProfile !== undefined &&
531
+ (!isBoundedText(options.stateProfile) ||
532
+ !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile)))
447
533
  invalidInput("Telegram consolidated admission requires an exact bot/profile section identity.");
448
534
  }
449
535
  function validateOperationId(operationId) {
@@ -498,7 +584,9 @@ export function createTelegramWorkspaceAdmissionRuntimeBinding(input) {
498
584
  });
499
585
  return createTelegramWorkspaceAdmissionLedger({
500
586
  path: getStatePath ? getStatePath() : getPath(configuredProfileName),
501
- ...(getStatePath ? { stateProfile: configuredProfileName ?? "default" } : {}),
587
+ ...(getStatePath
588
+ ? { stateProfile: configuredProfileName ?? "default" }
589
+ : {}),
502
590
  profileKey,
503
591
  owner: input.owner,
504
592
  getNowMs: input.getNowMs,
@@ -507,7 +595,8 @@ export function createTelegramWorkspaceAdmissionRuntimeBinding(input) {
507
595
  },
508
596
  };
509
597
  }
510
- function getAdmissionScopeKey(scope) {
598
+ /** Stable admission-scope identity used by leases and admission-gated journal operations. */
599
+ export function getTelegramWorkspaceAdmissionScopeKey(scope) {
511
600
  if (scope.kind === "profile")
512
601
  return "profile";
513
602
  if (scope.kind === "chat")
@@ -523,7 +612,8 @@ function reserveTelegramWorkspaceAdmissionOperationId(operationId) {
523
612
  activeTelegramWorkspaceAdmissionOperationIds.delete(operationId);
524
613
  };
525
614
  }
526
- export function runWithTelegramWorkspaceAdmissions(input) {
615
+ /** Validated, de-duplicated scopes in the stable key order every admitted operation acquires. */
616
+ function normalizeTelegramWorkspaceAdmittedScopes(input) {
527
617
  validateOperationId(input.operationId);
528
618
  if (!isBoundedText(input.operationKind) || input.scopes.length === 0) {
529
619
  invalidInput("Telegram Workspace admitted operation is invalid.");
@@ -531,31 +621,38 @@ export function runWithTelegramWorkspaceAdmissions(input) {
531
621
  const uniqueScopes = new Map();
532
622
  for (const scope of input.scopes) {
533
623
  validateScope(scope);
534
- uniqueScopes.set(getAdmissionScopeKey(scope), cloneScope(scope));
624
+ uniqueScopes.set(getTelegramWorkspaceAdmissionScopeKey(scope), cloneScope(scope));
535
625
  }
536
- const scopes = Array.from(uniqueScopes.entries())
626
+ return Array.from(uniqueScopes.entries())
537
627
  .sort(([left], [right]) => left.localeCompare(right))
538
628
  .map(([, scope]) => scope);
629
+ }
630
+ /** Appends each lease as it is acquired so a blocked or failed scope leaves earlier leases releasable. */
631
+ function acquireTelegramWorkspaceAdmittedScopes(input, scopes, acquired) {
632
+ for (const scope of scopes) {
633
+ const scopeOperationId = `admission:${createHash("sha256")
634
+ .update(input.operationId)
635
+ .update("\0")
636
+ .update(getTelegramWorkspaceAdmissionScopeKey(scope))
637
+ .digest("hex")}`;
638
+ const result = input.ledger.acquireAdmission({
639
+ operationId: scopeOperationId,
640
+ operationKind: input.operationKind,
641
+ scope,
642
+ });
643
+ if (result.kind === "blocked") {
644
+ throw new TelegramWorkspaceAdmissionError("admission-blocked", "Telegram Workspace operation is blocked by retirement.");
645
+ }
646
+ acquired.push(result.lease);
647
+ }
648
+ }
649
+ export function runWithTelegramWorkspaceAdmissions(input) {
650
+ const scopes = normalizeTelegramWorkspaceAdmittedScopes(input);
539
651
  const releaseActiveOperation = reserveTelegramWorkspaceAdmissionOperationId(input.operationId);
540
652
  const acquired = [];
541
653
  let outcome;
542
654
  try {
543
- for (const scope of scopes) {
544
- const scopeOperationId = `admission:${createHash("sha256")
545
- .update(input.operationId)
546
- .update("\0")
547
- .update(getAdmissionScopeKey(scope))
548
- .digest("hex")}`;
549
- const result = input.ledger.acquireAdmission({
550
- operationId: scopeOperationId,
551
- operationKind: input.operationKind,
552
- scope,
553
- });
554
- if (result.kind === "blocked") {
555
- throw new TelegramWorkspaceAdmissionError("admission-blocked", "Telegram Workspace operation is blocked by retirement.");
556
- }
557
- acquired.push(result.lease);
558
- }
655
+ acquireTelegramWorkspaceAdmittedScopes(input, scopes, acquired);
559
656
  outcome = { ok: true, value: input.operation() };
560
657
  }
561
658
  catch (error) {
@@ -584,14 +681,21 @@ export function createTelegramWorkspaceJournalWriterAdmission(input) {
584
681
  return (operation) => {
585
682
  if (active)
586
683
  invalidState("Telegram Workspace journal writer admission is already active.");
587
- const operationId = pendingOperationId ?? input.createOperationId?.() ??
684
+ const operationId = pendingOperationId ??
685
+ input.createOperationId?.() ??
588
686
  `journal-writer:${randomUUID()}`;
589
687
  validateOperationId(operationId);
590
688
  pendingOperationId = operationId;
591
689
  const result = input.protocolAuthority
592
- ? input.ledger.acquireJournalWriterAdmission({ operationId, ...input.protocolAuthority })
593
- : input.ledger.acquireAdmission({ operationId,
594
- operationKind: "journal-write", scope: { kind: "profile" } });
690
+ ? input.ledger.acquireJournalWriterAdmission({
691
+ operationId,
692
+ ...input.protocolAuthority,
693
+ })
694
+ : input.ledger.acquireAdmission({
695
+ operationId,
696
+ operationKind: "journal-write",
697
+ scope: { kind: "profile" },
698
+ });
595
699
  if (result.kind === "blocked") {
596
700
  throw new TelegramWorkspaceAdmissionError("admission-blocked", "Telegram Workspace journal writer is blocked by destructive closure.");
597
701
  }
@@ -610,24 +714,15 @@ export function createTelegramWorkspaceJournalWriterAdmission(input) {
610
714
  try {
611
715
  input.onReleaseError?.(error);
612
716
  }
613
- catch { /* Diagnostics cannot change the journal outcome. */ }
717
+ catch {
718
+ /* Diagnostics cannot change the journal outcome. */
719
+ }
614
720
  }
615
721
  }
616
722
  };
617
723
  }
618
724
  export async function runWithTelegramWorkspaceAdmissionsAsync(input) {
619
- validateOperationId(input.operationId);
620
- if (!isBoundedText(input.operationKind) || input.scopes.length === 0) {
621
- invalidInput("Telegram Workspace admitted operation is invalid.");
622
- }
623
- const uniqueScopes = new Map();
624
- for (const scope of input.scopes) {
625
- validateScope(scope);
626
- uniqueScopes.set(getAdmissionScopeKey(scope), cloneScope(scope));
627
- }
628
- const scopes = Array.from(uniqueScopes.entries())
629
- .sort(([left], [right]) => left.localeCompare(right))
630
- .map(([, scope]) => scope);
725
+ const scopes = normalizeTelegramWorkspaceAdmittedScopes(input);
631
726
  const releaseActiveOperation = reserveTelegramWorkspaceAdmissionOperationId(input.operationId);
632
727
  const acquired = [];
633
728
  const releaseAll = () => {
@@ -648,22 +743,7 @@ export async function runWithTelegramWorkspaceAdmissionsAsync(input) {
648
743
  }
649
744
  };
650
745
  try {
651
- for (const scope of scopes) {
652
- const scopeOperationId = `admission:${createHash("sha256")
653
- .update(input.operationId)
654
- .update("\0")
655
- .update(getAdmissionScopeKey(scope))
656
- .digest("hex")}`;
657
- const result = input.ledger.acquireAdmission({
658
- operationId: scopeOperationId,
659
- operationKind: input.operationKind,
660
- scope,
661
- });
662
- if (result.kind === "blocked") {
663
- throw new TelegramWorkspaceAdmissionError("admission-blocked", "Telegram Workspace operation is blocked by retirement.");
664
- }
665
- acquired.push(result.lease);
666
- }
746
+ acquireTelegramWorkspaceAdmittedScopes(input, scopes, acquired);
667
747
  }
668
748
  catch (error) {
669
749
  releaseAll();
@@ -688,9 +768,14 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
688
768
  ((owner) => getTelegramProcessLiveness(owner));
689
769
  const transactionPath = `${options.path}.transaction`;
690
770
  const path = options.path, profileKey = options.profileKey, stateProfile = options.stateProfile, boundOwner = { ...options.owner };
691
- const publication = { publishRename: options.publishRename, onPublicationBoundary: options.onPublicationBoundary };
692
- const isStorageCurrent = () => suppliedOptions.path === path && suppliedOptions.profileKey === profileKey &&
693
- suppliedOptions.stateProfile === stateProfile && areOwnersEqual(suppliedOptions.owner, boundOwner);
771
+ const publication = {
772
+ publishRename: options.publishRename,
773
+ onPublicationBoundary: options.onPublicationBoundary,
774
+ };
775
+ const isStorageCurrent = () => suppliedOptions.path === path &&
776
+ suppliedOptions.profileKey === profileKey &&
777
+ suppliedOptions.stateProfile === stateProfile &&
778
+ areOwnersEqual(suppliedOptions.owner, boundOwner);
694
779
  const mapStateError = (error) => {
695
780
  if (error instanceof TelegramRuntimeStateError)
696
781
  throw new TelegramWorkspaceAdmissionError(error.code === "invalid" ? "invalid-state" : error.code, "Telegram consolidated admission state operation failed.", { cause: error });
@@ -699,17 +784,26 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
699
784
  function transact(operation) {
700
785
  if (stateProfile !== undefined) {
701
786
  try {
702
- return mutateTelegramRuntimeStateSection(path, stateProfile, "admission", current => {
787
+ return mutateTelegramRuntimeStateSection(path, stateProfile, "admission", (current) => {
703
788
  const state = normalizeAdmissionState(current, profileKey, stateProfile);
704
- const retained = state.leases.filter(lease => getProcessLiveness(lease.owner) !== "dead");
789
+ const retained = state.leases.filter((lease) => getProcessLiveness(lease.owner) !== "dead");
705
790
  const pruned = retained.length !== state.leases.length;
706
791
  state.leases = retained;
707
792
  const outcome = operation(state);
708
- return { value: pruned || outcome.changed ? normalizeAdmissionState(state, profileKey, stateProfile) : current, result: outcome.result };
709
- }, { isCurrent: isStorageCurrent, publishRename: publication.publishRename, onPublicationBoundary(boundary) {
793
+ return {
794
+ value: pruned || outcome.changed
795
+ ? normalizeAdmissionState(state, profileKey, stateProfile)
796
+ : current,
797
+ result: outcome.result,
798
+ };
799
+ }, {
800
+ isCurrent: isStorageCurrent,
801
+ publishRename: publication.publishRename,
802
+ onPublicationBoundary(boundary) {
710
803
  if (boundary !== "after-rename")
711
804
  publication.onPublicationBoundary?.(boundary, path);
712
- } });
805
+ },
806
+ });
713
807
  }
714
808
  catch (error) {
715
809
  return mapStateError(error);
@@ -734,7 +828,9 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
734
828
  authorityChanged("Telegram consolidated admission storage authority changed.");
735
829
  try {
736
830
  const file = readTelegramRuntimeState(path);
737
- const value = Object.hasOwn(file.profiles, stateProfile) ? file.profiles[stateProfile]?.admission : undefined;
831
+ const value = Object.hasOwn(file.profiles, stateProfile)
832
+ ? file.profiles[stateProfile]?.admission
833
+ : undefined;
738
834
  state = normalizeAdmissionState(value, profileKey, stateProfile);
739
835
  if (!isStorageCurrent())
740
836
  authorityChanged("Telegram consolidated admission storage authority changed.");
@@ -749,9 +845,14 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
749
845
  profileKey: state.profileKey,
750
846
  leases: state.leases.map(cloneLease),
751
847
  ...(state.fence ? { fence: cloneFence(state.fence) } : {}),
752
- ...(state.writerProtocolMode ? { writerProtocolMode: {
753
- ...state.writerProtocolMode, installedBy: { ...state.writerProtocolMode.installedBy },
754
- } } : {}),
848
+ ...(state.writerProtocolMode
849
+ ? {
850
+ writerProtocolMode: {
851
+ ...state.writerProtocolMode,
852
+ installedBy: { ...state.writerProtocolMode.installedBy },
853
+ },
854
+ }
855
+ : {}),
755
856
  };
756
857
  }
757
858
  function acquireAdmission(input) {
@@ -763,7 +864,8 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
763
864
  return transact((state) => {
764
865
  const journalOperation = input.operationKind === "journal-write" ||
765
866
  input.operationKind.startsWith("journal.");
766
- const protocolWriterHeld = state.leases.some(lease => lease.operationKind === "journal-write:custody-v3" && lease.scope.kind === "profile" &&
867
+ const protocolWriterHeld = state.leases.some((lease) => lease.operationKind === "journal-write:custody-v3" &&
868
+ lease.scope.kind === "profile" &&
767
869
  areOwnersEqual(lease.owner, options.owner));
768
870
  if (state.writerProtocolMode && journalOperation && !protocolWriterHeld)
769
871
  authorityChanged("Telegram Workspace journal writer requires protocol authority.");
@@ -781,12 +883,17 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
781
883
  authorityChanged("Telegram Workspace admission operation identity changed.");
782
884
  }
783
885
  return {
784
- result: { kind: "acquired", lease: cloneLease(existing), resumed: true },
886
+ result: {
887
+ kind: "acquired",
888
+ lease: cloneLease(existing),
889
+ resumed: true,
890
+ },
785
891
  changed: false,
786
892
  };
787
893
  }
788
- if (state.fence && (!isTelegramWorkspaceRetirementFence(state.fence) ||
789
- scopesConflict(input.scope, state.fence.target))) {
894
+ if (state.fence &&
895
+ (!isTelegramWorkspaceRetirementFence(state.fence) ||
896
+ scopesConflict(input.scope, state.fence.target))) {
790
897
  return {
791
898
  result: { kind: "blocked", reason: "retirement-fenced" },
792
899
  changed: false,
@@ -830,49 +937,91 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
830
937
  function acquireJournalWriterClosure(input) {
831
938
  validateOperationId(input.operationId);
832
939
  const candidate = normalizeTelegramWorkspaceJournalWriterClosureFence({
833
- destructiveKind: "journal-writer-closure", phase: "fenced", ...input,
834
- profileKey: options.profileKey, owner: options.owner, acquiredAtMs: getNowMs(),
940
+ destructiveKind: "journal-writer-closure",
941
+ phase: "fenced",
942
+ ...input,
943
+ profileKey: options.profileKey,
944
+ owner: options.owner,
945
+ acquiredAtMs: getNowMs(),
835
946
  }, options.profileKey);
836
947
  if (!candidate)
837
948
  invalidInput("Telegram Workspace writer closure input is invalid.");
838
949
  return transact((state) => {
839
950
  if (state.writerProtocolMode) {
840
951
  if (state.leases.length > 0)
841
- return { result: { kind: "blocked", reason: "admission-active" }, changed: false };
952
+ return {
953
+ result: { kind: "blocked", reason: "admission-active" },
954
+ changed: false,
955
+ };
842
956
  let authorized = false;
843
957
  if (state.writerProtocolMode.recoveryKey === candidate.recoveryKey) {
844
958
  try {
845
- authorized = options.authorizeJournalWriterProtocolClosure?.({
846
- mode: { ...state.writerProtocolMode,
847
- installedBy: { ...state.writerProtocolMode.installedBy } },
848
- closure: cloneFence(candidate),
849
- }) === true;
959
+ authorized =
960
+ options.authorizeJournalWriterProtocolClosure?.({
961
+ mode: {
962
+ ...state.writerProtocolMode,
963
+ installedBy: { ...state.writerProtocolMode.installedBy },
964
+ },
965
+ closure: cloneFence(candidate),
966
+ }) === true;
850
967
  }
851
968
  catch {
852
969
  authorized = false;
853
970
  }
854
971
  }
855
972
  if (!authorized)
856
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
973
+ return {
974
+ result: { kind: "blocked", reason: "retirement-active" },
975
+ changed: false,
976
+ };
857
977
  delete state.writerProtocolMode;
858
978
  state.fence = candidate;
859
- return { result: { kind: "acquired", fence: cloneFence(candidate), resumed: false },
860
- changed: true };
979
+ return {
980
+ result: {
981
+ kind: "acquired",
982
+ fence: cloneFence(candidate),
983
+ resumed: false,
984
+ },
985
+ changed: true,
986
+ };
861
987
  }
862
988
  if (state.fence) {
863
989
  if (state.fence.destructiveKind === "journal-writer-closure") {
864
- const resumed = { ...candidate, acquiredAtMs: state.fence.acquiredAtMs };
990
+ const resumed = {
991
+ ...candidate,
992
+ acquiredAtMs: state.fence.acquiredAtMs,
993
+ };
865
994
  if (areWriterClosureFencesEqual(state.fence, resumed))
866
- return { result: { kind: "acquired", fence: cloneFence(state.fence), resumed: true }, changed: false };
995
+ return {
996
+ result: {
997
+ kind: "acquired",
998
+ fence: cloneFence(state.fence),
999
+ resumed: true,
1000
+ },
1001
+ changed: false,
1002
+ };
867
1003
  if (state.fence.operationId === candidate.operationId)
868
1004
  authorityChanged("Telegram Workspace writer closure authority changed.");
869
1005
  }
870
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
1006
+ return {
1007
+ result: { kind: "blocked", reason: "retirement-active" },
1008
+ changed: false,
1009
+ };
871
1010
  }
872
1011
  if (state.leases.length > 0)
873
- return { result: { kind: "blocked", reason: "admission-active" }, changed: false };
1012
+ return {
1013
+ result: { kind: "blocked", reason: "admission-active" },
1014
+ changed: false,
1015
+ };
874
1016
  state.fence = candidate;
875
- return { result: { kind: "acquired", fence: cloneFence(candidate), resumed: false }, changed: true };
1017
+ return {
1018
+ result: {
1019
+ kind: "acquired",
1020
+ fence: cloneFence(candidate),
1021
+ resumed: false,
1022
+ },
1023
+ changed: true,
1024
+ };
876
1025
  });
877
1026
  }
878
1027
  function releaseJournalWriterClosure(expected) {
@@ -895,64 +1044,112 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
895
1044
  const normalizedClosure = normalizeTelegramWorkspaceJournalWriterClosureFence(expected, options.profileKey);
896
1045
  if (!normalizedClosure || !areOwnersEqual(expected.owner, options.owner))
897
1046
  authorityChanged("Telegram Workspace writer protocol installation requires closure owner authority.");
898
- const candidate = normalizeTelegramWorkspaceJournalWriterProtocolMode({ version: 1,
899
- protocol: "custody-v3", profileKey: options.profileKey, recoveryKey: expected.recoveryKey,
900
- startupAuthorityId: input.startupAuthorityId, closureOperationId: expected.operationId,
901
- writerInventorySha256: input.writerInventorySha256, installedBy: options.owner,
902
- installedAtMs: getNowMs() }, options.profileKey);
1047
+ const candidate = normalizeTelegramWorkspaceJournalWriterProtocolMode({
1048
+ version: 1,
1049
+ protocol: "custody-v3",
1050
+ profileKey: options.profileKey,
1051
+ recoveryKey: expected.recoveryKey,
1052
+ startupAuthorityId: input.startupAuthorityId,
1053
+ closureOperationId: expected.operationId,
1054
+ writerInventorySha256: input.writerInventorySha256,
1055
+ installedBy: options.owner,
1056
+ installedAtMs: getNowMs(),
1057
+ }, options.profileKey);
903
1058
  if (!candidate)
904
1059
  invalidInput("Telegram Workspace writer protocol mode input is invalid.");
905
1060
  return transact((state) => {
906
1061
  if (state.writerProtocolMode) {
907
- const resumed = { ...candidate, installedAtMs: state.writerProtocolMode.installedAtMs };
908
- if (areWriterProtocolModesEqual(state.writerProtocolMode, resumed) && !state.fence)
909
- return { result: { mode: { ...state.writerProtocolMode,
910
- installedBy: { ...state.writerProtocolMode.installedBy } }, resumed: true }, changed: false };
1062
+ const resumed = {
1063
+ ...candidate,
1064
+ installedAtMs: state.writerProtocolMode.installedAtMs,
1065
+ };
1066
+ if (areWriterProtocolModesEqual(state.writerProtocolMode, resumed) &&
1067
+ !state.fence)
1068
+ return {
1069
+ result: {
1070
+ mode: {
1071
+ ...state.writerProtocolMode,
1072
+ installedBy: { ...state.writerProtocolMode.installedBy },
1073
+ },
1074
+ resumed: true,
1075
+ },
1076
+ changed: false,
1077
+ };
911
1078
  authorityChanged("Telegram Workspace writer protocol mode authority changed.");
912
1079
  }
913
- if (!state.fence || state.fence.destructiveKind !== "journal-writer-closure" ||
914
- !areWriterClosureFencesEqual(state.fence, expected) || state.leases.length !== 0)
1080
+ if (!state.fence ||
1081
+ state.fence.destructiveKind !== "journal-writer-closure" ||
1082
+ !areWriterClosureFencesEqual(state.fence, expected) ||
1083
+ state.leases.length !== 0)
915
1084
  authorityChanged("Telegram Workspace writer closure authority changed before protocol installation.");
916
1085
  state.writerProtocolMode = candidate;
917
1086
  delete state.fence;
918
- return { result: { mode: { ...candidate, installedBy: { ...candidate.installedBy } },
919
- resumed: false }, changed: true };
1087
+ return {
1088
+ result: {
1089
+ mode: { ...candidate, installedBy: { ...candidate.installedBy } },
1090
+ resumed: false,
1091
+ },
1092
+ changed: true,
1093
+ };
920
1094
  });
921
1095
  }
922
1096
  function acquireJournalWriterAdmission(input) {
923
1097
  validateOperationId(input.operationId);
924
- if (!isBoundedText(input.recoveryKey) || !isBoundedText(input.startupAuthorityId) ||
925
- !isBoundedText(input.closureOperationId) || !/^[a-f0-9]{64}$/u.test(input.writerInventorySha256))
1098
+ if (!isBoundedText(input.recoveryKey) ||
1099
+ !isBoundedText(input.startupAuthorityId) ||
1100
+ !isBoundedText(input.closureOperationId) ||
1101
+ !/^[a-f0-9]{64}$/u.test(input.writerInventorySha256))
926
1102
  invalidInput("Telegram Workspace journal writer protocol authority is invalid.");
927
1103
  return transact((state) => {
928
1104
  const mode = state.writerProtocolMode;
929
- if (!mode || mode.recoveryKey !== input.recoveryKey ||
1105
+ if (!mode ||
1106
+ mode.recoveryKey !== input.recoveryKey ||
930
1107
  mode.startupAuthorityId !== input.startupAuthorityId ||
931
1108
  mode.closureOperationId !== input.closureOperationId ||
932
1109
  mode.writerInventorySha256 !== input.writerInventorySha256)
933
1110
  authorityChanged("Telegram Workspace journal writer protocol authority changed.");
934
- const existing = state.leases.find(lease => lease.operationId === input.operationId);
1111
+ const existing = state.leases.find((lease) => lease.operationId === input.operationId);
935
1112
  const operationKind = "journal-write:custody-v3";
936
1113
  if (existing) {
937
- const retried = { operationId: input.operationId,
938
- operationKind, profileKey: options.profileKey, scope: { kind: "profile" },
939
- owner: { ...options.owner }, acquiredAtMs: existing.acquiredAtMs };
1114
+ const retried = {
1115
+ operationId: input.operationId,
1116
+ operationKind,
1117
+ profileKey: options.profileKey,
1118
+ scope: { kind: "profile" },
1119
+ owner: { ...options.owner },
1120
+ acquiredAtMs: existing.acquiredAtMs,
1121
+ };
940
1122
  if (!areLeasesEqual(existing, retried))
941
1123
  authorityChanged("Telegram Workspace journal writer operation identity changed.");
942
- return { result: { kind: "acquired", lease: cloneLease(existing), resumed: true },
943
- changed: false };
1124
+ return {
1125
+ result: {
1126
+ kind: "acquired",
1127
+ lease: cloneLease(existing),
1128
+ resumed: true,
1129
+ },
1130
+ changed: false,
1131
+ };
944
1132
  }
945
1133
  if (state.fence)
946
- return { result: { kind: "blocked", reason: "retirement-fenced" },
947
- changed: false };
1134
+ return {
1135
+ result: { kind: "blocked", reason: "retirement-fenced" },
1136
+ changed: false,
1137
+ };
948
1138
  if (state.leases.length >= TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES)
949
1139
  invalidState("Telegram Workspace admission lease capacity is exhausted.");
950
- const lease = { operationId: input.operationId,
951
- operationKind, profileKey: options.profileKey, scope: { kind: "profile" },
952
- owner: { ...options.owner }, acquiredAtMs: getNowMs() };
1140
+ const lease = {
1141
+ operationId: input.operationId,
1142
+ operationKind,
1143
+ profileKey: options.profileKey,
1144
+ scope: { kind: "profile" },
1145
+ owner: { ...options.owner },
1146
+ acquiredAtMs: getNowMs(),
1147
+ };
953
1148
  state.leases.push(lease);
954
- return { result: { kind: "acquired", lease: cloneLease(lease), resumed: false },
955
- changed: true };
1149
+ return {
1150
+ result: { kind: "acquired", lease: cloneLease(lease), resumed: false },
1151
+ changed: true,
1152
+ };
956
1153
  });
957
1154
  }
958
1155
  function acquireDestructiveFence(input, destructiveKind) {
@@ -971,7 +1168,10 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
971
1168
  return transact((state) => {
972
1169
  if (state.fence) {
973
1170
  if (!isTelegramWorkspaceRetirementFence(state.fence)) {
974
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
1171
+ return {
1172
+ result: { kind: "blocked", reason: "retirement-active" },
1173
+ changed: false,
1174
+ };
975
1175
  }
976
1176
  if (state.fence.phase === "fenced" &&
977
1177
  areFenceBasesEqual(state.fence, {
@@ -1003,7 +1203,11 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1003
1203
  }
1004
1204
  state.fence = candidate;
1005
1205
  return {
1006
- result: { kind: "acquired", fence: cloneFence(candidate), resumed: false },
1206
+ result: {
1207
+ kind: "acquired",
1208
+ fence: cloneFence(candidate),
1209
+ resumed: false,
1210
+ },
1007
1211
  changed: true,
1008
1212
  };
1009
1213
  });
@@ -1012,16 +1216,30 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1012
1216
  return acquireDestructiveFence(input);
1013
1217
  }
1014
1218
  function acquireThreadCleanupFence(input) {
1015
- return acquireDestructiveFence({ operationId: input.operationId,
1016
- retirementIntentId: input.cleanupWorkSetId, bindingKey: input.bindingKey,
1017
- slot: input.slot, target: input.target, leaderEpoch: input.leaderEpoch,
1018
- retirementRequestedAtMs: input.cleanupRequestedAtMs }, "manual-thread-cleanup");
1219
+ return acquireDestructiveFence({
1220
+ operationId: input.operationId,
1221
+ retirementIntentId: input.cleanupWorkSetId,
1222
+ bindingKey: input.bindingKey,
1223
+ slot: input.slot,
1224
+ target: input.target,
1225
+ leaderEpoch: input.leaderEpoch,
1226
+ retirementRequestedAtMs: input.cleanupRequestedAtMs,
1227
+ }, "manual-thread-cleanup");
1228
+ }
1229
+ // Exact profile/kind/owner preflight shared by owner-held fence transitions; the transaction still rechecks state.
1230
+ function assertOwnedFence(expected, expectedKind, ownerMessage) {
1231
+ validateExpectedFence(expected, options.profileKey);
1232
+ if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1233
+ authorityChanged("Telegram Workspace destructive fence kind changed.");
1234
+ if (!areOwnersEqual(expected.owner, options.owner))
1235
+ authorityChanged(ownerMessage);
1019
1236
  }
1020
1237
  function adoptRetirementFence(expected, replacement, expectedKind = "pressure-retirement") {
1021
1238
  validateExpectedFence(expected, options.profileKey);
1022
1239
  if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1023
1240
  authorityChanged("Telegram Workspace destructive fence kind changed.");
1024
- if (!normalizeOwner(replacement.owner) || !isLeaderEpoch(replacement.leaderEpoch)) {
1241
+ if (!normalizeOwner(replacement.owner) ||
1242
+ !isLeaderEpoch(replacement.leaderEpoch)) {
1025
1243
  invalidInput("Telegram Workspace retirement successor authority is invalid.");
1026
1244
  }
1027
1245
  if (!areOwnersEqual(replacement.owner, options.owner)) {
@@ -1047,12 +1265,7 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1047
1265
  });
1048
1266
  }
1049
1267
  function issueDeletionPermit(expected, expectedKind = "pressure-retirement") {
1050
- validateExpectedFence(expected, options.profileKey);
1051
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1052
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1053
- if (!areOwnersEqual(expected.owner, options.owner)) {
1054
- authorityChanged("Telegram Workspace deletion permit requires fence owner authority.");
1055
- }
1268
+ assertOwnedFence(expected, expectedKind, "Telegram Workspace deletion permit requires fence owner authority.");
1056
1269
  return transact((state) => {
1057
1270
  if (!state.fence || !isTelegramWorkspaceRetirementFence(state.fence)) {
1058
1271
  authorityChanged("Telegram Workspace retirement fence is absent or has another kind.");
@@ -1086,7 +1299,9 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1086
1299
  kind: "issued",
1087
1300
  fence: cloneFence(issued),
1088
1301
  permit: {
1089
- ...(issued.destructiveKind === undefined ? {} : { destructiveKind: issued.destructiveKind }),
1302
+ ...(issued.destructiveKind === undefined
1303
+ ? {}
1304
+ : { destructiveKind: issued.destructiveKind }),
1090
1305
  operationId: issued.operationId,
1091
1306
  retirementIntentId: issued.retirementIntentId,
1092
1307
  profileKey: issued.profileKey,
@@ -1102,12 +1317,7 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1102
1317
  });
1103
1318
  }
1104
1319
  function confirmRetirementAbsence(expected, expectedKind = "pressure-retirement") {
1105
- validateExpectedFence(expected, options.profileKey);
1106
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1107
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1108
- if (!areOwnersEqual(expected.owner, options.owner)) {
1109
- authorityChanged("Telegram Workspace absence confirmation requires fence owner authority.");
1110
- }
1320
+ assertOwnedFence(expected, expectedKind, "Telegram Workspace absence confirmation requires fence owner authority.");
1111
1321
  if (expected.phase === "deletion-rejected") {
1112
1322
  authorityChanged("Rejected Telegram Workspace deletion cannot confirm absence.");
1113
1323
  }
@@ -1136,12 +1346,7 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1136
1346
  });
1137
1347
  }
1138
1348
  function releaseUnissuedRetirementFence(expected, expectedKind = "pressure-retirement") {
1139
- validateExpectedFence(expected, options.profileKey);
1140
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1141
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1142
- if (!areOwnersEqual(expected.owner, options.owner)) {
1143
- authorityChanged("Telegram Workspace fence release requires owner authority.");
1144
- }
1349
+ assertOwnedFence(expected, expectedKind, "Telegram Workspace fence release requires owner authority.");
1145
1350
  return transact((state) => {
1146
1351
  if (!state.fence)
1147
1352
  return { result: false, changed: false };
@@ -1158,12 +1363,7 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1158
1363
  });
1159
1364
  }
1160
1365
  function completeDestructiveFence(expected, expectedKind, requiredPhase) {
1161
- validateExpectedFence(expected, options.profileKey);
1162
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1163
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1164
- if (!areOwnersEqual(expected.owner, options.owner)) {
1165
- authorityChanged("Telegram Workspace fence completion requires owner authority.");
1166
- }
1366
+ assertOwnedFence(expected, expectedKind, "Telegram Workspace fence completion requires owner authority.");
1167
1367
  return transact((state) => {
1168
1368
  if (!state.fence)
1169
1369
  return { result: false, changed: false };
@@ -1183,23 +1383,33 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1183
1383
  }
1184
1384
  function confirmRetirementRejection(expected) {
1185
1385
  validateExpectedFence(expected, options.profileKey);
1186
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== "pressure-retirement" ||
1386
+ if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !==
1387
+ "pressure-retirement" ||
1187
1388
  !areOwnersEqual(expected.owner, options.owner) ||
1188
- (expected.phase !== "deletion-issued" && expected.phase !== "deletion-rejected")) {
1389
+ (expected.phase !== "deletion-issued" &&
1390
+ expected.phase !== "deletion-rejected")) {
1189
1391
  authorityChanged("Telegram Workspace rejection requires exact issued retirement authority.");
1190
1392
  }
1191
1393
  return transact((state) => {
1192
1394
  const current = state.fence;
1193
- if (!current || !isTelegramWorkspaceRetirementFence(current) || !areFenceBasesEqual(current, expected) ||
1194
- (current.phase !== "deletion-issued" && current.phase !== "deletion-rejected") ||
1395
+ if (!current ||
1396
+ !isTelegramWorkspaceRetirementFence(current) ||
1397
+ !areFenceBasesEqual(current, expected) ||
1398
+ (current.phase !== "deletion-issued" &&
1399
+ current.phase !== "deletion-rejected") ||
1195
1400
  current.deletionIssuedAtMs !== expected.deletionIssuedAtMs ||
1196
- (expected.phase === "deletion-rejected" && !areFencesEqual(current, expected))) {
1401
+ (expected.phase === "deletion-rejected" &&
1402
+ !areFencesEqual(current, expected))) {
1197
1403
  authorityChanged("Telegram Workspace retirement fence authority changed.");
1198
1404
  }
1199
1405
  if (current.phase === "deletion-rejected")
1200
1406
  return { result: cloneFence(current), changed: false };
1201
- const rejected = { ...current,
1202
- destructiveKind: "pressure-retirement", phase: "deletion-rejected", rejectionConfirmedAtMs: getNowMs() };
1407
+ const rejected = {
1408
+ ...current,
1409
+ destructiveKind: "pressure-retirement",
1410
+ phase: "deletion-rejected",
1411
+ rejectionConfirmedAtMs: getNowMs(),
1412
+ };
1203
1413
  state.fence = rejected;
1204
1414
  return { result: cloneFence(rejected), changed: true };
1205
1415
  });
@@ -1227,10 +1437,14 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1227
1437
  }
1228
1438
  return {
1229
1439
  getProfileKey: () => stateProfile === undefined ? options.profileKey : profileKey,
1230
- getOwner: () => ({ ...(stateProfile === undefined ? options.owner : boundOwner) }),
1440
+ getOwner: () => ({
1441
+ ...(stateProfile === undefined ? options.owner : boundOwner),
1442
+ }),
1231
1443
  listReservedSlots: () => {
1232
1444
  const fence = read().fence;
1233
- return fence && isTelegramWorkspaceRetirementFence(fence) ? [fence.slot] : [];
1445
+ return fence && isTelegramWorkspaceRetirementFence(fence)
1446
+ ? [fence.slot]
1447
+ : [];
1234
1448
  },
1235
1449
  read,
1236
1450
  acquireAdmission,