@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
@@ -18,18 +18,24 @@ import { dirname } from "node:path";
18
18
  import { isDeepStrictEqual } from "node:util";
19
19
 
20
20
  import {
21
- getTelegramProcessLiveness,
22
- type TelegramProcessLiveness,
23
- } from "./bus.ts";
24
- import {
25
- renameTelegramPathWithRetry,
26
- withTelegramFileTransaction,
27
- readTelegramRuntimeState,
28
21
  mutateTelegramRuntimeStateSection,
22
+ readTelegramRuntimeState,
23
+ renameTelegramPathWithRetry,
29
24
  TelegramRuntimeStateError,
25
+ withTelegramFileTransaction,
30
26
  } from "./locks.ts";
31
- import { areTelegramTargetsEqual as areTargetsEqual, type TelegramTarget } from "./target.ts";
32
- import { isWireRecord as isRecord, isNonNegativeWireInteger as isSafeTimestamp } from "./wire.ts";
27
+ import {
28
+ getTelegramProcessLiveness,
29
+ type TelegramProcessLiveness,
30
+ } from "./process-identity.ts";
31
+ import {
32
+ areTelegramTargetsEqual as areTargetsEqual,
33
+ type TelegramTarget,
34
+ } from "./target.ts";
35
+ import {
36
+ isWireRecord as isRecord,
37
+ isNonNegativeWireInteger as isSafeTimestamp,
38
+ } from "./wire.ts";
33
39
 
34
40
  const TELEGRAM_WORKSPACE_ADMISSION_VERSION = 1;
35
41
  const TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES = 4096;
@@ -56,12 +62,12 @@ interface TelegramWorkspaceAdmissionLease {
56
62
  }
57
63
 
58
64
  type TelegramWorkspaceDestructiveFenceKind =
59
- | "pressure-retirement"
60
- | "manual-thread-cleanup"
61
- | "journal-writer-closure";
65
+ "pressure-retirement" | "manual-thread-cleanup" | "journal-writer-closure";
62
66
 
63
67
  export type TelegramWorkspaceDeletionFenceKind = Exclude<
64
- TelegramWorkspaceDestructiveFenceKind, "journal-writer-closure">;
68
+ TelegramWorkspaceDestructiveFenceKind,
69
+ "journal-writer-closure"
70
+ >;
65
71
 
66
72
  interface TelegramWorkspaceJournalWriterClosureFence {
67
73
  destructiveKind: "journal-writer-closure";
@@ -78,18 +84,45 @@ export function normalizeTelegramWorkspaceJournalWriterClosureFence(
78
84
  value: unknown,
79
85
  profileKey: string,
80
86
  ): TelegramWorkspaceJournalWriterClosureFence | undefined {
81
- if (!isRecord(value) || value.destructiveKind !== "journal-writer-closure" ||
82
- value.phase !== "fenced" || !isBoundedText(value.operationId) ||
83
- value.profileKey !== profileKey || !isBoundedText(value.recoveryKey) ||
84
- !isSafeTimestamp(value.requestedAtMs) || !isSafeTimestamp(value.acquiredAtMs)) return undefined;
87
+ if (
88
+ !isRecord(value) ||
89
+ value.destructiveKind !== "journal-writer-closure" ||
90
+ value.phase !== "fenced" ||
91
+ !isBoundedText(value.operationId) ||
92
+ value.profileKey !== profileKey ||
93
+ !isBoundedText(value.recoveryKey) ||
94
+ !isSafeTimestamp(value.requestedAtMs) ||
95
+ !isSafeTimestamp(value.acquiredAtMs)
96
+ )
97
+ return undefined;
85
98
  const owner = normalizeOwner(value.owner);
86
- if (!owner || Object.keys(value).some(key => ![
87
- "destructiveKind", "phase", "operationId", "profileKey", "recoveryKey",
88
- "owner", "requestedAtMs", "acquiredAtMs",
89
- ].includes(key))) return undefined;
90
- return { destructiveKind: "journal-writer-closure", phase: "fenced",
91
- operationId: value.operationId, profileKey, recoveryKey: value.recoveryKey,
92
- owner, requestedAtMs: value.requestedAtMs, acquiredAtMs: value.acquiredAtMs };
99
+ if (
100
+ !owner ||
101
+ Object.keys(value).some(
102
+ (key) =>
103
+ ![
104
+ "destructiveKind",
105
+ "phase",
106
+ "operationId",
107
+ "profileKey",
108
+ "recoveryKey",
109
+ "owner",
110
+ "requestedAtMs",
111
+ "acquiredAtMs",
112
+ ].includes(key),
113
+ )
114
+ )
115
+ return undefined;
116
+ return {
117
+ destructiveKind: "journal-writer-closure",
118
+ phase: "fenced",
119
+ operationId: value.operationId,
120
+ profileKey,
121
+ recoveryKey: value.recoveryKey,
122
+ owner,
123
+ requestedAtMs: value.requestedAtMs,
124
+ acquiredAtMs: value.acquiredAtMs,
125
+ };
93
126
  }
94
127
 
95
128
  interface TelegramWorkspaceJournalWriterProtocolMode {
@@ -109,21 +142,43 @@ export function normalizeTelegramWorkspaceJournalWriterProtocolMode(
109
142
  profileKey: string,
110
143
  ): TelegramWorkspaceJournalWriterProtocolMode | undefined {
111
144
  if (!isRecord(value)) return undefined;
112
- const keys = ["version", "protocol", "profileKey", "recoveryKey", "startupAuthorityId",
113
- "closureOperationId", "writerInventorySha256", "installedBy", "installedAtMs"];
145
+ const keys = [
146
+ "version",
147
+ "protocol",
148
+ "profileKey",
149
+ "recoveryKey",
150
+ "startupAuthorityId",
151
+ "closureOperationId",
152
+ "writerInventorySha256",
153
+ "installedBy",
154
+ "installedAtMs",
155
+ ];
114
156
  const installedBy = normalizeOwner(value.installedBy);
115
- if (Object.keys(value).some(key => !keys.includes(key)) || value.version !== 1 ||
116
- value.protocol !== "custody-v3" || value.profileKey !== profileKey ||
117
- !isBoundedText(value.recoveryKey) || !isBoundedText(value.startupAuthorityId) ||
157
+ if (
158
+ Object.keys(value).some((key) => !keys.includes(key)) ||
159
+ value.version !== 1 ||
160
+ value.protocol !== "custody-v3" ||
161
+ value.profileKey !== profileKey ||
162
+ !isBoundedText(value.recoveryKey) ||
163
+ !isBoundedText(value.startupAuthorityId) ||
118
164
  !isBoundedText(value.closureOperationId) ||
119
165
  typeof value.writerInventorySha256 !== "string" ||
120
- !/^[a-f0-9]{64}$/u.test(value.writerInventorySha256) || !installedBy ||
121
- !isSafeTimestamp(value.installedAtMs)) return undefined;
122
- return { version: 1, protocol: "custody-v3", profileKey,
123
- recoveryKey: value.recoveryKey, startupAuthorityId: value.startupAuthorityId,
166
+ !/^[a-f0-9]{64}$/u.test(value.writerInventorySha256) ||
167
+ !installedBy ||
168
+ !isSafeTimestamp(value.installedAtMs)
169
+ )
170
+ return undefined;
171
+ return {
172
+ version: 1,
173
+ protocol: "custody-v3",
174
+ profileKey,
175
+ recoveryKey: value.recoveryKey,
176
+ startupAuthorityId: value.startupAuthorityId,
124
177
  closureOperationId: value.closureOperationId,
125
178
  writerInventorySha256: value.writerInventorySha256,
126
- installedBy, installedAtMs: value.installedAtMs };
179
+ installedBy,
180
+ installedAtMs: value.installedAtMs,
181
+ };
127
182
  }
128
183
 
129
184
  interface TelegramWorkspaceRetirementFenceBase {
@@ -159,8 +214,7 @@ export type TelegramWorkspaceRetirementFence =
159
214
  });
160
215
 
161
216
  export type TelegramWorkspaceDestructiveFence =
162
- | TelegramWorkspaceRetirementFence
163
- | TelegramWorkspaceJournalWriterClosureFence;
217
+ TelegramWorkspaceRetirementFence | TelegramWorkspaceJournalWriterClosureFence;
164
218
 
165
219
  export function isTelegramWorkspaceRetirementFence(
166
220
  fence: TelegramWorkspaceDestructiveFence,
@@ -189,7 +243,11 @@ interface TelegramWorkspaceAdmissionState {
189
243
  }
190
244
 
191
245
  type TelegramWorkspaceAdmissionAcquireResult =
192
- | { kind: "acquired"; lease: TelegramWorkspaceAdmissionLease; resumed: boolean }
246
+ | {
247
+ kind: "acquired";
248
+ lease: TelegramWorkspaceAdmissionLease;
249
+ resumed: boolean;
250
+ }
193
251
  | { kind: "blocked"; reason: "retirement-fenced" };
194
252
 
195
253
  type TelegramWorkspaceRetirementFenceAcquireResult =
@@ -209,7 +267,11 @@ type TelegramWorkspaceJournalWriterProtocolInstallResult = {
209
267
  };
210
268
 
211
269
  type TelegramWorkspaceJournalWriterClosureAcquireResult =
212
- | { kind: "acquired"; fence: TelegramWorkspaceJournalWriterClosureFence; resumed: boolean }
270
+ | {
271
+ kind: "acquired";
272
+ fence: TelegramWorkspaceJournalWriterClosureFence;
273
+ resumed: boolean;
274
+ }
213
275
  | { kind: "blocked"; reason: "admission-active" | "retirement-active" };
214
276
 
215
277
  type TelegramWorkspaceDeletionPermitResult =
@@ -228,8 +290,7 @@ interface TelegramWorkspaceAdmissionLedgerSnapshot {
228
290
  }
229
291
 
230
292
  type TelegramWorkspaceAdmissionPublicationBoundary =
231
- | "before-write"
232
- | "after-write-before-rename";
293
+ "before-write" | "after-write-before-rename";
233
294
 
234
295
  interface TelegramWorkspaceAdmissionLedgerOptions {
235
296
  path: string;
@@ -283,15 +344,25 @@ export interface TelegramWorkspaceAdmissionLedger {
283
344
  scope: TelegramWorkspaceAdmissionScope;
284
345
  }) => TelegramWorkspaceAdmissionAcquireResult;
285
346
  releaseAdmission: (expected: TelegramWorkspaceAdmissionLease) => boolean;
286
- acquireJournalWriterClosure: (input: { operationId: string; recoveryKey: string;
287
- requestedAtMs: number }) => TelegramWorkspaceJournalWriterClosureAcquireResult;
288
- releaseJournalWriterClosure: (expected: TelegramWorkspaceJournalWriterClosureFence) => boolean;
289
- installJournalWriterProtocolMode: (expected: TelegramWorkspaceJournalWriterClosureFence,
290
- input: { startupAuthorityId: string; writerInventorySha256: string }) =>
291
- TelegramWorkspaceJournalWriterProtocolInstallResult;
292
- acquireJournalWriterAdmission: (input: { operationId: string; recoveryKey: string;
293
- startupAuthorityId: string; closureOperationId: string; writerInventorySha256: string }) =>
294
- TelegramWorkspaceAdmissionAcquireResult;
347
+ acquireJournalWriterClosure: (input: {
348
+ operationId: string;
349
+ recoveryKey: string;
350
+ requestedAtMs: number;
351
+ }) => TelegramWorkspaceJournalWriterClosureAcquireResult;
352
+ releaseJournalWriterClosure: (
353
+ expected: TelegramWorkspaceJournalWriterClosureFence,
354
+ ) => boolean;
355
+ installJournalWriterProtocolMode: (
356
+ expected: TelegramWorkspaceJournalWriterClosureFence,
357
+ input: { startupAuthorityId: string; writerInventorySha256: string },
358
+ ) => TelegramWorkspaceJournalWriterProtocolInstallResult;
359
+ acquireJournalWriterAdmission: (input: {
360
+ operationId: string;
361
+ recoveryKey: string;
362
+ startupAuthorityId: string;
363
+ closureOperationId: string;
364
+ writerInventorySha256: string;
365
+ }) => TelegramWorkspaceAdmissionAcquireResult;
295
366
  acquireRetirementFence: (input: {
296
367
  operationId: string;
297
368
  retirementIntentId: string;
@@ -412,7 +483,8 @@ function normalizeScope(
412
483
  if (!isRecord(value)) return undefined;
413
484
  if (value.kind === "profile") return { kind: "profile" };
414
485
  if (value.kind === "chat") {
415
- if (!Number.isSafeInteger(value.chatId) || value.chatId === 0) return undefined;
486
+ if (!Number.isSafeInteger(value.chatId) || value.chatId === 0)
487
+ return undefined;
416
488
  return { kind: "chat", chatId: value.chatId as number };
417
489
  }
418
490
  if (value.kind === "target") {
@@ -455,7 +527,10 @@ function normalizeFence(
455
527
  ): TelegramWorkspaceDestructiveFence | undefined {
456
528
  if (!isRecord(value)) return undefined;
457
529
  if (value.destructiveKind === "journal-writer-closure")
458
- return normalizeTelegramWorkspaceJournalWriterClosureFence(value, profileKey);
530
+ return normalizeTelegramWorkspaceJournalWriterClosureFence(
531
+ value,
532
+ profileKey,
533
+ );
459
534
  const target = normalizeTarget(value.target);
460
535
  const owner = normalizeOwner(value.owner);
461
536
  if (
@@ -473,10 +548,16 @@ function normalizeFence(
473
548
  ) {
474
549
  return undefined;
475
550
  }
476
- if (value.destructiveKind !== undefined && value.destructiveKind !== "pressure-retirement" &&
477
- value.destructiveKind !== "manual-thread-cleanup") return undefined;
551
+ if (
552
+ value.destructiveKind !== undefined &&
553
+ value.destructiveKind !== "pressure-retirement" &&
554
+ value.destructiveKind !== "manual-thread-cleanup"
555
+ )
556
+ return undefined;
478
557
  const base: TelegramWorkspaceRetirementFenceBase = {
479
- ...(value.destructiveKind === undefined ? {} : { destructiveKind: value.destructiveKind }),
558
+ ...(value.destructiveKind === undefined
559
+ ? {}
560
+ : { destructiveKind: value.destructiveKind }),
480
561
  operationId: value.operationId,
481
562
  retirementIntentId: value.retirementIntentId,
482
563
  profileKey,
@@ -499,12 +580,23 @@ function normalizeFence(
499
580
  deletionIssuedAtMs: value.deletionIssuedAtMs,
500
581
  };
501
582
  }
502
- if (value.phase === "deletion-rejected" &&
503
- (value.destructiveKind === undefined || value.destructiveKind === "pressure-retirement") &&
504
- isSafeTimestamp(value.deletionIssuedAtMs) && isSafeTimestamp(value.rejectionConfirmedAtMs)) {
583
+ if (
584
+ value.phase === "deletion-rejected" &&
585
+ (value.destructiveKind === undefined ||
586
+ value.destructiveKind === "pressure-retirement") &&
587
+ isSafeTimestamp(value.deletionIssuedAtMs) &&
588
+ isSafeTimestamp(value.rejectionConfirmedAtMs)
589
+ ) {
505
590
  const { destructiveKind: _kind, ...pressureBase } = base;
506
- return { ...pressureBase, ...(value.destructiveKind === "pressure-retirement" ? { destructiveKind: "pressure-retirement" as const } : {}),
507
- phase: "deletion-rejected", deletionIssuedAtMs: value.deletionIssuedAtMs, rejectionConfirmedAtMs: value.rejectionConfirmedAtMs };
591
+ return {
592
+ ...pressureBase,
593
+ ...(value.destructiveKind === "pressure-retirement"
594
+ ? { destructiveKind: "pressure-retirement" as const }
595
+ : {}),
596
+ phase: "deletion-rejected",
597
+ deletionIssuedAtMs: value.deletionIssuedAtMs,
598
+ rejectionConfirmedAtMs: value.rejectionConfirmedAtMs,
599
+ };
508
600
  }
509
601
  if (
510
602
  value.phase === "commit-ready" &&
@@ -535,18 +627,28 @@ function cloneScope(
535
627
  function cloneLease(
536
628
  lease: TelegramWorkspaceAdmissionLease,
537
629
  ): TelegramWorkspaceAdmissionLease {
538
- return { ...lease, scope: cloneScope(lease.scope), owner: { ...lease.owner } };
630
+ return {
631
+ ...lease,
632
+ scope: cloneScope(lease.scope),
633
+ owner: { ...lease.owner },
634
+ };
539
635
  }
540
636
 
541
- export function resolveTelegramWorkspaceDestructiveFenceKind(
542
- fence: { destructiveKind?: TelegramWorkspaceDestructiveFenceKind },
543
- ): TelegramWorkspaceDestructiveFenceKind {
637
+ export function resolveTelegramWorkspaceDestructiveFenceKind(fence: {
638
+ destructiveKind?: TelegramWorkspaceDestructiveFenceKind;
639
+ }): TelegramWorkspaceDestructiveFenceKind {
544
640
  return fence.destructiveKind ?? "pressure-retirement";
545
641
  }
546
642
 
547
- function cloneFence(fence: TelegramWorkspaceRetirementFence): TelegramWorkspaceRetirementFence;
548
- function cloneFence(fence: TelegramWorkspaceJournalWriterClosureFence): TelegramWorkspaceJournalWriterClosureFence;
549
- function cloneFence(fence: TelegramWorkspaceDestructiveFence): TelegramWorkspaceDestructiveFence;
643
+ function cloneFence(
644
+ fence: TelegramWorkspaceRetirementFence,
645
+ ): TelegramWorkspaceRetirementFence;
646
+ function cloneFence(
647
+ fence: TelegramWorkspaceJournalWriterClosureFence,
648
+ ): TelegramWorkspaceJournalWriterClosureFence;
649
+ function cloneFence(
650
+ fence: TelegramWorkspaceDestructiveFence,
651
+ ): TelegramWorkspaceDestructiveFence;
550
652
  function cloneFence(
551
653
  fence: TelegramWorkspaceDestructiveFence,
552
654
  ): TelegramWorkspaceDestructiveFence {
@@ -589,22 +691,65 @@ function isConsolidatedAdmissionKey(value: unknown, profile: string): boolean {
589
691
  if (!isBoundedText(value)) return false;
590
692
  try {
591
693
  const key: unknown = JSON.parse(value);
592
- return isRecord(key) && Object.keys(key).length === 3 && key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION && key.profile === profile &&
593
- isRecord(key.bot) && Object.keys(key.bot).length === 1 && typeof key.bot.tokenSha256 === "string" && /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256);
594
- } catch { return false; }
694
+ return (
695
+ isRecord(key) &&
696
+ Object.keys(key).length === 3 &&
697
+ key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION &&
698
+ key.profile === profile &&
699
+ isRecord(key.bot) &&
700
+ Object.keys(key.bot).length === 1 &&
701
+ typeof key.bot.tokenSha256 === "string" &&
702
+ /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256)
703
+ );
704
+ } catch {
705
+ return false;
706
+ }
595
707
  }
596
708
  /** Pure damage check for one consolidated admission section under its own stored profile key; throws when invalid. */
597
- export function assertTelegramConsolidatedAdmissionSection(value: unknown, stateProfile: string): void {
709
+ export function assertTelegramConsolidatedAdmissionSection(
710
+ value: unknown,
711
+ stateProfile: string,
712
+ ): void {
598
713
  if (value === undefined) return;
599
- normalizeAdmissionState(value, isRecord(value) && typeof value.profileKey === "string" ? value.profileKey : "", stateProfile);
714
+ normalizeAdmissionState(
715
+ value,
716
+ isRecord(value) && typeof value.profileKey === "string"
717
+ ? value.profileKey
718
+ : "",
719
+ stateProfile,
720
+ );
600
721
  }
601
722
 
602
- function normalizeAdmissionState(parsed: unknown, profileKey: string, stateProfile?: string): TelegramWorkspaceAdmissionState {
603
- if (parsed === undefined) return { version: TELEGRAM_WORKSPACE_ADMISSION_VERSION, profileKey, leases: [] };
604
- if (!isRecord(parsed)) invalidState("Telegram Workspace admission state is invalid.");
605
- if (stateProfile !== undefined && (Object.keys(parsed).some(key => !["version", "profileKey", "leases", "fence", "writerProtocolMode"].includes(key)) ||
606
- !isConsolidatedAdmissionKey(parsed.profileKey, stateProfile)))
607
- invalidState("Telegram consolidated admission section identity is invalid.");
723
+ function normalizeAdmissionState(
724
+ parsed: unknown,
725
+ profileKey: string,
726
+ stateProfile?: string,
727
+ ): TelegramWorkspaceAdmissionState {
728
+ if (parsed === undefined)
729
+ return {
730
+ version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
731
+ profileKey,
732
+ leases: [],
733
+ };
734
+ if (!isRecord(parsed))
735
+ invalidState("Telegram Workspace admission state is invalid.");
736
+ if (
737
+ stateProfile !== undefined &&
738
+ (Object.keys(parsed).some(
739
+ (key) =>
740
+ ![
741
+ "version",
742
+ "profileKey",
743
+ "leases",
744
+ "fence",
745
+ "writerProtocolMode",
746
+ ].includes(key),
747
+ ) ||
748
+ !isConsolidatedAdmissionKey(parsed.profileKey, stateProfile))
749
+ )
750
+ invalidState(
751
+ "Telegram consolidated admission section identity is invalid.",
752
+ );
608
753
  if (
609
754
  parsed.version !== TELEGRAM_WORKSPACE_ADMISSION_VERSION ||
610
755
  !Array.isArray(parsed.leases) ||
@@ -613,8 +758,11 @@ function normalizeAdmissionState(parsed: unknown, profileKey: string, stateProfi
613
758
  invalidState("Telegram Workspace admission state authority is invalid.");
614
759
  }
615
760
  if (parsed.profileKey !== profileKey) {
616
- if (parsed.leases.length === 0 && parsed.fence === undefined &&
617
- parsed.writerProtocolMode === undefined) {
761
+ if (
762
+ parsed.leases.length === 0 &&
763
+ parsed.fence === undefined &&
764
+ parsed.writerProtocolMode === undefined
765
+ ) {
618
766
  return {
619
767
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
620
768
  profileKey,
@@ -623,28 +771,48 @@ function normalizeAdmissionState(parsed: unknown, profileKey: string, stateProfi
623
771
  }
624
772
  invalidState("Telegram Workspace admission profile authority changed.");
625
773
  }
626
- const leases = parsed.leases.map((value) => normalizeLease(value, profileKey));
774
+ const leases = parsed.leases.map((value) =>
775
+ normalizeLease(value, profileKey),
776
+ );
627
777
  if (leases.some((lease) => lease === undefined)) {
628
778
  invalidState("Telegram Workspace admission lease is invalid.");
629
779
  }
630
780
  const normalizedLeases = leases as TelegramWorkspaceAdmissionLease[];
631
- if (new Set(normalizedLeases.map((lease) => lease.operationId)).size !== normalizedLeases.length) {
781
+ if (
782
+ new Set(normalizedLeases.map((lease) => lease.operationId)).size !==
783
+ normalizedLeases.length
784
+ ) {
632
785
  invalidState("Telegram Workspace admission lease identity is duplicated.");
633
786
  }
634
787
  const fence =
635
- parsed.fence === undefined ? undefined : normalizeFence(parsed.fence, profileKey);
788
+ parsed.fence === undefined
789
+ ? undefined
790
+ : normalizeFence(parsed.fence, profileKey);
636
791
  if (parsed.fence !== undefined && !fence) {
637
792
  invalidState("Telegram Workspace retirement fence is invalid.");
638
793
  }
639
- const writerProtocolMode = parsed.writerProtocolMode === undefined ? undefined :
640
- normalizeTelegramWorkspaceJournalWriterProtocolMode(parsed.writerProtocolMode, profileKey);
794
+ const writerProtocolMode =
795
+ parsed.writerProtocolMode === undefined
796
+ ? undefined
797
+ : normalizeTelegramWorkspaceJournalWriterProtocolMode(
798
+ parsed.writerProtocolMode,
799
+ profileKey,
800
+ );
641
801
  if (parsed.writerProtocolMode !== undefined && !writerProtocolMode)
642
802
  invalidState("Telegram Workspace writer protocol mode is invalid.");
643
803
  if (fence && writerProtocolMode)
644
- invalidState("Telegram Workspace destructive fence conflicts with writer protocol mode.");
645
- if (stateProfile !== undefined && (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
646
- !isDeepStrictEqual(parsed.fence, fence) || !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode)))
647
- invalidState("Telegram consolidated admission evidence cannot drop unknown fields.");
804
+ invalidState(
805
+ "Telegram Workspace destructive fence conflicts with writer protocol mode.",
806
+ );
807
+ if (
808
+ stateProfile !== undefined &&
809
+ (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
810
+ !isDeepStrictEqual(parsed.fence, fence) ||
811
+ !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode))
812
+ )
813
+ invalidState(
814
+ "Telegram consolidated admission evidence cannot drop unknown fields.",
815
+ );
648
816
  return {
649
817
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
650
818
  profileKey,
@@ -678,7 +846,9 @@ function writeState(
678
846
  ...(options.publishRename ? { rename: options.publishRename } : {}),
679
847
  })
680
848
  ) {
681
- throw new Error("Temporary admission state disappeared before publication.");
849
+ throw new Error(
850
+ "Temporary admission state disappeared before publication.",
851
+ );
682
852
  }
683
853
  chmodSync(path, 0o600);
684
854
  } catch (error) {
@@ -770,38 +940,50 @@ function areWriterProtocolModesEqual(
770
940
  left: TelegramWorkspaceJournalWriterProtocolMode,
771
941
  right: TelegramWorkspaceJournalWriterProtocolMode,
772
942
  ): boolean {
773
- return left.protocol === right.protocol && left.profileKey === right.profileKey &&
943
+ return (
944
+ left.protocol === right.protocol &&
945
+ left.profileKey === right.profileKey &&
774
946
  left.recoveryKey === right.recoveryKey &&
775
947
  left.startupAuthorityId === right.startupAuthorityId &&
776
948
  left.closureOperationId === right.closureOperationId &&
777
949
  left.writerInventorySha256 === right.writerInventorySha256 &&
778
- areOwnersEqual(left.installedBy, right.installedBy) && left.installedAtMs === right.installedAtMs;
950
+ areOwnersEqual(left.installedBy, right.installedBy) &&
951
+ left.installedAtMs === right.installedAtMs
952
+ );
779
953
  }
780
954
 
781
955
  function areWriterClosureFencesEqual(
782
956
  left: TelegramWorkspaceJournalWriterClosureFence,
783
957
  right: TelegramWorkspaceJournalWriterClosureFence,
784
958
  ): boolean {
785
- return left.operationId === right.operationId && left.profileKey === right.profileKey &&
786
- left.recoveryKey === right.recoveryKey && areOwnersEqual(left.owner, right.owner) &&
787
- left.requestedAtMs === right.requestedAtMs && left.acquiredAtMs === right.acquiredAtMs;
959
+ return (
960
+ left.operationId === right.operationId &&
961
+ left.profileKey === right.profileKey &&
962
+ left.recoveryKey === right.recoveryKey &&
963
+ areOwnersEqual(left.owner, right.owner) &&
964
+ left.requestedAtMs === right.requestedAtMs &&
965
+ left.acquiredAtMs === right.acquiredAtMs
966
+ );
788
967
  }
789
968
 
790
969
  function areFencesEqual(
791
970
  left: TelegramWorkspaceRetirementFence,
792
971
  right: TelegramWorkspaceRetirementFence,
793
972
  ): boolean {
794
- if (!areFenceBasesEqual(left, right) || left.phase !== right.phase) return false;
973
+ if (!areFenceBasesEqual(left, right) || left.phase !== right.phase)
974
+ return false;
795
975
  if (left.phase === "fenced" && right.phase === "fenced") return true;
796
- if (
797
- left.phase === "deletion-issued" &&
798
- right.phase === "deletion-issued"
799
- ) {
976
+ if (left.phase === "deletion-issued" && right.phase === "deletion-issued") {
800
977
  return left.deletionIssuedAtMs === right.deletionIssuedAtMs;
801
978
  }
802
- if (left.phase === "deletion-rejected" && right.phase === "deletion-rejected") {
803
- return left.deletionIssuedAtMs === right.deletionIssuedAtMs &&
804
- left.rejectionConfirmedAtMs === right.rejectionConfirmedAtMs;
979
+ if (
980
+ left.phase === "deletion-rejected" &&
981
+ right.phase === "deletion-rejected"
982
+ ) {
983
+ return (
984
+ left.deletionIssuedAtMs === right.deletionIssuedAtMs &&
985
+ left.rejectionConfirmedAtMs === right.rejectionConfirmedAtMs
986
+ );
805
987
  }
806
988
  return (
807
989
  left.phase === "commit-ready" &&
@@ -811,7 +993,9 @@ function areFencesEqual(
811
993
  );
812
994
  }
813
995
 
814
- function validateOptions(options: TelegramWorkspaceAdmissionLedgerOptions): void {
996
+ function validateOptions(
997
+ options: TelegramWorkspaceAdmissionLedgerOptions,
998
+ ): void {
815
999
  if (!isBoundedText(options.path)) {
816
1000
  invalidInput("Telegram Workspace admission path is required.");
817
1001
  }
@@ -821,8 +1005,14 @@ function validateOptions(options: TelegramWorkspaceAdmissionLedgerOptions): void
821
1005
  if (!normalizeOwner(options.owner)) {
822
1006
  invalidInput("Telegram Workspace admission owner is invalid.");
823
1007
  }
824
- if (options.stateProfile !== undefined && (!isBoundedText(options.stateProfile) || !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile)))
825
- invalidInput("Telegram consolidated admission requires an exact bot/profile section identity.");
1008
+ if (
1009
+ options.stateProfile !== undefined &&
1010
+ (!isBoundedText(options.stateProfile) ||
1011
+ !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile))
1012
+ )
1013
+ invalidInput(
1014
+ "Telegram consolidated admission requires an exact bot/profile section identity.",
1015
+ );
826
1016
  }
827
1017
 
828
1018
  function validateOperationId(operationId: string): void {
@@ -865,7 +1055,9 @@ export function createTelegramWorkspaceAdmissionProfileKey(input: {
865
1055
  }): string {
866
1056
  const profileName = (input.profileName ?? "default").trim();
867
1057
  if (!isBoundedText(profileName) || !isBoundedText(input.botToken)) {
868
- invalidInput("Telegram Workspace admission bot/profile identity is invalid.");
1058
+ invalidInput(
1059
+ "Telegram Workspace admission bot/profile identity is invalid.",
1060
+ );
869
1061
  }
870
1062
  return JSON.stringify({
871
1063
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
@@ -880,18 +1072,25 @@ interface TelegramWorkspaceAdmissionRuntimeBinding {
880
1072
  resolve: () => TelegramWorkspaceAdmissionLedger | undefined;
881
1073
  }
882
1074
 
883
- export function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
884
- getProfileName: () => string | undefined;
885
- getBotToken: () => string | undefined;
886
- owner: TelegramWorkspaceAdmissionOwner;
887
- getNowMs?: () => number;
888
- getProcessLiveness?: (
889
- owner: TelegramWorkspaceAdmissionOwner,
890
- ) => TelegramProcessLiveness;
891
- } & ({ getPath: (profileName?: string) => string; getStatePath?: never } |
892
- { getStatePath: () => string; getPath?: never })): TelegramWorkspaceAdmissionRuntimeBinding {
1075
+ export function createTelegramWorkspaceAdmissionRuntimeBinding(
1076
+ input: {
1077
+ getProfileName: () => string | undefined;
1078
+ getBotToken: () => string | undefined;
1079
+ owner: TelegramWorkspaceAdmissionOwner;
1080
+ getNowMs?: () => number;
1081
+ getProcessLiveness?: (
1082
+ owner: TelegramWorkspaceAdmissionOwner,
1083
+ ) => TelegramProcessLiveness;
1084
+ } & (
1085
+ | { getPath: (profileName?: string) => string; getStatePath?: never }
1086
+ | { getStatePath: () => string; getPath?: never }
1087
+ ),
1088
+ ): TelegramWorkspaceAdmissionRuntimeBinding {
893
1089
  const { getProfileName, getBotToken, getPath, getStatePath } = input;
894
- if ((getPath === undefined) === (getStatePath === undefined)) invalidInput("Telegram admission binding must select one storage identity.");
1090
+ if ((getPath === undefined) === (getStatePath === undefined))
1091
+ invalidInput(
1092
+ "Telegram admission binding must select one storage identity.",
1093
+ );
895
1094
  return {
896
1095
  resolve() {
897
1096
  const botToken = getBotToken();
@@ -903,7 +1102,9 @@ export function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
903
1102
  });
904
1103
  return createTelegramWorkspaceAdmissionLedger({
905
1104
  path: getStatePath ? getStatePath() : getPath!(configuredProfileName),
906
- ...(getStatePath ? { stateProfile: configuredProfileName ?? "default" } : {}),
1105
+ ...(getStatePath
1106
+ ? { stateProfile: configuredProfileName ?? "default" }
1107
+ : {}),
907
1108
  profileKey,
908
1109
  owner: input.owner,
909
1110
  getNowMs: input.getNowMs,
@@ -913,7 +1114,10 @@ export function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
913
1114
  };
914
1115
  }
915
1116
 
916
- function getAdmissionScopeKey(scope: TelegramWorkspaceAdmissionScope): string {
1117
+ /** Stable admission-scope identity used by leases and admission-gated journal operations. */
1118
+ export function getTelegramWorkspaceAdmissionScopeKey(
1119
+ scope: TelegramWorkspaceAdmissionScope,
1120
+ ): string {
917
1121
  if (scope.kind === "profile") return "profile";
918
1122
  if (scope.kind === "chat") return `chat:${scope.chatId}`;
919
1123
  return `target:${scope.target.chatId}:${scope.target.threadId}`;
@@ -931,16 +1135,12 @@ function reserveTelegramWorkspaceAdmissionOperationId(
931
1135
  };
932
1136
  }
933
1137
 
934
- export function runWithTelegramWorkspaceAdmissions<T>(input: {
935
- ledger: Pick<
936
- TelegramWorkspaceAdmissionLedger,
937
- "acquireAdmission" | "releaseAdmission"
938
- >;
1138
+ /** Validated, de-duplicated scopes in the stable key order every admitted operation acquires. */
1139
+ function normalizeTelegramWorkspaceAdmittedScopes(input: {
939
1140
  operationId: string;
940
1141
  operationKind: string;
941
1142
  scopes: readonly TelegramWorkspaceAdmissionScope[];
942
- operation: () => T;
943
- }): T {
1143
+ }): TelegramWorkspaceAdmissionScope[] {
944
1144
  validateOperationId(input.operationId);
945
1145
  if (!isBoundedText(input.operationKind) || input.scopes.length === 0) {
946
1146
  invalidInput("Telegram Workspace admitted operation is invalid.");
@@ -948,35 +1148,65 @@ export function runWithTelegramWorkspaceAdmissions<T>(input: {
948
1148
  const uniqueScopes = new Map<string, TelegramWorkspaceAdmissionScope>();
949
1149
  for (const scope of input.scopes) {
950
1150
  validateScope(scope);
951
- uniqueScopes.set(getAdmissionScopeKey(scope), cloneScope(scope));
1151
+ uniqueScopes.set(
1152
+ getTelegramWorkspaceAdmissionScopeKey(scope),
1153
+ cloneScope(scope),
1154
+ );
952
1155
  }
953
- const scopes = Array.from(uniqueScopes.entries())
1156
+ return Array.from(uniqueScopes.entries())
954
1157
  .sort(([left], [right]) => left.localeCompare(right))
955
1158
  .map(([, scope]) => scope);
956
- const releaseActiveOperation =
957
- reserveTelegramWorkspaceAdmissionOperationId(input.operationId);
1159
+ }
1160
+
1161
+ /** Appends each lease as it is acquired so a blocked or failed scope leaves earlier leases releasable. */
1162
+ function acquireTelegramWorkspaceAdmittedScopes(
1163
+ input: {
1164
+ ledger: Pick<TelegramWorkspaceAdmissionLedger, "acquireAdmission">;
1165
+ operationId: string;
1166
+ operationKind: string;
1167
+ },
1168
+ scopes: readonly TelegramWorkspaceAdmissionScope[],
1169
+ acquired: TelegramWorkspaceAdmissionLease[],
1170
+ ): void {
1171
+ for (const scope of scopes) {
1172
+ const scopeOperationId = `admission:${createHash("sha256")
1173
+ .update(input.operationId)
1174
+ .update("\0")
1175
+ .update(getTelegramWorkspaceAdmissionScopeKey(scope))
1176
+ .digest("hex")}`;
1177
+ const result = input.ledger.acquireAdmission({
1178
+ operationId: scopeOperationId,
1179
+ operationKind: input.operationKind,
1180
+ scope,
1181
+ });
1182
+ if (result.kind === "blocked") {
1183
+ throw new TelegramWorkspaceAdmissionError(
1184
+ "admission-blocked",
1185
+ "Telegram Workspace operation is blocked by retirement.",
1186
+ );
1187
+ }
1188
+ acquired.push(result.lease);
1189
+ }
1190
+ }
1191
+
1192
+ export function runWithTelegramWorkspaceAdmissions<T>(input: {
1193
+ ledger: Pick<
1194
+ TelegramWorkspaceAdmissionLedger,
1195
+ "acquireAdmission" | "releaseAdmission"
1196
+ >;
1197
+ operationId: string;
1198
+ operationKind: string;
1199
+ scopes: readonly TelegramWorkspaceAdmissionScope[];
1200
+ operation: () => T;
1201
+ }): T {
1202
+ const scopes = normalizeTelegramWorkspaceAdmittedScopes(input);
1203
+ const releaseActiveOperation = reserveTelegramWorkspaceAdmissionOperationId(
1204
+ input.operationId,
1205
+ );
958
1206
  const acquired: TelegramWorkspaceAdmissionLease[] = [];
959
1207
  let outcome: { ok: true; value: T } | { ok: false; error: unknown };
960
1208
  try {
961
- for (const scope of scopes) {
962
- const scopeOperationId = `admission:${createHash("sha256")
963
- .update(input.operationId)
964
- .update("\0")
965
- .update(getAdmissionScopeKey(scope))
966
- .digest("hex")}`;
967
- const result = input.ledger.acquireAdmission({
968
- operationId: scopeOperationId,
969
- operationKind: input.operationKind,
970
- scope,
971
- });
972
- if (result.kind === "blocked") {
973
- throw new TelegramWorkspaceAdmissionError(
974
- "admission-blocked",
975
- "Telegram Workspace operation is blocked by retirement.",
976
- );
977
- }
978
- acquired.push(result.lease);
979
- }
1209
+ acquireTelegramWorkspaceAdmittedScopes(input, scopes, acquired);
980
1210
  outcome = { ok: true, value: input.operation() };
981
1211
  } catch (error) {
982
1212
  outcome = { ok: false, error };
@@ -1001,28 +1231,47 @@ export function runWithTelegramWorkspaceAdmissions<T>(input: {
1001
1231
  }
1002
1232
 
1003
1233
  export function createTelegramWorkspaceJournalWriterAdmission(input: {
1004
- ledger: Pick<TelegramWorkspaceAdmissionLedger,
1005
- "acquireAdmission" | "acquireJournalWriterAdmission" | "releaseAdmission">;
1006
- protocolAuthority?: Omit<Parameters<TelegramWorkspaceAdmissionLedger["acquireJournalWriterAdmission"]>[0],
1007
- "operationId">;
1234
+ ledger: Pick<
1235
+ TelegramWorkspaceAdmissionLedger,
1236
+ "acquireAdmission" | "acquireJournalWriterAdmission" | "releaseAdmission"
1237
+ >;
1238
+ protocolAuthority?: Omit<
1239
+ Parameters<
1240
+ TelegramWorkspaceAdmissionLedger["acquireJournalWriterAdmission"]
1241
+ >[0],
1242
+ "operationId"
1243
+ >;
1008
1244
  createOperationId?: () => string;
1009
1245
  onReleaseError?: (error: unknown) => void;
1010
1246
  }): <T>(operation: () => T) => T {
1011
1247
  let pendingOperationId: string | undefined;
1012
1248
  let active = false;
1013
1249
  return <T>(operation: () => T): T => {
1014
- if (active) invalidState("Telegram Workspace journal writer admission is already active.");
1015
- const operationId = pendingOperationId ?? input.createOperationId?.() ??
1250
+ if (active)
1251
+ invalidState(
1252
+ "Telegram Workspace journal writer admission is already active.",
1253
+ );
1254
+ const operationId =
1255
+ pendingOperationId ??
1256
+ input.createOperationId?.() ??
1016
1257
  `journal-writer:${randomUUID()}`;
1017
1258
  validateOperationId(operationId);
1018
1259
  pendingOperationId = operationId;
1019
1260
  const result = input.protocolAuthority
1020
- ? input.ledger.acquireJournalWriterAdmission({ operationId, ...input.protocolAuthority })
1021
- : input.ledger.acquireAdmission({ operationId,
1022
- operationKind: "journal-write", scope: { kind: "profile" } });
1261
+ ? input.ledger.acquireJournalWriterAdmission({
1262
+ operationId,
1263
+ ...input.protocolAuthority,
1264
+ })
1265
+ : input.ledger.acquireAdmission({
1266
+ operationId,
1267
+ operationKind: "journal-write",
1268
+ scope: { kind: "profile" },
1269
+ });
1023
1270
  if (result.kind === "blocked") {
1024
- throw new TelegramWorkspaceAdmissionError("admission-blocked",
1025
- "Telegram Workspace journal writer is blocked by destructive closure.");
1271
+ throw new TelegramWorkspaceAdmissionError(
1272
+ "admission-blocked",
1273
+ "Telegram Workspace journal writer is blocked by destructive closure.",
1274
+ );
1026
1275
  }
1027
1276
  pendingOperationId = undefined;
1028
1277
  active = true;
@@ -1032,9 +1281,15 @@ export function createTelegramWorkspaceJournalWriterAdmission(input: {
1032
1281
  active = false;
1033
1282
  try {
1034
1283
  if (!input.ledger.releaseAdmission(result.lease))
1035
- authorityChanged("Telegram Workspace journal writer lease disappeared before release.");
1284
+ authorityChanged(
1285
+ "Telegram Workspace journal writer lease disappeared before release.",
1286
+ );
1036
1287
  } catch (error) {
1037
- try { input.onReleaseError?.(error); } catch { /* Diagnostics cannot change the journal outcome. */ }
1288
+ try {
1289
+ input.onReleaseError?.(error);
1290
+ } catch {
1291
+ /* Diagnostics cannot change the journal outcome. */
1292
+ }
1038
1293
  }
1039
1294
  }
1040
1295
  };
@@ -1051,20 +1306,10 @@ export async function runWithTelegramWorkspaceAdmissionsAsync<T>(input: {
1051
1306
  operation: () => Promise<T>;
1052
1307
  onReleaseError?: (error: unknown) => void;
1053
1308
  }): Promise<T> {
1054
- validateOperationId(input.operationId);
1055
- if (!isBoundedText(input.operationKind) || input.scopes.length === 0) {
1056
- invalidInput("Telegram Workspace admitted operation is invalid.");
1057
- }
1058
- const uniqueScopes = new Map<string, TelegramWorkspaceAdmissionScope>();
1059
- for (const scope of input.scopes) {
1060
- validateScope(scope);
1061
- uniqueScopes.set(getAdmissionScopeKey(scope), cloneScope(scope));
1062
- }
1063
- const scopes = Array.from(uniqueScopes.entries())
1064
- .sort(([left], [right]) => left.localeCompare(right))
1065
- .map(([, scope]) => scope);
1066
- const releaseActiveOperation =
1067
- reserveTelegramWorkspaceAdmissionOperationId(input.operationId);
1309
+ const scopes = normalizeTelegramWorkspaceAdmittedScopes(input);
1310
+ const releaseActiveOperation = reserveTelegramWorkspaceAdmissionOperationId(
1311
+ input.operationId,
1312
+ );
1068
1313
  const acquired: TelegramWorkspaceAdmissionLease[] = [];
1069
1314
  const releaseAll = (): void => {
1070
1315
  for (const lease of acquired.reverse()) {
@@ -1085,25 +1330,7 @@ export async function runWithTelegramWorkspaceAdmissionsAsync<T>(input: {
1085
1330
  }
1086
1331
  };
1087
1332
  try {
1088
- for (const scope of scopes) {
1089
- const scopeOperationId = `admission:${createHash("sha256")
1090
- .update(input.operationId)
1091
- .update("\0")
1092
- .update(getAdmissionScopeKey(scope))
1093
- .digest("hex")}`;
1094
- const result = input.ledger.acquireAdmission({
1095
- operationId: scopeOperationId,
1096
- operationKind: input.operationKind,
1097
- scope,
1098
- });
1099
- if (result.kind === "blocked") {
1100
- throw new TelegramWorkspaceAdmissionError(
1101
- "admission-blocked",
1102
- "Telegram Workspace operation is blocked by retirement.",
1103
- );
1104
- }
1105
- acquired.push(result.lease);
1106
- }
1333
+ acquireTelegramWorkspaceAdmittedScopes(input, scopes, acquired);
1107
1334
  } catch (error) {
1108
1335
  releaseAll();
1109
1336
  releaseActiveOperation();
@@ -1122,20 +1349,34 @@ export function createTelegramWorkspaceAdmissionLedger(
1122
1349
  ): TelegramWorkspaceAdmissionLedger {
1123
1350
  validateOptions(options);
1124
1351
  const suppliedOptions = options;
1125
- if (options.stateProfile !== undefined) options = { ...options, owner: { ...options.owner } };
1352
+ if (options.stateProfile !== undefined)
1353
+ options = { ...options, owner: { ...options.owner } };
1126
1354
  const getNowMs = options.getNowMs ?? Date.now;
1127
1355
  const getProcessLiveness =
1128
1356
  options.getProcessLiveness ??
1129
1357
  ((owner: TelegramWorkspaceAdmissionOwner) =>
1130
1358
  getTelegramProcessLiveness(owner));
1131
1359
  const transactionPath = `${options.path}.transaction`;
1132
- const path = options.path, profileKey = options.profileKey, stateProfile = options.stateProfile, boundOwner = { ...options.owner };
1133
- const publication = { publishRename: options.publishRename, onPublicationBoundary: options.onPublicationBoundary };
1134
- const isStorageCurrent = (): boolean => suppliedOptions.path === path && suppliedOptions.profileKey === profileKey &&
1135
- suppliedOptions.stateProfile === stateProfile && areOwnersEqual(suppliedOptions.owner, boundOwner);
1360
+ const path = options.path,
1361
+ profileKey = options.profileKey,
1362
+ stateProfile = options.stateProfile,
1363
+ boundOwner = { ...options.owner };
1364
+ const publication = {
1365
+ publishRename: options.publishRename,
1366
+ onPublicationBoundary: options.onPublicationBoundary,
1367
+ };
1368
+ const isStorageCurrent = (): boolean =>
1369
+ suppliedOptions.path === path &&
1370
+ suppliedOptions.profileKey === profileKey &&
1371
+ suppliedOptions.stateProfile === stateProfile &&
1372
+ areOwnersEqual(suppliedOptions.owner, boundOwner);
1136
1373
  const mapStateError = (error: unknown): never => {
1137
- if (error instanceof TelegramRuntimeStateError) throw new TelegramWorkspaceAdmissionError(
1138
- error.code === "invalid" ? "invalid-state" : error.code, "Telegram consolidated admission state operation failed.", { cause: error });
1374
+ if (error instanceof TelegramRuntimeStateError)
1375
+ throw new TelegramWorkspaceAdmissionError(
1376
+ error.code === "invalid" ? "invalid-state" : error.code,
1377
+ "Telegram consolidated admission state operation failed.",
1378
+ { cause: error },
1379
+ );
1139
1380
  throw error;
1140
1381
  };
1141
1382
 
@@ -1147,17 +1388,42 @@ export function createTelegramWorkspaceAdmissionLedger(
1147
1388
  ): T {
1148
1389
  if (stateProfile !== undefined) {
1149
1390
  try {
1150
- return mutateTelegramRuntimeStateSection(path, stateProfile, "admission", current => {
1151
- const state = normalizeAdmissionState(current, profileKey, stateProfile);
1152
- const retained = state.leases.filter(lease => getProcessLiveness(lease.owner) !== "dead");
1153
- const pruned = retained.length !== state.leases.length;
1154
- state.leases = retained;
1155
- const outcome = operation(state);
1156
- return { value: pruned || outcome.changed ? normalizeAdmissionState(state, profileKey, stateProfile) : current, result: outcome.result };
1157
- }, { isCurrent: isStorageCurrent, publishRename: publication.publishRename, onPublicationBoundary(boundary) {
1158
- if (boundary !== "after-rename") publication.onPublicationBoundary?.(boundary, path);
1159
- } });
1160
- } catch (error) { return mapStateError(error); }
1391
+ return mutateTelegramRuntimeStateSection(
1392
+ path,
1393
+ stateProfile,
1394
+ "admission",
1395
+ (current) => {
1396
+ const state = normalizeAdmissionState(
1397
+ current,
1398
+ profileKey,
1399
+ stateProfile,
1400
+ );
1401
+ const retained = state.leases.filter(
1402
+ (lease) => getProcessLiveness(lease.owner) !== "dead",
1403
+ );
1404
+ const pruned = retained.length !== state.leases.length;
1405
+ state.leases = retained;
1406
+ const outcome = operation(state);
1407
+ return {
1408
+ value:
1409
+ pruned || outcome.changed
1410
+ ? normalizeAdmissionState(state, profileKey, stateProfile)
1411
+ : current,
1412
+ result: outcome.result,
1413
+ };
1414
+ },
1415
+ {
1416
+ isCurrent: isStorageCurrent,
1417
+ publishRename: publication.publishRename,
1418
+ onPublicationBoundary(boundary) {
1419
+ if (boundary !== "after-rename")
1420
+ publication.onPublicationBoundary?.(boundary, path);
1421
+ },
1422
+ },
1423
+ );
1424
+ } catch (error) {
1425
+ return mapStateError(error);
1426
+ }
1161
1427
  }
1162
1428
  return withTelegramFileTransaction(transactionPath, () => {
1163
1429
  const state = readState(options.path, options.profileKey);
@@ -1175,21 +1441,36 @@ export function createTelegramWorkspaceAdmissionLedger(
1175
1441
  function read(): TelegramWorkspaceAdmissionLedgerSnapshot {
1176
1442
  let state: TelegramWorkspaceAdmissionState;
1177
1443
  if (stateProfile !== undefined) {
1178
- if (!isStorageCurrent()) authorityChanged("Telegram consolidated admission storage authority changed.");
1444
+ if (!isStorageCurrent())
1445
+ authorityChanged(
1446
+ "Telegram consolidated admission storage authority changed.",
1447
+ );
1179
1448
  try {
1180
1449
  const file = readTelegramRuntimeState(path);
1181
- const value = Object.hasOwn(file.profiles, stateProfile) ? file.profiles[stateProfile]?.admission : undefined;
1450
+ const value = Object.hasOwn(file.profiles, stateProfile)
1451
+ ? file.profiles[stateProfile]?.admission
1452
+ : undefined;
1182
1453
  state = normalizeAdmissionState(value, profileKey, stateProfile);
1183
- if (!isStorageCurrent()) authorityChanged("Telegram consolidated admission storage authority changed.");
1184
- } catch (error) { return mapStateError(error); }
1454
+ if (!isStorageCurrent())
1455
+ authorityChanged(
1456
+ "Telegram consolidated admission storage authority changed.",
1457
+ );
1458
+ } catch (error) {
1459
+ return mapStateError(error);
1460
+ }
1185
1461
  } else state = readState(options.path, options.profileKey);
1186
1462
  return {
1187
1463
  profileKey: state.profileKey,
1188
1464
  leases: state.leases.map(cloneLease),
1189
1465
  ...(state.fence ? { fence: cloneFence(state.fence) } : {}),
1190
- ...(state.writerProtocolMode ? { writerProtocolMode: {
1191
- ...state.writerProtocolMode, installedBy: { ...state.writerProtocolMode.installedBy },
1192
- } } : {}),
1466
+ ...(state.writerProtocolMode
1467
+ ? {
1468
+ writerProtocolMode: {
1469
+ ...state.writerProtocolMode,
1470
+ installedBy: { ...state.writerProtocolMode.installedBy },
1471
+ },
1472
+ }
1473
+ : {}),
1193
1474
  };
1194
1475
  }
1195
1476
 
@@ -1204,13 +1485,19 @@ export function createTelegramWorkspaceAdmissionLedger(
1204
1485
  }
1205
1486
  validateScope(input.scope);
1206
1487
  return transact<TelegramWorkspaceAdmissionAcquireResult>((state) => {
1207
- const journalOperation = input.operationKind === "journal-write" ||
1488
+ const journalOperation =
1489
+ input.operationKind === "journal-write" ||
1208
1490
  input.operationKind.startsWith("journal.");
1209
- const protocolWriterHeld = state.leases.some(lease =>
1210
- lease.operationKind === "journal-write:custody-v3" && lease.scope.kind === "profile" &&
1211
- areOwnersEqual(lease.owner, options.owner));
1491
+ const protocolWriterHeld = state.leases.some(
1492
+ (lease) =>
1493
+ lease.operationKind === "journal-write:custody-v3" &&
1494
+ lease.scope.kind === "profile" &&
1495
+ areOwnersEqual(lease.owner, options.owner),
1496
+ );
1212
1497
  if (state.writerProtocolMode && journalOperation && !protocolWriterHeld)
1213
- authorityChanged("Telegram Workspace journal writer requires protocol authority.");
1498
+ authorityChanged(
1499
+ "Telegram Workspace journal writer requires protocol authority.",
1500
+ );
1214
1501
  const existing = state.leases.find(
1215
1502
  (lease) => lease.operationId === input.operationId,
1216
1503
  );
@@ -1224,22 +1511,33 @@ export function createTelegramWorkspaceAdmissionLedger(
1224
1511
  acquiredAtMs: existing.acquiredAtMs,
1225
1512
  };
1226
1513
  if (!areLeasesEqual(existing, retried)) {
1227
- authorityChanged("Telegram Workspace admission operation identity changed.");
1514
+ authorityChanged(
1515
+ "Telegram Workspace admission operation identity changed.",
1516
+ );
1228
1517
  }
1229
1518
  return {
1230
- result: { kind: "acquired", lease: cloneLease(existing), resumed: true },
1519
+ result: {
1520
+ kind: "acquired",
1521
+ lease: cloneLease(existing),
1522
+ resumed: true,
1523
+ },
1231
1524
  changed: false,
1232
1525
  };
1233
1526
  }
1234
- if (state.fence && (!isTelegramWorkspaceRetirementFence(state.fence) ||
1235
- scopesConflict(input.scope, state.fence.target))) {
1527
+ if (
1528
+ state.fence &&
1529
+ (!isTelegramWorkspaceRetirementFence(state.fence) ||
1530
+ scopesConflict(input.scope, state.fence.target))
1531
+ ) {
1236
1532
  return {
1237
1533
  result: { kind: "blocked", reason: "retirement-fenced" },
1238
1534
  changed: false,
1239
1535
  };
1240
1536
  }
1241
1537
  if (state.leases.length >= TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES) {
1242
- invalidState("Telegram Workspace admission lease capacity is exhausted.");
1538
+ invalidState(
1539
+ "Telegram Workspace admission lease capacity is exhausted.",
1540
+ );
1243
1541
  }
1244
1542
  const lease: TelegramWorkspaceAdmissionLease = {
1245
1543
  operationId: input.operationId,
@@ -1257,10 +1555,14 @@ export function createTelegramWorkspaceAdmissionLedger(
1257
1555
  });
1258
1556
  }
1259
1557
 
1260
- function releaseAdmission(expected: TelegramWorkspaceAdmissionLease): boolean {
1558
+ function releaseAdmission(
1559
+ expected: TelegramWorkspaceAdmissionLease,
1560
+ ): boolean {
1261
1561
  validateExpectedLease(expected, options.profileKey);
1262
1562
  if (!areOwnersEqual(expected.owner, options.owner)) {
1263
- authorityChanged("Telegram Workspace admission release requires lease owner authority.");
1563
+ authorityChanged(
1564
+ "Telegram Workspace admission release requires lease owner authority.",
1565
+ );
1264
1566
  }
1265
1567
  return transact((state) => {
1266
1568
  const index = state.leases.findIndex(
@@ -1269,67 +1571,138 @@ export function createTelegramWorkspaceAdmissionLedger(
1269
1571
  if (index < 0) return { result: false, changed: false };
1270
1572
  const current = state.leases[index];
1271
1573
  if (!areLeasesEqual(current, expected)) {
1272
- authorityChanged("Telegram Workspace admission lease authority changed.");
1574
+ authorityChanged(
1575
+ "Telegram Workspace admission lease authority changed.",
1576
+ );
1273
1577
  }
1274
1578
  state.leases.splice(index, 1);
1275
1579
  return { result: true, changed: true };
1276
1580
  });
1277
1581
  }
1278
1582
 
1279
- function acquireJournalWriterClosure(input: { operationId: string; recoveryKey: string;
1280
- requestedAtMs: number }): TelegramWorkspaceJournalWriterClosureAcquireResult {
1583
+ function acquireJournalWriterClosure(input: {
1584
+ operationId: string;
1585
+ recoveryKey: string;
1586
+ requestedAtMs: number;
1587
+ }): TelegramWorkspaceJournalWriterClosureAcquireResult {
1281
1588
  validateOperationId(input.operationId);
1282
- const candidate = normalizeTelegramWorkspaceJournalWriterClosureFence({
1283
- destructiveKind: "journal-writer-closure", phase: "fenced", ...input,
1284
- profileKey: options.profileKey, owner: options.owner, acquiredAtMs: getNowMs(),
1285
- }, options.profileKey);
1286
- if (!candidate) invalidInput("Telegram Workspace writer closure input is invalid.");
1287
- return transact<TelegramWorkspaceJournalWriterClosureAcquireResult>((state) => {
1288
- if (state.writerProtocolMode) {
1289
- if (state.leases.length > 0)
1290
- return { result: { kind: "blocked", reason: "admission-active" }, changed: false };
1291
- let authorized = false;
1292
- if (state.writerProtocolMode.recoveryKey === candidate.recoveryKey) {
1293
- try { authorized = options.authorizeJournalWriterProtocolClosure?.({
1294
- mode: { ...state.writerProtocolMode,
1295
- installedBy: { ...state.writerProtocolMode.installedBy } },
1296
- closure: cloneFence(candidate),
1297
- }) === true; } catch { authorized = false; }
1589
+ const candidate = normalizeTelegramWorkspaceJournalWriterClosureFence(
1590
+ {
1591
+ destructiveKind: "journal-writer-closure",
1592
+ phase: "fenced",
1593
+ ...input,
1594
+ profileKey: options.profileKey,
1595
+ owner: options.owner,
1596
+ acquiredAtMs: getNowMs(),
1597
+ },
1598
+ options.profileKey,
1599
+ );
1600
+ if (!candidate)
1601
+ invalidInput("Telegram Workspace writer closure input is invalid.");
1602
+ return transact<TelegramWorkspaceJournalWriterClosureAcquireResult>(
1603
+ (state) => {
1604
+ if (state.writerProtocolMode) {
1605
+ if (state.leases.length > 0)
1606
+ return {
1607
+ result: { kind: "blocked", reason: "admission-active" },
1608
+ changed: false,
1609
+ };
1610
+ let authorized = false;
1611
+ if (state.writerProtocolMode.recoveryKey === candidate.recoveryKey) {
1612
+ try {
1613
+ authorized =
1614
+ options.authorizeJournalWriterProtocolClosure?.({
1615
+ mode: {
1616
+ ...state.writerProtocolMode,
1617
+ installedBy: { ...state.writerProtocolMode.installedBy },
1618
+ },
1619
+ closure: cloneFence(candidate),
1620
+ }) === true;
1621
+ } catch {
1622
+ authorized = false;
1623
+ }
1624
+ }
1625
+ if (!authorized)
1626
+ return {
1627
+ result: { kind: "blocked", reason: "retirement-active" },
1628
+ changed: false,
1629
+ };
1630
+ delete state.writerProtocolMode;
1631
+ state.fence = candidate;
1632
+ return {
1633
+ result: {
1634
+ kind: "acquired",
1635
+ fence: cloneFence(candidate),
1636
+ resumed: false,
1637
+ },
1638
+ changed: true,
1639
+ };
1298
1640
  }
1299
- if (!authorized)
1300
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
1301
- delete state.writerProtocolMode;
1302
- state.fence = candidate;
1303
- return { result: { kind: "acquired", fence: cloneFence(candidate), resumed: false },
1304
- changed: true };
1305
- }
1306
- if (state.fence) {
1307
- if (state.fence.destructiveKind === "journal-writer-closure") {
1308
- const resumed = { ...candidate, acquiredAtMs: state.fence.acquiredAtMs };
1309
- if (areWriterClosureFencesEqual(state.fence, resumed))
1310
- return { result: { kind: "acquired", fence: cloneFence(state.fence), resumed: true }, changed: false };
1311
- if (state.fence.operationId === candidate.operationId)
1312
- authorityChanged("Telegram Workspace writer closure authority changed.");
1641
+ if (state.fence) {
1642
+ if (state.fence.destructiveKind === "journal-writer-closure") {
1643
+ const resumed = {
1644
+ ...candidate,
1645
+ acquiredAtMs: state.fence.acquiredAtMs,
1646
+ };
1647
+ if (areWriterClosureFencesEqual(state.fence, resumed))
1648
+ return {
1649
+ result: {
1650
+ kind: "acquired",
1651
+ fence: cloneFence(state.fence),
1652
+ resumed: true,
1653
+ },
1654
+ changed: false,
1655
+ };
1656
+ if (state.fence.operationId === candidate.operationId)
1657
+ authorityChanged(
1658
+ "Telegram Workspace writer closure authority changed.",
1659
+ );
1660
+ }
1661
+ return {
1662
+ result: { kind: "blocked", reason: "retirement-active" },
1663
+ changed: false,
1664
+ };
1313
1665
  }
1314
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
1315
- }
1316
- if (state.leases.length > 0)
1317
- return { result: { kind: "blocked", reason: "admission-active" }, changed: false };
1318
- state.fence = candidate;
1319
- return { result: { kind: "acquired", fence: cloneFence(candidate), resumed: false }, changed: true };
1320
- });
1666
+ if (state.leases.length > 0)
1667
+ return {
1668
+ result: { kind: "blocked", reason: "admission-active" },
1669
+ changed: false,
1670
+ };
1671
+ state.fence = candidate;
1672
+ return {
1673
+ result: {
1674
+ kind: "acquired",
1675
+ fence: cloneFence(candidate),
1676
+ resumed: false,
1677
+ },
1678
+ changed: true,
1679
+ };
1680
+ },
1681
+ );
1321
1682
  }
1322
1683
 
1323
- function releaseJournalWriterClosure(expected: TelegramWorkspaceJournalWriterClosureFence): boolean {
1324
- const normalized = normalizeTelegramWorkspaceJournalWriterClosureFence(expected, options.profileKey);
1325
- if (!normalized) invalidInput("Telegram Workspace writer closure authority is invalid.");
1684
+ function releaseJournalWriterClosure(
1685
+ expected: TelegramWorkspaceJournalWriterClosureFence,
1686
+ ): boolean {
1687
+ const normalized = normalizeTelegramWorkspaceJournalWriterClosureFence(
1688
+ expected,
1689
+ options.profileKey,
1690
+ );
1691
+ if (!normalized)
1692
+ invalidInput("Telegram Workspace writer closure authority is invalid.");
1326
1693
  if (!areOwnersEqual(expected.owner, options.owner))
1327
- authorityChanged("Telegram Workspace writer closure release requires owner authority.");
1694
+ authorityChanged(
1695
+ "Telegram Workspace writer closure release requires owner authority.",
1696
+ );
1328
1697
  return transact((state) => {
1329
1698
  if (!state.fence) return { result: false, changed: false };
1330
- if (state.fence.destructiveKind !== "journal-writer-closure" ||
1331
- !areWriterClosureFencesEqual(state.fence, expected))
1332
- authorityChanged("Telegram Workspace writer closure authority changed.");
1699
+ if (
1700
+ state.fence.destructiveKind !== "journal-writer-closure" ||
1701
+ !areWriterClosureFencesEqual(state.fence, expected)
1702
+ )
1703
+ authorityChanged(
1704
+ "Telegram Workspace writer closure authority changed.",
1705
+ );
1333
1706
  delete state.fence;
1334
1707
  return { result: true, changed: true };
1335
1708
  });
@@ -1339,81 +1712,170 @@ export function createTelegramWorkspaceAdmissionLedger(
1339
1712
  expected: TelegramWorkspaceJournalWriterClosureFence,
1340
1713
  input: { startupAuthorityId: string; writerInventorySha256: string },
1341
1714
  ): TelegramWorkspaceJournalWriterProtocolInstallResult {
1342
- const normalizedClosure = normalizeTelegramWorkspaceJournalWriterClosureFence(expected,
1343
- options.profileKey);
1715
+ const normalizedClosure =
1716
+ normalizeTelegramWorkspaceJournalWriterClosureFence(
1717
+ expected,
1718
+ options.profileKey,
1719
+ );
1344
1720
  if (!normalizedClosure || !areOwnersEqual(expected.owner, options.owner))
1345
- authorityChanged("Telegram Workspace writer protocol installation requires closure owner authority.");
1346
- const candidate = normalizeTelegramWorkspaceJournalWriterProtocolMode({ version: 1,
1347
- protocol: "custody-v3", profileKey: options.profileKey, recoveryKey: expected.recoveryKey,
1348
- startupAuthorityId: input.startupAuthorityId, closureOperationId: expected.operationId,
1349
- writerInventorySha256: input.writerInventorySha256, installedBy: options.owner,
1350
- installedAtMs: getNowMs() }, options.profileKey);
1351
- if (!candidate) invalidInput("Telegram Workspace writer protocol mode input is invalid.");
1352
- return transact<TelegramWorkspaceJournalWriterProtocolInstallResult>((state) => {
1353
- if (state.writerProtocolMode) {
1354
- const resumed = { ...candidate, installedAtMs: state.writerProtocolMode.installedAtMs };
1355
- if (areWriterProtocolModesEqual(state.writerProtocolMode, resumed) && !state.fence)
1356
- return { result: { mode: { ...state.writerProtocolMode,
1357
- installedBy: { ...state.writerProtocolMode.installedBy } }, resumed: true }, changed: false };
1358
- authorityChanged("Telegram Workspace writer protocol mode authority changed.");
1359
- }
1360
- if (!state.fence || state.fence.destructiveKind !== "journal-writer-closure" ||
1361
- !areWriterClosureFencesEqual(state.fence, expected) || state.leases.length !== 0)
1362
- authorityChanged("Telegram Workspace writer closure authority changed before protocol installation.");
1363
- state.writerProtocolMode = candidate;
1364
- delete state.fence;
1365
- return { result: { mode: { ...candidate, installedBy: { ...candidate.installedBy } },
1366
- resumed: false }, changed: true };
1367
- });
1721
+ authorityChanged(
1722
+ "Telegram Workspace writer protocol installation requires closure owner authority.",
1723
+ );
1724
+ const candidate = normalizeTelegramWorkspaceJournalWriterProtocolMode(
1725
+ {
1726
+ version: 1,
1727
+ protocol: "custody-v3",
1728
+ profileKey: options.profileKey,
1729
+ recoveryKey: expected.recoveryKey,
1730
+ startupAuthorityId: input.startupAuthorityId,
1731
+ closureOperationId: expected.operationId,
1732
+ writerInventorySha256: input.writerInventorySha256,
1733
+ installedBy: options.owner,
1734
+ installedAtMs: getNowMs(),
1735
+ },
1736
+ options.profileKey,
1737
+ );
1738
+ if (!candidate)
1739
+ invalidInput("Telegram Workspace writer protocol mode input is invalid.");
1740
+ return transact<TelegramWorkspaceJournalWriterProtocolInstallResult>(
1741
+ (state) => {
1742
+ if (state.writerProtocolMode) {
1743
+ const resumed = {
1744
+ ...candidate,
1745
+ installedAtMs: state.writerProtocolMode.installedAtMs,
1746
+ };
1747
+ if (
1748
+ areWriterProtocolModesEqual(state.writerProtocolMode, resumed) &&
1749
+ !state.fence
1750
+ )
1751
+ return {
1752
+ result: {
1753
+ mode: {
1754
+ ...state.writerProtocolMode,
1755
+ installedBy: { ...state.writerProtocolMode.installedBy },
1756
+ },
1757
+ resumed: true,
1758
+ },
1759
+ changed: false,
1760
+ };
1761
+ authorityChanged(
1762
+ "Telegram Workspace writer protocol mode authority changed.",
1763
+ );
1764
+ }
1765
+ if (
1766
+ !state.fence ||
1767
+ state.fence.destructiveKind !== "journal-writer-closure" ||
1768
+ !areWriterClosureFencesEqual(state.fence, expected) ||
1769
+ state.leases.length !== 0
1770
+ )
1771
+ authorityChanged(
1772
+ "Telegram Workspace writer closure authority changed before protocol installation.",
1773
+ );
1774
+ state.writerProtocolMode = candidate;
1775
+ delete state.fence;
1776
+ return {
1777
+ result: {
1778
+ mode: { ...candidate, installedBy: { ...candidate.installedBy } },
1779
+ resumed: false,
1780
+ },
1781
+ changed: true,
1782
+ };
1783
+ },
1784
+ );
1368
1785
  }
1369
1786
 
1370
- function acquireJournalWriterAdmission(input: { operationId: string; recoveryKey: string;
1371
- startupAuthorityId: string; closureOperationId: string; writerInventorySha256: string }):
1372
- TelegramWorkspaceAdmissionAcquireResult {
1787
+ function acquireJournalWriterAdmission(input: {
1788
+ operationId: string;
1789
+ recoveryKey: string;
1790
+ startupAuthorityId: string;
1791
+ closureOperationId: string;
1792
+ writerInventorySha256: string;
1793
+ }): TelegramWorkspaceAdmissionAcquireResult {
1373
1794
  validateOperationId(input.operationId);
1374
- if (!isBoundedText(input.recoveryKey) || !isBoundedText(input.startupAuthorityId) ||
1375
- !isBoundedText(input.closureOperationId) || !/^[a-f0-9]{64}$/u.test(input.writerInventorySha256))
1376
- invalidInput("Telegram Workspace journal writer protocol authority is invalid.");
1795
+ if (
1796
+ !isBoundedText(input.recoveryKey) ||
1797
+ !isBoundedText(input.startupAuthorityId) ||
1798
+ !isBoundedText(input.closureOperationId) ||
1799
+ !/^[a-f0-9]{64}$/u.test(input.writerInventorySha256)
1800
+ )
1801
+ invalidInput(
1802
+ "Telegram Workspace journal writer protocol authority is invalid.",
1803
+ );
1377
1804
  return transact<TelegramWorkspaceAdmissionAcquireResult>((state) => {
1378
1805
  const mode = state.writerProtocolMode;
1379
- if (!mode || mode.recoveryKey !== input.recoveryKey ||
1806
+ if (
1807
+ !mode ||
1808
+ mode.recoveryKey !== input.recoveryKey ||
1380
1809
  mode.startupAuthorityId !== input.startupAuthorityId ||
1381
1810
  mode.closureOperationId !== input.closureOperationId ||
1382
- mode.writerInventorySha256 !== input.writerInventorySha256)
1383
- authorityChanged("Telegram Workspace journal writer protocol authority changed.");
1384
- const existing = state.leases.find(lease => lease.operationId === input.operationId);
1811
+ mode.writerInventorySha256 !== input.writerInventorySha256
1812
+ )
1813
+ authorityChanged(
1814
+ "Telegram Workspace journal writer protocol authority changed.",
1815
+ );
1816
+ const existing = state.leases.find(
1817
+ (lease) => lease.operationId === input.operationId,
1818
+ );
1385
1819
  const operationKind = "journal-write:custody-v3";
1386
1820
  if (existing) {
1387
- const retried: TelegramWorkspaceAdmissionLease = { operationId: input.operationId,
1388
- operationKind, profileKey: options.profileKey, scope: { kind: "profile" },
1389
- owner: { ...options.owner }, acquiredAtMs: existing.acquiredAtMs };
1821
+ const retried: TelegramWorkspaceAdmissionLease = {
1822
+ operationId: input.operationId,
1823
+ operationKind,
1824
+ profileKey: options.profileKey,
1825
+ scope: { kind: "profile" },
1826
+ owner: { ...options.owner },
1827
+ acquiredAtMs: existing.acquiredAtMs,
1828
+ };
1390
1829
  if (!areLeasesEqual(existing, retried))
1391
- authorityChanged("Telegram Workspace journal writer operation identity changed.");
1392
- return { result: { kind: "acquired", lease: cloneLease(existing), resumed: true },
1393
- changed: false };
1830
+ authorityChanged(
1831
+ "Telegram Workspace journal writer operation identity changed.",
1832
+ );
1833
+ return {
1834
+ result: {
1835
+ kind: "acquired",
1836
+ lease: cloneLease(existing),
1837
+ resumed: true,
1838
+ },
1839
+ changed: false,
1840
+ };
1394
1841
  }
1395
- if (state.fence) return { result: { kind: "blocked", reason: "retirement-fenced" },
1396
- changed: false };
1842
+ if (state.fence)
1843
+ return {
1844
+ result: { kind: "blocked", reason: "retirement-fenced" },
1845
+ changed: false,
1846
+ };
1397
1847
  if (state.leases.length >= TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES)
1398
- invalidState("Telegram Workspace admission lease capacity is exhausted.");
1399
- const lease: TelegramWorkspaceAdmissionLease = { operationId: input.operationId,
1400
- operationKind, profileKey: options.profileKey, scope: { kind: "profile" },
1401
- owner: { ...options.owner }, acquiredAtMs: getNowMs() };
1848
+ invalidState(
1849
+ "Telegram Workspace admission lease capacity is exhausted.",
1850
+ );
1851
+ const lease: TelegramWorkspaceAdmissionLease = {
1852
+ operationId: input.operationId,
1853
+ operationKind,
1854
+ profileKey: options.profileKey,
1855
+ scope: { kind: "profile" },
1856
+ owner: { ...options.owner },
1857
+ acquiredAtMs: getNowMs(),
1858
+ };
1402
1859
  state.leases.push(lease);
1403
- return { result: { kind: "acquired", lease: cloneLease(lease), resumed: false },
1404
- changed: true };
1860
+ return {
1861
+ result: { kind: "acquired", lease: cloneLease(lease), resumed: false },
1862
+ changed: true,
1863
+ };
1405
1864
  });
1406
1865
  }
1407
1866
 
1408
- function acquireDestructiveFence(input: {
1409
- operationId: string;
1410
- retirementIntentId: string;
1411
- bindingKey: string;
1412
- slot: string;
1413
- target: TelegramTarget & { threadId: number };
1414
- leaderEpoch: number | string;
1415
- retirementRequestedAtMs: number;
1416
- }, destructiveKind?: TelegramWorkspaceDeletionFenceKind): TelegramWorkspaceRetirementFenceAcquireResult {
1867
+ function acquireDestructiveFence(
1868
+ input: {
1869
+ operationId: string;
1870
+ retirementIntentId: string;
1871
+ bindingKey: string;
1872
+ slot: string;
1873
+ target: TelegramTarget & { threadId: number };
1874
+ leaderEpoch: number | string;
1875
+ retirementRequestedAtMs: number;
1876
+ },
1877
+ destructiveKind?: TelegramWorkspaceDeletionFenceKind,
1878
+ ): TelegramWorkspaceRetirementFenceAcquireResult {
1417
1879
  validateOperationId(input.operationId);
1418
1880
  const candidate = normalizeFence(
1419
1881
  {
@@ -1432,7 +1894,10 @@ export function createTelegramWorkspaceAdmissionLedger(
1432
1894
  return transact<TelegramWorkspaceRetirementFenceAcquireResult>((state) => {
1433
1895
  if (state.fence) {
1434
1896
  if (!isTelegramWorkspaceRetirementFence(state.fence)) {
1435
- return { result: { kind: "blocked", reason: "retirement-active" }, changed: false };
1897
+ return {
1898
+ result: { kind: "blocked", reason: "retirement-active" },
1899
+ changed: false,
1900
+ };
1436
1901
  }
1437
1902
  if (
1438
1903
  state.fence.phase === "fenced" &&
@@ -1451,14 +1916,20 @@ export function createTelegramWorkspaceAdmissionLedger(
1451
1916
  };
1452
1917
  }
1453
1918
  if (state.fence.operationId === candidate.operationId) {
1454
- authorityChanged("Telegram Workspace retirement fence authority changed.");
1919
+ authorityChanged(
1920
+ "Telegram Workspace retirement fence authority changed.",
1921
+ );
1455
1922
  }
1456
1923
  return {
1457
1924
  result: { kind: "blocked", reason: "retirement-active" },
1458
1925
  changed: false,
1459
1926
  };
1460
1927
  }
1461
- if (state.leases.some((lease) => scopesConflict(lease.scope, candidate.target))) {
1928
+ if (
1929
+ state.leases.some((lease) =>
1930
+ scopesConflict(lease.scope, candidate.target),
1931
+ )
1932
+ ) {
1462
1933
  return {
1463
1934
  result: { kind: "blocked", reason: "admission-active" },
1464
1935
  changed: false,
@@ -1466,23 +1937,54 @@ export function createTelegramWorkspaceAdmissionLedger(
1466
1937
  }
1467
1938
  state.fence = candidate;
1468
1939
  return {
1469
- result: { kind: "acquired", fence: cloneFence(candidate), resumed: false },
1940
+ result: {
1941
+ kind: "acquired",
1942
+ fence: cloneFence(candidate),
1943
+ resumed: false,
1944
+ },
1470
1945
  changed: true,
1471
1946
  };
1472
1947
  });
1473
1948
  }
1474
1949
 
1475
- function acquireRetirementFence(input: Parameters<TelegramWorkspaceAdmissionLedger["acquireRetirementFence"]>[0]) {
1950
+ function acquireRetirementFence(
1951
+ input: Parameters<
1952
+ TelegramWorkspaceAdmissionLedger["acquireRetirementFence"]
1953
+ >[0],
1954
+ ) {
1476
1955
  return acquireDestructiveFence(input);
1477
1956
  }
1478
1957
 
1479
1958
  function acquireThreadCleanupFence(
1480
- input: Parameters<TelegramWorkspaceAdmissionLedger["acquireThreadCleanupFence"]>[0],
1959
+ input: Parameters<
1960
+ TelegramWorkspaceAdmissionLedger["acquireThreadCleanupFence"]
1961
+ >[0],
1481
1962
  ): TelegramWorkspaceRetirementFenceAcquireResult {
1482
- return acquireDestructiveFence({ operationId: input.operationId,
1483
- retirementIntentId: input.cleanupWorkSetId, bindingKey: input.bindingKey,
1484
- slot: input.slot, target: input.target, leaderEpoch: input.leaderEpoch,
1485
- retirementRequestedAtMs: input.cleanupRequestedAtMs }, "manual-thread-cleanup");
1963
+ return acquireDestructiveFence(
1964
+ {
1965
+ operationId: input.operationId,
1966
+ retirementIntentId: input.cleanupWorkSetId,
1967
+ bindingKey: input.bindingKey,
1968
+ slot: input.slot,
1969
+ target: input.target,
1970
+ leaderEpoch: input.leaderEpoch,
1971
+ retirementRequestedAtMs: input.cleanupRequestedAtMs,
1972
+ },
1973
+ "manual-thread-cleanup",
1974
+ );
1975
+ }
1976
+
1977
+ // Exact profile/kind/owner preflight shared by owner-held fence transitions; the transaction still rechecks state.
1978
+ function assertOwnedFence(
1979
+ expected: TelegramWorkspaceRetirementFence,
1980
+ expectedKind: TelegramWorkspaceDeletionFenceKind,
1981
+ ownerMessage: string,
1982
+ ): void {
1983
+ validateExpectedFence(expected, options.profileKey);
1984
+ if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1985
+ authorityChanged("Telegram Workspace destructive fence kind changed.");
1986
+ if (!areOwnersEqual(expected.owner, options.owner))
1987
+ authorityChanged(ownerMessage);
1486
1988
  }
1487
1989
 
1488
1990
  function adoptRetirementFence(
@@ -1496,11 +1998,18 @@ export function createTelegramWorkspaceAdmissionLedger(
1496
1998
  validateExpectedFence(expected, options.profileKey);
1497
1999
  if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1498
2000
  authorityChanged("Telegram Workspace destructive fence kind changed.");
1499
- if (!normalizeOwner(replacement.owner) || !isLeaderEpoch(replacement.leaderEpoch)) {
1500
- invalidInput("Telegram Workspace retirement successor authority is invalid.");
2001
+ if (
2002
+ !normalizeOwner(replacement.owner) ||
2003
+ !isLeaderEpoch(replacement.leaderEpoch)
2004
+ ) {
2005
+ invalidInput(
2006
+ "Telegram Workspace retirement successor authority is invalid.",
2007
+ );
1501
2008
  }
1502
2009
  if (!areOwnersEqual(replacement.owner, options.owner)) {
1503
- authorityChanged("Telegram Workspace retirement adoption requires successor owner authority.");
2010
+ authorityChanged(
2011
+ "Telegram Workspace retirement adoption requires successor owner authority.",
2012
+ );
1504
2013
  }
1505
2014
  const adopted = {
1506
2015
  ...cloneFence(expected),
@@ -1509,13 +2018,17 @@ export function createTelegramWorkspaceAdmissionLedger(
1509
2018
  } satisfies TelegramWorkspaceRetirementFence;
1510
2019
  return transact((state) => {
1511
2020
  if (!state.fence || !isTelegramWorkspaceRetirementFence(state.fence)) {
1512
- authorityChanged("Telegram Workspace retirement fence is absent or has another kind.");
2021
+ authorityChanged(
2022
+ "Telegram Workspace retirement fence is absent or has another kind.",
2023
+ );
1513
2024
  }
1514
2025
  if (areFencesEqual(state.fence, adopted)) {
1515
2026
  return { result: cloneFence(state.fence), changed: false };
1516
2027
  }
1517
2028
  if (!areFencesEqual(state.fence, expected)) {
1518
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2029
+ authorityChanged(
2030
+ "Telegram Workspace retirement fence authority changed.",
2031
+ );
1519
2032
  }
1520
2033
  state.fence = adopted;
1521
2034
  return { result: cloneFence(adopted), changed: true };
@@ -1526,15 +2039,16 @@ export function createTelegramWorkspaceAdmissionLedger(
1526
2039
  expected: TelegramWorkspaceRetirementFence,
1527
2040
  expectedKind: TelegramWorkspaceDeletionFenceKind = "pressure-retirement",
1528
2041
  ): TelegramWorkspaceDeletionPermitResult {
1529
- validateExpectedFence(expected, options.profileKey);
1530
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1531
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1532
- if (!areOwnersEqual(expected.owner, options.owner)) {
1533
- authorityChanged("Telegram Workspace deletion permit requires fence owner authority.");
1534
- }
2042
+ assertOwnedFence(
2043
+ expected,
2044
+ expectedKind,
2045
+ "Telegram Workspace deletion permit requires fence owner authority.",
2046
+ );
1535
2047
  return transact<TelegramWorkspaceDeletionPermitResult>((state) => {
1536
2048
  if (!state.fence || !isTelegramWorkspaceRetirementFence(state.fence)) {
1537
- authorityChanged("Telegram Workspace retirement fence is absent or has another kind.");
2049
+ authorityChanged(
2050
+ "Telegram Workspace retirement fence is absent or has another kind.",
2051
+ );
1538
2052
  }
1539
2053
  if (
1540
2054
  state.fence.operationId === expected.operationId &&
@@ -1547,7 +2061,9 @@ export function createTelegramWorkspaceAdmissionLedger(
1547
2061
  };
1548
2062
  }
1549
2063
  if (!areFencesEqual(state.fence, expected)) {
1550
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2064
+ authorityChanged(
2065
+ "Telegram Workspace retirement fence authority changed.",
2066
+ );
1551
2067
  }
1552
2068
  if (state.fence.phase !== "fenced") {
1553
2069
  return {
@@ -1567,7 +2083,9 @@ export function createTelegramWorkspaceAdmissionLedger(
1567
2083
  kind: "issued",
1568
2084
  fence: cloneFence(issued),
1569
2085
  permit: {
1570
- ...(issued.destructiveKind === undefined ? {} : { destructiveKind: issued.destructiveKind }),
2086
+ ...(issued.destructiveKind === undefined
2087
+ ? {}
2088
+ : { destructiveKind: issued.destructiveKind }),
1571
2089
  operationId: issued.operationId,
1572
2090
  retirementIntentId: issued.retirementIntentId,
1573
2091
  profileKey: issued.profileKey,
@@ -1587,18 +2105,21 @@ export function createTelegramWorkspaceAdmissionLedger(
1587
2105
  expected: TelegramWorkspaceRetirementFence,
1588
2106
  expectedKind: TelegramWorkspaceDeletionFenceKind = "pressure-retirement",
1589
2107
  ): TelegramWorkspaceRetirementFence {
1590
- validateExpectedFence(expected, options.profileKey);
1591
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1592
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1593
- if (!areOwnersEqual(expected.owner, options.owner)) {
1594
- authorityChanged("Telegram Workspace absence confirmation requires fence owner authority.");
1595
- }
2108
+ assertOwnedFence(
2109
+ expected,
2110
+ expectedKind,
2111
+ "Telegram Workspace absence confirmation requires fence owner authority.",
2112
+ );
1596
2113
  if (expected.phase === "deletion-rejected") {
1597
- authorityChanged("Rejected Telegram Workspace deletion cannot confirm absence.");
2114
+ authorityChanged(
2115
+ "Rejected Telegram Workspace deletion cannot confirm absence.",
2116
+ );
1598
2117
  }
1599
2118
  return transact((state) => {
1600
2119
  if (!state.fence || !isTelegramWorkspaceRetirementFence(state.fence)) {
1601
- authorityChanged("Telegram Workspace retirement fence is absent or has another kind.");
2120
+ authorityChanged(
2121
+ "Telegram Workspace retirement fence is absent or has another kind.",
2122
+ );
1602
2123
  }
1603
2124
  if (
1604
2125
  state.fence.operationId === expected.operationId &&
@@ -1608,7 +2129,9 @@ export function createTelegramWorkspaceAdmissionLedger(
1608
2129
  return { result: cloneFence(state.fence), changed: false };
1609
2130
  }
1610
2131
  if (!areFencesEqual(state.fence, expected)) {
1611
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2132
+ authorityChanged(
2133
+ "Telegram Workspace retirement fence authority changed.",
2134
+ );
1612
2135
  }
1613
2136
  const ready: TelegramWorkspaceRetirementFence = {
1614
2137
  ...state.fence,
@@ -1627,21 +2150,26 @@ export function createTelegramWorkspaceAdmissionLedger(
1627
2150
  expected: TelegramWorkspaceRetirementFence,
1628
2151
  expectedKind: TelegramWorkspaceDeletionFenceKind = "pressure-retirement",
1629
2152
  ): boolean {
1630
- validateExpectedFence(expected, options.profileKey);
1631
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1632
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1633
- if (!areOwnersEqual(expected.owner, options.owner)) {
1634
- authorityChanged("Telegram Workspace fence release requires owner authority.");
1635
- }
2153
+ assertOwnedFence(
2154
+ expected,
2155
+ expectedKind,
2156
+ "Telegram Workspace fence release requires owner authority.",
2157
+ );
1636
2158
  return transact((state) => {
1637
2159
  if (!state.fence) return { result: false, changed: false };
1638
2160
  if (!isTelegramWorkspaceRetirementFence(state.fence))
1639
- authorityChanged("Telegram Workspace retirement fence has another kind.");
2161
+ authorityChanged(
2162
+ "Telegram Workspace retirement fence has another kind.",
2163
+ );
1640
2164
  if (!areFencesEqual(state.fence, expected)) {
1641
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2165
+ authorityChanged(
2166
+ "Telegram Workspace retirement fence authority changed.",
2167
+ );
1642
2168
  }
1643
2169
  if (state.fence.phase !== "fenced") {
1644
- authorityChanged("Issued Telegram Workspace retirement fence cannot be released.");
2170
+ authorityChanged(
2171
+ "Issued Telegram Workspace retirement fence cannot be released.",
2172
+ );
1645
2173
  }
1646
2174
  delete state.fence;
1647
2175
  return { result: true, changed: true };
@@ -1653,83 +2181,135 @@ export function createTelegramWorkspaceAdmissionLedger(
1653
2181
  expectedKind: TelegramWorkspaceDeletionFenceKind,
1654
2182
  requiredPhase: "commit-ready" | "deletion-rejected",
1655
2183
  ): boolean {
1656
- validateExpectedFence(expected, options.profileKey);
1657
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== expectedKind)
1658
- authorityChanged("Telegram Workspace destructive fence kind changed.");
1659
- if (!areOwnersEqual(expected.owner, options.owner)) {
1660
- authorityChanged("Telegram Workspace fence completion requires owner authority.");
1661
- }
2184
+ assertOwnedFence(
2185
+ expected,
2186
+ expectedKind,
2187
+ "Telegram Workspace fence completion requires owner authority.",
2188
+ );
1662
2189
  return transact((state) => {
1663
2190
  if (!state.fence) return { result: false, changed: false };
1664
2191
  if (!isTelegramWorkspaceRetirementFence(state.fence))
1665
- authorityChanged("Telegram Workspace retirement fence has another kind.");
2192
+ authorityChanged(
2193
+ "Telegram Workspace retirement fence has another kind.",
2194
+ );
1666
2195
  if (!areFencesEqual(state.fence, expected)) {
1667
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2196
+ authorityChanged(
2197
+ "Telegram Workspace retirement fence authority changed.",
2198
+ );
1668
2199
  }
1669
2200
  if (state.fence.phase !== requiredPhase) {
1670
- authorityChanged(requiredPhase === "commit-ready"
1671
- ? "Telegram Workspace retirement commit is not ready."
1672
- : "Telegram Workspace retirement rejection is not confirmed.");
2201
+ authorityChanged(
2202
+ requiredPhase === "commit-ready"
2203
+ ? "Telegram Workspace retirement commit is not ready."
2204
+ : "Telegram Workspace retirement rejection is not confirmed.",
2205
+ );
1673
2206
  }
1674
2207
  delete state.fence;
1675
2208
  return { result: true, changed: true };
1676
2209
  });
1677
2210
  }
1678
2211
 
1679
- function confirmRetirementRejection(expected: TelegramWorkspaceRetirementFence): TelegramWorkspaceRetirementFence {
2212
+ function confirmRetirementRejection(
2213
+ expected: TelegramWorkspaceRetirementFence,
2214
+ ): TelegramWorkspaceRetirementFence {
1680
2215
  validateExpectedFence(expected, options.profileKey);
1681
- if (resolveTelegramWorkspaceDestructiveFenceKind(expected) !== "pressure-retirement" ||
1682
- !areOwnersEqual(expected.owner, options.owner) ||
1683
- (expected.phase !== "deletion-issued" && expected.phase !== "deletion-rejected")) {
1684
- authorityChanged("Telegram Workspace rejection requires exact issued retirement authority.");
2216
+ if (
2217
+ resolveTelegramWorkspaceDestructiveFenceKind(expected) !==
2218
+ "pressure-retirement" ||
2219
+ !areOwnersEqual(expected.owner, options.owner) ||
2220
+ (expected.phase !== "deletion-issued" &&
2221
+ expected.phase !== "deletion-rejected")
2222
+ ) {
2223
+ authorityChanged(
2224
+ "Telegram Workspace rejection requires exact issued retirement authority.",
2225
+ );
1685
2226
  }
1686
2227
  return transact((state) => {
1687
2228
  const current = state.fence;
1688
- if (!current || !isTelegramWorkspaceRetirementFence(current) || !areFenceBasesEqual(current, expected) ||
1689
- (current.phase !== "deletion-issued" && current.phase !== "deletion-rejected") ||
1690
- current.deletionIssuedAtMs !== expected.deletionIssuedAtMs ||
1691
- (expected.phase === "deletion-rejected" && !areFencesEqual(current, expected))) {
1692
- authorityChanged("Telegram Workspace retirement fence authority changed.");
2229
+ if (
2230
+ !current ||
2231
+ !isTelegramWorkspaceRetirementFence(current) ||
2232
+ !areFenceBasesEqual(current, expected) ||
2233
+ (current.phase !== "deletion-issued" &&
2234
+ current.phase !== "deletion-rejected") ||
2235
+ current.deletionIssuedAtMs !== expected.deletionIssuedAtMs ||
2236
+ (expected.phase === "deletion-rejected" &&
2237
+ !areFencesEqual(current, expected))
2238
+ ) {
2239
+ authorityChanged(
2240
+ "Telegram Workspace retirement fence authority changed.",
2241
+ );
1693
2242
  }
1694
- if (current.phase === "deletion-rejected") return { result: cloneFence(current), changed: false };
1695
- const rejected: TelegramWorkspaceRetirementFence = { ...current,
1696
- destructiveKind: "pressure-retirement", phase: "deletion-rejected", rejectionConfirmedAtMs: getNowMs() };
2243
+ if (current.phase === "deletion-rejected")
2244
+ return { result: cloneFence(current), changed: false };
2245
+ const rejected: TelegramWorkspaceRetirementFence = {
2246
+ ...current,
2247
+ destructiveKind: "pressure-retirement",
2248
+ phase: "deletion-rejected",
2249
+ rejectionConfirmedAtMs: getNowMs(),
2250
+ };
1697
2251
  state.fence = rejected;
1698
2252
  return { result: cloneFence(rejected), changed: true };
1699
2253
  });
1700
2254
  }
1701
2255
 
1702
- function completeRetirementFence(expected: TelegramWorkspaceRetirementFence,
1703
- expectedKind: TelegramWorkspaceDeletionFenceKind = "pressure-retirement") {
2256
+ function completeRetirementFence(
2257
+ expected: TelegramWorkspaceRetirementFence,
2258
+ expectedKind: TelegramWorkspaceDeletionFenceKind = "pressure-retirement",
2259
+ ) {
1704
2260
  return completeDestructiveFence(expected, expectedKind, "commit-ready");
1705
2261
  }
1706
- function completeRejectedRetirementFence(expected: TelegramWorkspaceRetirementFence) {
1707
- return completeDestructiveFence(expected, "pressure-retirement", "deletion-rejected");
2262
+ function completeRejectedRetirementFence(
2263
+ expected: TelegramWorkspaceRetirementFence,
2264
+ ) {
2265
+ return completeDestructiveFence(
2266
+ expected,
2267
+ "pressure-retirement",
2268
+ "deletion-rejected",
2269
+ );
1708
2270
  }
1709
2271
 
1710
- function adoptThreadCleanupFence(expected: TelegramWorkspaceRetirementFence,
1711
- replacement: { owner: TelegramWorkspaceAdmissionOwner; leaderEpoch: number | string }) {
2272
+ function adoptThreadCleanupFence(
2273
+ expected: TelegramWorkspaceRetirementFence,
2274
+ replacement: {
2275
+ owner: TelegramWorkspaceAdmissionOwner;
2276
+ leaderEpoch: number | string;
2277
+ },
2278
+ ) {
1712
2279
  return adoptRetirementFence(expected, replacement, "manual-thread-cleanup");
1713
2280
  }
1714
- function issueThreadCleanupDeletionPermit(expected: TelegramWorkspaceRetirementFence) {
2281
+ function issueThreadCleanupDeletionPermit(
2282
+ expected: TelegramWorkspaceRetirementFence,
2283
+ ) {
1715
2284
  return issueDeletionPermit(expected, "manual-thread-cleanup");
1716
2285
  }
1717
- function confirmThreadCleanupAbsence(expected: TelegramWorkspaceRetirementFence) {
2286
+ function confirmThreadCleanupAbsence(
2287
+ expected: TelegramWorkspaceRetirementFence,
2288
+ ) {
1718
2289
  return confirmRetirementAbsence(expected, "manual-thread-cleanup");
1719
2290
  }
1720
- function releaseUnissuedThreadCleanupFence(expected: TelegramWorkspaceRetirementFence) {
2291
+ function releaseUnissuedThreadCleanupFence(
2292
+ expected: TelegramWorkspaceRetirementFence,
2293
+ ) {
1721
2294
  return releaseUnissuedRetirementFence(expected, "manual-thread-cleanup");
1722
2295
  }
1723
- function completeThreadCleanupFence(expected: TelegramWorkspaceRetirementFence) {
2296
+ function completeThreadCleanupFence(
2297
+ expected: TelegramWorkspaceRetirementFence,
2298
+ ) {
1724
2299
  return completeRetirementFence(expected, "manual-thread-cleanup");
1725
2300
  }
1726
2301
 
1727
2302
  return {
1728
- getProfileKey: () => stateProfile === undefined ? options.profileKey : profileKey,
1729
- getOwner: () => ({ ...(stateProfile === undefined ? options.owner : boundOwner) }),
2303
+ getProfileKey: () =>
2304
+ stateProfile === undefined ? options.profileKey : profileKey,
2305
+ getOwner: () => ({
2306
+ ...(stateProfile === undefined ? options.owner : boundOwner),
2307
+ }),
1730
2308
  listReservedSlots: () => {
1731
2309
  const fence = read().fence;
1732
- return fence && isTelegramWorkspaceRetirementFence(fence) ? [fence.slot] : [];
2310
+ return fence && isTelegramWorkspaceRetirementFence(fence)
2311
+ ? [fence.slot]
2312
+ : [];
1733
2313
  },
1734
2314
  read,
1735
2315
  acquireAdmission,