@llblab/pi-kit 0.27.5 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
  5. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
  6. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
  9. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  10. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
  11. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
  12. package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
  15. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  16. package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
  17. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
  18. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  19. package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
  20. package/node_modules/@llblab/pi-telegram/README.md +15 -5
  21. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  22. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
  26. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
  27. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
  28. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
  34. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
  35. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
  36. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
  37. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
  38. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
  39. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
  40. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
  41. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
  42. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
  43. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
  44. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
  45. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
  46. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
  47. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
  48. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
  49. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
  50. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
  51. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
  52. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
  53. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
  54. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
  55. package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
  56. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
  57. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
  58. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
  63. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
  64. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
  73. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
  74. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
  75. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
  76. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
  77. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
  78. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
  79. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
  80. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
  81. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
  82. package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
  83. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
  84. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
  86. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
  87. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
  88. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
  89. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
  90. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
  91. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
  93. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
  95. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
  97. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
  99. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
  100. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
  102. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
  103. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
  108. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
  109. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
  110. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
  111. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
  112. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
  113. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
  114. package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
  116. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
  117. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
  118. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
  119. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
  120. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
  122. package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
  123. package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
  124. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  125. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  126. package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
  127. package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
  128. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  129. package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
  131. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
  133. package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
  134. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
  135. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  136. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
  149. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
  150. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
  151. package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
  152. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
  153. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
  154. package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
  155. package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
  156. package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
  157. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
  158. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
  160. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
  161. package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
  162. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
  163. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
  164. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
  165. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
  166. package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
  167. package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
  168. package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
  169. package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
  170. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
  171. package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
  172. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
  173. package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
  174. package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
  175. package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
  176. package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
  177. package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
  178. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
  179. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
  180. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
  181. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
  182. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
  183. package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
  184. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
  185. package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
  186. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
  187. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
  188. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
  189. package/node_modules/@llblab/pi-telegram/package.json +6 -6
  190. package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
  191. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
  192. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
  193. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
  194. package/package.json +3 -3
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Non-coercing decoded wire-value inspection
3
+ * Zones: wire fields, record shape, integer bounds
4
+ * Owns shared shallow predicates; excludes schemas, lossless decoding,
5
+ * required fields, normalization, storage and execution authority.
6
+ */
7
+ /** Non-array object shape only; not a plain-object or JSON-serializability guarantee. */
8
+ export declare function isWireRecord(value: unknown): value is Record<string, unknown>;
9
+ /** Checks only own enumerable string keys, not required, inherited or symbol fields. */
10
+ export declare function hasOnlyWireKeys(value: Record<string, unknown>, allowedKeys: readonly string[]): boolean;
11
+ export declare function isNonEmptyWireString(value: unknown): value is string;
12
+ export declare function isNonNegativeWireInteger(value: unknown): value is number;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Non-coercing decoded wire-value inspection
3
+ * Zones: wire fields, record shape, integer bounds
4
+ * Owns shared shallow predicates; excludes schemas, lossless decoding,
5
+ * required fields, normalization, storage and execution authority.
6
+ */
7
+ /** Non-array object shape only; not a plain-object or JSON-serializability guarantee. */
8
+ export function isWireRecord(value) {
9
+ return !!value && typeof value === "object" && !Array.isArray(value);
10
+ }
11
+ /** Checks only own enumerable string keys, not required, inherited or symbol fields. */
12
+ export function hasOnlyWireKeys(value, allowedKeys) {
13
+ const allowed = new Set(allowedKeys);
14
+ return Object.keys(value).every(key => allowed.has(key));
15
+ }
16
+ export function isNonEmptyWireString(value) {
17
+ return typeof value === "string" && value.length > 0;
18
+ }
19
+ export function isNonNegativeWireInteger(value) {
20
+ return Number.isSafeInteger(value) && value >= 0;
21
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { type PathLike } from "node:fs";
8
8
  import { type TelegramProcessLiveness } from "./bus.ts";
9
- import type { TelegramTarget } from "./target.ts";
9
+ import { type TelegramTarget } from "./target.ts";
10
10
  export interface TelegramWorkspaceAdmissionOwner {
11
11
  processId: number;
12
12
  processBirthId: string;
@@ -22,7 +22,7 @@ export type TelegramWorkspaceAdmissionScope = {
22
22
  } | {
23
23
  kind: "profile";
24
24
  };
25
- export interface TelegramWorkspaceAdmissionLease {
25
+ interface TelegramWorkspaceAdmissionLease {
26
26
  operationId: string;
27
27
  operationKind: string;
28
28
  profileKey: string;
@@ -30,9 +30,9 @@ export interface TelegramWorkspaceAdmissionLease {
30
30
  owner: TelegramWorkspaceAdmissionOwner;
31
31
  acquiredAtMs: number;
32
32
  }
33
- export type TelegramWorkspaceDestructiveFenceKind = "pressure-retirement" | "manual-thread-cleanup" | "journal-writer-closure";
33
+ type TelegramWorkspaceDestructiveFenceKind = "pressure-retirement" | "manual-thread-cleanup" | "journal-writer-closure";
34
34
  export type TelegramWorkspaceDeletionFenceKind = Exclude<TelegramWorkspaceDestructiveFenceKind, "journal-writer-closure">;
35
- export interface TelegramWorkspaceJournalWriterClosureFence {
35
+ interface TelegramWorkspaceJournalWriterClosureFence {
36
36
  destructiveKind: "journal-writer-closure";
37
37
  phase: "fenced";
38
38
  operationId: string;
@@ -43,7 +43,7 @@ export interface TelegramWorkspaceJournalWriterClosureFence {
43
43
  acquiredAtMs: number;
44
44
  }
45
45
  export declare function normalizeTelegramWorkspaceJournalWriterClosureFence(value: unknown, profileKey: string): TelegramWorkspaceJournalWriterClosureFence | undefined;
46
- export interface TelegramWorkspaceJournalWriterProtocolMode {
46
+ interface TelegramWorkspaceJournalWriterProtocolMode {
47
47
  version: 1;
48
48
  protocol: "custody-v3";
49
49
  profileKey: string;
@@ -100,8 +100,7 @@ export interface TelegramWorkspaceDeletionPermit {
100
100
  readonly leaderEpoch: number | string;
101
101
  readonly issuedAtMs: number;
102
102
  }
103
- export type TelegramWorkspaceAdmissionBlockReason = "retirement-fenced" | "admission-active" | "retirement-active";
104
- export type TelegramWorkspaceAdmissionAcquireResult = {
103
+ type TelegramWorkspaceAdmissionAcquireResult = {
105
104
  kind: "acquired";
106
105
  lease: TelegramWorkspaceAdmissionLease;
107
106
  resumed: boolean;
@@ -109,7 +108,7 @@ export type TelegramWorkspaceAdmissionAcquireResult = {
109
108
  kind: "blocked";
110
109
  reason: "retirement-fenced";
111
110
  };
112
- export type TelegramWorkspaceRetirementFenceAcquireResult = {
111
+ type TelegramWorkspaceRetirementFenceAcquireResult = {
113
112
  kind: "acquired";
114
113
  fence: TelegramWorkspaceRetirementFence;
115
114
  resumed: boolean;
@@ -117,11 +116,11 @@ export type TelegramWorkspaceRetirementFenceAcquireResult = {
117
116
  kind: "blocked";
118
117
  reason: "admission-active" | "retirement-active";
119
118
  };
120
- export type TelegramWorkspaceJournalWriterProtocolInstallResult = {
119
+ type TelegramWorkspaceJournalWriterProtocolInstallResult = {
121
120
  mode: TelegramWorkspaceJournalWriterProtocolMode;
122
121
  resumed: boolean;
123
122
  };
124
- export type TelegramWorkspaceJournalWriterClosureAcquireResult = {
123
+ type TelegramWorkspaceJournalWriterClosureAcquireResult = {
125
124
  kind: "acquired";
126
125
  fence: TelegramWorkspaceJournalWriterClosureFence;
127
126
  resumed: boolean;
@@ -129,7 +128,7 @@ export type TelegramWorkspaceJournalWriterClosureAcquireResult = {
129
128
  kind: "blocked";
130
129
  reason: "admission-active" | "retirement-active";
131
130
  };
132
- export type TelegramWorkspaceDeletionPermitResult = {
131
+ type TelegramWorkspaceDeletionPermitResult = {
133
132
  kind: "issued";
134
133
  fence: TelegramWorkspaceRetirementFence;
135
134
  permit: TelegramWorkspaceDeletionPermit;
@@ -137,15 +136,17 @@ export type TelegramWorkspaceDeletionPermitResult = {
137
136
  kind: "already-issued";
138
137
  fence: TelegramWorkspaceRetirementFence;
139
138
  };
140
- export interface TelegramWorkspaceAdmissionLedgerSnapshot {
139
+ interface TelegramWorkspaceAdmissionLedgerSnapshot {
141
140
  profileKey: string;
142
141
  leases: TelegramWorkspaceAdmissionLease[];
143
142
  fence?: TelegramWorkspaceDestructiveFence;
144
143
  writerProtocolMode?: TelegramWorkspaceJournalWriterProtocolMode;
145
144
  }
146
- export type TelegramWorkspaceAdmissionPublicationBoundary = "before-write" | "after-write-before-rename";
147
- export interface TelegramWorkspaceAdmissionLedgerOptions {
145
+ type TelegramWorkspaceAdmissionPublicationBoundary = "before-write" | "after-write-before-rename";
146
+ interface TelegramWorkspaceAdmissionLedgerOptions {
148
147
  path: string;
148
+ /** Explicit logical section identity for the consolidated root; absent means the standalone ledger adapter. */
149
+ stateProfile?: string;
149
150
  profileKey: string;
150
151
  owner: TelegramWorkspaceAdmissionOwner;
151
152
  getNowMs?: () => number;
@@ -232,22 +233,29 @@ export interface TelegramWorkspaceAdmissionLedger {
232
233
  export declare function resolveTelegramWorkspaceDestructiveFenceKind(fence: {
233
234
  destructiveKind?: TelegramWorkspaceDestructiveFenceKind;
234
235
  }): TelegramWorkspaceDestructiveFenceKind;
236
+ /** Pure damage check for one consolidated admission section under its own stored profile key; throws when invalid. */
237
+ export declare function assertTelegramConsolidatedAdmissionSection(value: unknown, stateProfile: string): void;
235
238
  export declare function createTelegramWorkspaceAdmissionOperationId(): string;
236
239
  export declare function createTelegramWorkspaceAdmissionProfileKey(input: {
237
240
  profileName?: string;
238
241
  botToken: string;
239
242
  }): string;
240
- export interface TelegramWorkspaceAdmissionRuntimeBinding {
243
+ interface TelegramWorkspaceAdmissionRuntimeBinding {
241
244
  resolve: () => TelegramWorkspaceAdmissionLedger | undefined;
242
245
  }
243
246
  export declare function createTelegramWorkspaceAdmissionRuntimeBinding(input: {
244
247
  getProfileName: () => string | undefined;
245
248
  getBotToken: () => string | undefined;
246
- getPath: (profileName?: string) => string;
247
249
  owner: TelegramWorkspaceAdmissionOwner;
248
250
  getNowMs?: () => number;
249
251
  getProcessLiveness?: (owner: TelegramWorkspaceAdmissionOwner) => TelegramProcessLiveness;
250
- }): TelegramWorkspaceAdmissionRuntimeBinding;
252
+ } & ({
253
+ getPath: (profileName?: string) => string;
254
+ getStatePath?: never;
255
+ } | {
256
+ getStatePath: () => string;
257
+ getPath?: never;
258
+ })): TelegramWorkspaceAdmissionRuntimeBinding;
251
259
  export declare function runWithTelegramWorkspaceAdmissions<T>(input: {
252
260
  ledger: Pick<TelegramWorkspaceAdmissionLedger, "acquireAdmission" | "releaseAdmission">;
253
261
  operationId: string;
@@ -7,8 +7,11 @@
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { chmodSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
9
9
  import { dirname } from "node:path";
10
+ import { isDeepStrictEqual } from "node:util";
10
11
  import { getTelegramProcessLiveness, } from "./bus.js";
11
- import { renameTelegramPathWithRetry, withTelegramFileTransaction, } from "./locks.js";
12
+ import { renameTelegramPathWithRetry, withTelegramFileTransaction, readTelegramRuntimeState, mutateTelegramRuntimeStateSection, TelegramRuntimeStateError, } from "./locks.js";
13
+ import { areTelegramTargetsEqual as areTargetsEqual } from "./target.js";
14
+ import { isWireRecord as isRecord, isNonNegativeWireInteger as isSafeTimestamp } from "./wire.js";
12
15
  const TELEGRAM_WORKSPACE_ADMISSION_VERSION = 1;
13
16
  const TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES = 4096;
14
17
  const TELEGRAM_WORKSPACE_ADMISSION_MAX_TEXT = 512;
@@ -71,12 +74,6 @@ function invalidState(message, cause) {
71
74
  function authorityChanged(message) {
72
75
  throw new TelegramWorkspaceAdmissionError("authority-changed", message);
73
76
  }
74
- function isRecord(value) {
75
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
76
- }
77
- function isSafeTimestamp(value) {
78
- return Number.isSafeInteger(value) && value >= 0;
79
- }
80
77
  function isBoundedText(value) {
81
78
  return (typeof value === "string" &&
82
79
  value.length > 0 &&
@@ -198,8 +195,9 @@ function normalizeFence(value, profileKey) {
198
195
  if (value.phase === "deletion-rejected" &&
199
196
  (value.destructiveKind === undefined || value.destructiveKind === "pressure-retirement") &&
200
197
  isSafeTimestamp(value.deletionIssuedAtMs) && isSafeTimestamp(value.rejectionConfirmedAtMs)) {
201
- return { ...base, destructiveKind: value.destructiveKind, phase: "deletion-rejected",
202
- deletionIssuedAtMs: value.deletionIssuedAtMs, rejectionConfirmedAtMs: value.rejectionConfirmedAtMs };
198
+ const { destructiveKind: _kind, ...pressureBase } = base;
199
+ return { ...pressureBase, ...(value.destructiveKind === "pressure-retirement" ? { destructiveKind: "pressure-retirement" } : {}),
200
+ phase: "deletion-rejected", deletionIssuedAtMs: value.deletionIssuedAtMs, rejectionConfirmedAtMs: value.rejectionConfirmedAtMs };
203
201
  }
204
202
  if (value.phase === "commit-ready" &&
205
203
  isSafeTimestamp(value.absenceConfirmedAtMs) &&
@@ -254,9 +252,34 @@ function readState(path, profileKey) {
254
252
  catch (error) {
255
253
  invalidState("Telegram Workspace admission state is malformed.", error);
256
254
  }
257
- if (!isRecord(parsed)) {
258
- invalidState("Telegram Workspace admission state is invalid.");
255
+ return normalizeAdmissionState(parsed, profileKey);
256
+ }
257
+ function isConsolidatedAdmissionKey(value, profile) {
258
+ if (!isBoundedText(value))
259
+ return false;
260
+ try {
261
+ const key = JSON.parse(value);
262
+ return isRecord(key) && Object.keys(key).length === 3 && key.version === TELEGRAM_WORKSPACE_ADMISSION_VERSION && key.profile === profile &&
263
+ isRecord(key.bot) && Object.keys(key.bot).length === 1 && typeof key.bot.tokenSha256 === "string" && /^[a-f0-9]{64}$/u.test(key.bot.tokenSha256);
259
264
  }
265
+ catch {
266
+ return false;
267
+ }
268
+ }
269
+ /** Pure damage check for one consolidated admission section under its own stored profile key; throws when invalid. */
270
+ export function assertTelegramConsolidatedAdmissionSection(value, stateProfile) {
271
+ if (value === undefined)
272
+ return;
273
+ normalizeAdmissionState(value, isRecord(value) && typeof value.profileKey === "string" ? value.profileKey : "", stateProfile);
274
+ }
275
+ function normalizeAdmissionState(parsed, profileKey, stateProfile) {
276
+ if (parsed === undefined)
277
+ return { version: TELEGRAM_WORKSPACE_ADMISSION_VERSION, profileKey, leases: [] };
278
+ if (!isRecord(parsed))
279
+ invalidState("Telegram Workspace admission state is invalid.");
280
+ if (stateProfile !== undefined && (Object.keys(parsed).some(key => !["version", "profileKey", "leases", "fence", "writerProtocolMode"].includes(key)) ||
281
+ !isConsolidatedAdmissionKey(parsed.profileKey, stateProfile)))
282
+ invalidState("Telegram consolidated admission section identity is invalid.");
260
283
  if (parsed.version !== TELEGRAM_WORKSPACE_ADMISSION_VERSION ||
261
284
  !Array.isArray(parsed.leases) ||
262
285
  parsed.leases.length > TELEGRAM_WORKSPACE_ADMISSION_MAX_LEASES) {
@@ -291,6 +314,9 @@ function readState(path, profileKey) {
291
314
  invalidState("Telegram Workspace writer protocol mode is invalid.");
292
315
  if (fence && writerProtocolMode)
293
316
  invalidState("Telegram Workspace destructive fence conflicts with writer protocol mode.");
317
+ if (stateProfile !== undefined && (!isDeepStrictEqual(parsed.leases, normalizedLeases) ||
318
+ !isDeepStrictEqual(parsed.fence, fence) || !isDeepStrictEqual(parsed.writerProtocolMode, writerProtocolMode)))
319
+ invalidState("Telegram consolidated admission evidence cannot drop unknown fields.");
294
320
  return {
295
321
  version: TELEGRAM_WORKSPACE_ADMISSION_VERSION,
296
322
  profileKey,
@@ -331,9 +357,6 @@ function writeState(path, state, options) {
331
357
  }
332
358
  }
333
359
  }
334
- function areTargetsEqual(left, right) {
335
- return left.chatId === right.chatId && left.threadId === right.threadId;
336
- }
337
360
  function scopesConflict(scope, target) {
338
361
  if (scope.kind === "profile")
339
362
  return true;
@@ -420,6 +443,8 @@ function validateOptions(options) {
420
443
  if (!normalizeOwner(options.owner)) {
421
444
  invalidInput("Telegram Workspace admission owner is invalid.");
422
445
  }
446
+ if (options.stateProfile !== undefined && (!isBoundedText(options.stateProfile) || !isConsolidatedAdmissionKey(options.profileKey, options.stateProfile)))
447
+ invalidInput("Telegram consolidated admission requires an exact bot/profile section identity.");
423
448
  }
424
449
  function validateOperationId(operationId) {
425
450
  if (!isBoundedText(operationId)) {
@@ -458,18 +483,22 @@ export function createTelegramWorkspaceAdmissionProfileKey(input) {
458
483
  });
459
484
  }
460
485
  export function createTelegramWorkspaceAdmissionRuntimeBinding(input) {
486
+ const { getProfileName, getBotToken, getPath, getStatePath } = input;
487
+ if ((getPath === undefined) === (getStatePath === undefined))
488
+ invalidInput("Telegram admission binding must select one storage identity.");
461
489
  return {
462
490
  resolve() {
463
- const botToken = input.getBotToken();
491
+ const botToken = getBotToken();
464
492
  if (!botToken)
465
493
  return undefined;
466
- const configuredProfileName = input.getProfileName();
494
+ const configuredProfileName = getProfileName();
467
495
  const profileKey = createTelegramWorkspaceAdmissionProfileKey({
468
496
  profileName: configuredProfileName ?? "default",
469
497
  botToken,
470
498
  });
471
499
  return createTelegramWorkspaceAdmissionLedger({
472
- path: input.getPath(configuredProfileName),
500
+ path: getStatePath ? getStatePath() : getPath(configuredProfileName),
501
+ ...(getStatePath ? { stateProfile: configuredProfileName ?? "default" } : {}),
473
502
  profileKey,
474
503
  owner: input.owner,
475
504
  getNowMs: input.getNowMs,
@@ -651,11 +680,41 @@ export async function runWithTelegramWorkspaceAdmissionsAsync(input) {
651
680
  }
652
681
  export function createTelegramWorkspaceAdmissionLedger(options) {
653
682
  validateOptions(options);
683
+ const suppliedOptions = options;
684
+ if (options.stateProfile !== undefined)
685
+ options = { ...options, owner: { ...options.owner } };
654
686
  const getNowMs = options.getNowMs ?? Date.now;
655
687
  const getProcessLiveness = options.getProcessLiveness ??
656
688
  ((owner) => getTelegramProcessLiveness(owner));
657
689
  const transactionPath = `${options.path}.transaction`;
690
+ const path = options.path, profileKey = options.profileKey, stateProfile = options.stateProfile, boundOwner = { ...options.owner };
691
+ const publication = { publishRename: options.publishRename, onPublicationBoundary: options.onPublicationBoundary };
692
+ const isStorageCurrent = () => suppliedOptions.path === path && suppliedOptions.profileKey === profileKey &&
693
+ suppliedOptions.stateProfile === stateProfile && areOwnersEqual(suppliedOptions.owner, boundOwner);
694
+ const mapStateError = (error) => {
695
+ if (error instanceof TelegramRuntimeStateError)
696
+ throw new TelegramWorkspaceAdmissionError(error.code === "invalid" ? "invalid-state" : error.code, "Telegram consolidated admission state operation failed.", { cause: error });
697
+ throw error;
698
+ };
658
699
  function transact(operation) {
700
+ if (stateProfile !== undefined) {
701
+ try {
702
+ return mutateTelegramRuntimeStateSection(path, stateProfile, "admission", current => {
703
+ const state = normalizeAdmissionState(current, profileKey, stateProfile);
704
+ const retained = state.leases.filter(lease => getProcessLiveness(lease.owner) !== "dead");
705
+ const pruned = retained.length !== state.leases.length;
706
+ state.leases = retained;
707
+ const outcome = operation(state);
708
+ return { value: pruned || outcome.changed ? normalizeAdmissionState(state, profileKey, stateProfile) : current, result: outcome.result };
709
+ }, { isCurrent: isStorageCurrent, publishRename: publication.publishRename, onPublicationBoundary(boundary) {
710
+ if (boundary !== "after-rename")
711
+ publication.onPublicationBoundary?.(boundary, path);
712
+ } });
713
+ }
714
+ catch (error) {
715
+ return mapStateError(error);
716
+ }
717
+ }
659
718
  return withTelegramFileTransaction(transactionPath, () => {
660
719
  const state = readState(options.path, options.profileKey);
661
720
  const retainedLeases = state.leases.filter((lease) => getProcessLiveness(lease.owner) !== "dead");
@@ -669,7 +728,23 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
669
728
  });
670
729
  }
671
730
  function read() {
672
- const state = readState(options.path, options.profileKey);
731
+ let state;
732
+ if (stateProfile !== undefined) {
733
+ if (!isStorageCurrent())
734
+ authorityChanged("Telegram consolidated admission storage authority changed.");
735
+ try {
736
+ const file = readTelegramRuntimeState(path);
737
+ const value = Object.hasOwn(file.profiles, stateProfile) ? file.profiles[stateProfile]?.admission : undefined;
738
+ state = normalizeAdmissionState(value, profileKey, stateProfile);
739
+ if (!isStorageCurrent())
740
+ authorityChanged("Telegram consolidated admission storage authority changed.");
741
+ }
742
+ catch (error) {
743
+ return mapStateError(error);
744
+ }
745
+ }
746
+ else
747
+ state = readState(options.path, options.profileKey);
673
748
  return {
674
749
  profileKey: state.profileKey,
675
750
  leases: state.leases.map(cloneLease),
@@ -1151,8 +1226,8 @@ export function createTelegramWorkspaceAdmissionLedger(options) {
1151
1226
  return completeRetirementFence(expected, "manual-thread-cleanup");
1152
1227
  }
1153
1228
  return {
1154
- getProfileKey: () => options.profileKey,
1155
- getOwner: () => ({ ...options.owner }),
1229
+ getProfileKey: () => stateProfile === undefined ? options.profileKey : profileKey,
1230
+ getOwner: () => ({ ...(stateProfile === undefined ? options.owner : boundOwner) }),
1156
1231
  listReservedSlots: () => {
1157
1232
  const fence = read().fence;
1158
1233
  return fence && isTelegramWorkspaceRetirementFence(fence) ? [fence.slot] : [];
@@ -0,0 +1,20 @@
1
+ export interface TelegramWorkspaceBindingIdentity {
2
+ cwd: string;
3
+ workspaceKey: string;
4
+ /** Exact durable Pi session identity; absent only on legacy cwd-only bindings. */
5
+ sessionId?: string;
6
+ /** Full SHA-256 index component for session-qualified bindings. */
7
+ sessionKey?: string;
8
+ /** Immutable legacy binding-key component, not the displayed global letter. */
9
+ instanceSlot: string;
10
+ bindingKey: string;
11
+ /** Profile-wide letter reserved by the transient claim. */
12
+ slot?: string;
13
+ }
14
+ export declare const TELEGRAM_WORKSPACE_KEY_MAX_LENGTH = 180;
15
+ export declare function normalizeTelegramSessionId(sessionId: string): string | undefined;
16
+ export declare function createTelegramSessionKey(sessionId: string): string | undefined;
17
+ export declare function normalizeTelegramWorkspacePath(cwd: string): string | undefined;
18
+ export declare function createTelegramWorkspaceDirectoryKey(cwd: string): string | undefined;
19
+ export declare function createTelegramWorkspaceBindingIdentityWithKey(cwd: string, workspaceKey: string, ordinal: number, sessionId?: string): TelegramWorkspaceBindingIdentity | undefined;
20
+ export declare function createTelegramWorkspaceBindingIdentity(cwd: string, ordinal?: number, sessionId?: string): TelegramWorkspaceBindingIdentity | undefined;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Durable Workspace identity value construction
3
+ * Zones: workspace identity, session identity, key encoding
4
+ * Owns exact session/CWD normalization, bounded keys and local instance-slot encoding.
5
+ * Excludes allocation, bindings/targets, storage-reference approval, custody and effects.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { posix, resolve } from "node:path";
9
+ export const TELEGRAM_WORKSPACE_KEY_MAX_LENGTH = 180;
10
+ const TELEGRAM_SESSION_ID_MAX_LENGTH = 256;
11
+ export function normalizeTelegramSessionId(sessionId) {
12
+ if (typeof sessionId !== "string")
13
+ return undefined;
14
+ const normalized = sessionId.trim();
15
+ return normalized && Buffer.byteLength(normalized, "utf8") <=
16
+ TELEGRAM_SESSION_ID_MAX_LENGTH
17
+ ? normalized
18
+ : undefined;
19
+ }
20
+ export function createTelegramSessionKey(sessionId) {
21
+ const normalized = normalizeTelegramSessionId(sessionId);
22
+ return normalized
23
+ ? createHash("sha256").update(normalized).digest("hex")
24
+ : undefined;
25
+ }
26
+ export function normalizeTelegramWorkspacePath(cwd) {
27
+ const trimmed = cwd.trim();
28
+ if (!trimmed)
29
+ return undefined;
30
+ const normalized = (trimmed.startsWith("/")
31
+ ? posix.normalize(trimmed)
32
+ : resolve(trimmed).replaceAll("\\", "/"));
33
+ const withoutTrailingSeparators = normalized.length > 1 ? normalized.replace(/\/+$/u, "") : normalized;
34
+ return process.platform === "win32"
35
+ ? withoutTrailingSeparators.replace(/^([A-Z]):/u, (_, drive) => `${drive.toLowerCase()}:`)
36
+ : withoutTrailingSeparators;
37
+ }
38
+ export function createTelegramWorkspaceDirectoryKey(cwd) {
39
+ const normalized = normalizeTelegramWorkspacePath(cwd);
40
+ if (!normalized)
41
+ return undefined;
42
+ const readable = normalized
43
+ .replace(/[^\p{L}\p{N}._-]+/gu, "-")
44
+ .replace(/^-+|-+$/gu, "") || "root";
45
+ const candidate = `--${readable}--`;
46
+ if (candidate.length <= TELEGRAM_WORKSPACE_KEY_MAX_LENGTH)
47
+ return candidate;
48
+ const digest = createHash("sha256")
49
+ .update(normalized)
50
+ .digest("hex")
51
+ .slice(0, 12);
52
+ const prefixLength = TELEGRAM_WORKSPACE_KEY_MAX_LENGTH - digest.length - 5;
53
+ return `--${readable.slice(0, prefixLength)}-${digest}--`;
54
+ }
55
+ function createTelegramWorkspaceInstanceSlot(ordinal) {
56
+ if (!Number.isSafeInteger(ordinal) || ordinal < 0)
57
+ return undefined;
58
+ let value = ordinal + 1;
59
+ let slot = "";
60
+ while (value > 0) {
61
+ value -= 1;
62
+ slot = String.fromCharCode(97 + (value % 26)) + slot;
63
+ value = Math.floor(value / 26);
64
+ }
65
+ return slot;
66
+ }
67
+ export function createTelegramWorkspaceBindingIdentityWithKey(cwd, workspaceKey, ordinal, sessionId) {
68
+ const instanceSlot = createTelegramWorkspaceInstanceSlot(ordinal);
69
+ if (!instanceSlot)
70
+ return undefined;
71
+ const normalizedSessionId = sessionId === undefined
72
+ ? undefined
73
+ : normalizeTelegramSessionId(sessionId);
74
+ const sessionKey = normalizedSessionId
75
+ ? createTelegramSessionKey(normalizedSessionId)
76
+ : undefined;
77
+ if (sessionId !== undefined && (!normalizedSessionId || !sessionKey)) {
78
+ return undefined;
79
+ }
80
+ const legacyBindingKey = instanceSlot === "a"
81
+ ? workspaceKey
82
+ : `${workspaceKey}${instanceSlot}`;
83
+ return {
84
+ cwd,
85
+ workspaceKey,
86
+ ...(normalizedSessionId && sessionKey
87
+ ? { sessionId: normalizedSessionId, sessionKey }
88
+ : {}),
89
+ instanceSlot,
90
+ bindingKey: sessionKey
91
+ ? `${legacyBindingKey}-s-${sessionKey}`
92
+ : legacyBindingKey,
93
+ };
94
+ }
95
+ export function createTelegramWorkspaceBindingIdentity(cwd, ordinal = 0, sessionId) {
96
+ const normalized = normalizeTelegramWorkspacePath(cwd);
97
+ const workspaceKey = normalized
98
+ ? createTelegramWorkspaceDirectoryKey(normalized)
99
+ : undefined;
100
+ if (!normalized || !workspaceKey)
101
+ return undefined;
102
+ return createTelegramWorkspaceBindingIdentityWithKey(normalized, workspaceKey, ordinal, sessionId);
103
+ }
@@ -4,10 +4,10 @@
4
4
  * Owns fail-closed protection, demand-driven pressure retirement, exact-intent admission,
5
5
  * successor recovery, and fenced one-shot deletion before durable slot reuse.
6
6
  */
7
- import type { TelegramUpdateJournalDeadQueueOwnerRecoveryInput, TelegramUpdateJournalDeadQueueOwnerRecoveryResult, TelegramUpdateJournalEntry, TelegramUpdateJournalQueueOwnerIdentity } from "./journal.ts";
8
- import type { TelegramTarget } from "./target.ts";
7
+ import type { TelegramJournalNamespaceInspection, TelegramUpdateJournalDeadQueueOwnerRecoveryInput, TelegramUpdateJournalDeadQueueOwnerRecoveryResult, TelegramUpdateJournalEntry, TelegramUpdateJournalQueueOwnerIdentity } from "./journal.ts";
8
+ import { type TelegramTarget } from "./target.ts";
9
9
  import { type TelegramWorkspaceThreadDeletionTransport } from "./telegram-api.ts";
10
- import type { TelegramTopicTargetStore, TelegramWorkspaceExternalProtectionEvidence, TelegramWorkspaceRetirementIntent, TelegramWorkspaceProtectionState, TelegramWorkspaceThreadBinding } from "./threads.ts";
10
+ import type { TelegramTopicTargetStore, TelegramWorkspaceJournalSource, TelegramWorkspaceExternalProtectionEvidence, TelegramWorkspaceRetirementIntent, TelegramWorkspaceProtectionState, TelegramWorkspaceThreadBinding } from "./threads.ts";
11
11
  import { type TelegramWorkspaceAdmissionLedger, type TelegramWorkspaceAdmissionScope, type TelegramWorkspaceDeletionPermit } from "./workspace-admission.ts";
12
12
  export interface TelegramWorkspaceOperationGate {
13
13
  runExclusive: <T>(operation: () => Promise<T>) => Promise<T>;
@@ -36,18 +36,21 @@ interface TelegramWorkspaceJournalReader {
36
36
  entries: readonly {
37
37
  update: unknown;
38
38
  }[];
39
+ exists?: boolean;
39
40
  };
40
41
  };
41
42
  readForProtection?: () => {
42
43
  entries: readonly {
43
44
  update: unknown;
44
45
  }[];
46
+ exists?: boolean;
45
47
  };
46
48
  }
47
49
  export declare function captureTelegramWorkspaceJournalProtectionSources(input: {
48
50
  binding: TelegramWorkspaceThreadBinding;
49
51
  resolveLeader: () => TelegramWorkspaceJournalReader | undefined;
50
52
  createFollowerResolver: (journalBindingKey: string) => () => TelegramWorkspaceJournalReader | undefined;
53
+ createSessionResolver?: (recipientBindingKey: string, sessionId: string) => () => TelegramWorkspaceJournalReader | undefined;
51
54
  withJournalReference?: <T>(binding: TelegramWorkspaceJournalReader, operation: () => T) => T;
52
55
  discovery?: {
53
56
  paths: readonly string[];
@@ -63,6 +66,7 @@ export type TelegramWorkspaceJournalProtectionSource = {
63
66
  kind: "binding";
64
67
  bindingKey: string;
65
68
  journalBindingKey: string;
69
+ sessionId?: string;
66
70
  } | {
67
71
  kind: "discovered";
68
72
  path: string;
@@ -70,6 +74,8 @@ export type TelegramWorkspaceJournalProtectionSource = {
70
74
  entries: readonly {
71
75
  update: unknown;
72
76
  }[];
77
+ /** Positive filesystem presence is distinct from a complete namespace with a missing historical family. */
78
+ exists?: boolean;
73
79
  } | {
74
80
  kind: "unknown";
75
81
  scope: {
@@ -78,6 +84,7 @@ export type TelegramWorkspaceJournalProtectionSource = {
78
84
  kind: "binding";
79
85
  bindingKey: string;
80
86
  journalBindingKey: string;
87
+ sessionId?: string;
81
88
  } | {
82
89
  kind: "discovered";
83
90
  path: string;
@@ -91,19 +98,24 @@ export declare function resolveTelegramWorkspaceAcceptedWorkProtection(input: {
91
98
  }[];
92
99
  journalSources: readonly TelegramWorkspaceJournalProtectionSource[];
93
100
  sourcesComplete: boolean;
101
+ /** Relocation cannot infer execution ownership from the original Telegram message target. */
102
+ requireBindingProvenance?: boolean;
94
103
  }): TelegramWorkspaceProtectionState;
95
104
  export type TelegramWorkspaceJournalPruneResult = {
96
105
  kind: "committed";
97
106
  binding: TelegramWorkspaceThreadBinding;
98
107
  removedKeys: string[];
108
+ removedSources?: TelegramWorkspaceJournalSource[];
99
109
  } | {
100
110
  kind: "blocked";
101
- reason: "incomplete-evidence" | "writer-not-quiescent" | "state-changed";
111
+ reason: "incomplete-evidence" | "writer-not-quiescent" | "state-changed" | "publication-refused";
102
112
  };
103
113
  export declare function pruneTelegramWorkspaceJournalEvidence(input: {
104
- store: Pick<TelegramTopicTargetStore, "commitWorkspaceJournalEvidence" | "persist">;
114
+ store: Pick<TelegramTopicTargetStore, "commitWorkspaceJournalEvidence" | "persistWorkspaceJournalEvidence">;
105
115
  binding: TelegramWorkspaceThreadBinding;
106
- capture: TelegramWorkspaceJournalProtectionCapture;
116
+ /** Capture fresh source evidence after admission, not a snapshot taken before the lease. */
117
+ capture: () => TelegramWorkspaceJournalProtectionCapture;
118
+ runExclusive: TelegramWorkspaceOperationGate["runExclusive"];
107
119
  getJournalWriterProtection: (journalBindingKey: string) => TelegramWorkspaceProtectionState;
108
120
  getLeaderEpoch: () => number | string | undefined;
109
121
  getProfileKey: () => string;
@@ -113,6 +125,7 @@ export declare function pruneTelegramWorkspaceJournalEvidence(input: {
113
125
  }): Promise<TelegramWorkspaceJournalPruneResult>;
114
126
  export declare function captureTelegramWorkspaceExternalProtection(input: {
115
127
  binding: TelegramWorkspaceThreadBinding;
128
+ requireBindingProvenance?: boolean;
116
129
  getLiveOwnerProtection: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceProtectionState;
117
130
  getLocalAcceptedTargets: (binding: TelegramWorkspaceThreadBinding) => {
118
131
  targets: readonly TelegramTarget[];
@@ -121,7 +134,7 @@ export declare function captureTelegramWorkspaceExternalProtection(input: {
121
134
  captureJournalSources: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceJournalProtectionCapture;
122
135
  getDeliveryAuthorityProtection?: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceProtectionState;
123
136
  }): TelegramWorkspaceExternalProtectionEvidence;
124
- export declare function createTelegramWorkspaceExternalProtectionCapture(deps: {
137
+ interface TelegramWorkspaceProtectionObserverDeps {
125
138
  listFollowers: () => readonly {
126
139
  target?: TelegramTarget;
127
140
  }[];
@@ -132,15 +145,35 @@ export declare function createTelegramWorkspaceExternalProtectionCapture(deps: {
132
145
  }[];
133
146
  resolveLeaderJournal: () => TelegramWorkspaceJournalReader | undefined;
134
147
  createFollowerJournalResolver: (journalBindingKey: string) => () => TelegramWorkspaceJournalReader | undefined;
148
+ createSessionJournalResolver?: (recipientBindingKey: string, sessionId: string) => () => TelegramWorkspaceJournalReader | undefined;
135
149
  discoverFollowerJournals?: () => {
136
150
  paths: readonly string[];
137
151
  complete: boolean;
138
152
  };
153
+ /** Strict read-only namespace evidence; it never substitutes for writer closure. */
154
+ inspectJournalNamespace?: () => TelegramJournalNamespaceInspection;
139
155
  createJournalPathResolver?: (path: string) => () => TelegramWorkspaceJournalReader | undefined;
140
156
  withJournalReference?: <T>(binding: TelegramWorkspaceJournalReader, operation: () => T) => T;
141
157
  getJournalWriterProtection?: (journalBindingKey: string) => TelegramWorkspaceProtectionState;
142
158
  getDeliveryAuthorityProtection?: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceProtectionState;
143
- }): (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceExternalProtectionEvidence;
159
+ }
160
+ export interface TelegramWorkspaceProtectionObserver {
161
+ capture: (binding: TelegramWorkspaceThreadBinding, options?: {
162
+ requireBindingProvenance?: boolean;
163
+ }) => TelegramWorkspaceExternalProtectionEvidence;
164
+ captureJournalSources: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceJournalProtectionCapture;
165
+ getJournalWriterProtection: (journalBindingKey: string) => TelegramWorkspaceProtectionState;
166
+ }
167
+ /** Shared read-only observer: protection and metadata pruning use the same scoped evidence path. */
168
+ export declare function createTelegramWorkspaceProtectionObserver(deps: TelegramWorkspaceProtectionObserverDeps): TelegramWorkspaceProtectionObserver;
169
+ /** Callable protection view retained for callers that need no metadata observation ports. */
170
+ export declare function createTelegramWorkspaceExternalProtectionCapture(deps: TelegramWorkspaceProtectionObserverDeps): TelegramWorkspaceProtectionObserver["capture"];
171
+ export declare function createTelegramWorkspaceJournalEvidencePruner(deps: TelegramWorkspaceOperationGate & {
172
+ store: TelegramTopicTargetStore;
173
+ getAdmission: () => TelegramWorkspaceAdmissionLedger | undefined;
174
+ getLeaderEpoch: () => number | string | undefined;
175
+ protection: Pick<TelegramWorkspaceProtectionObserver, "captureJournalSources" | "getJournalWriterProtection">;
176
+ }): (binding: TelegramWorkspaceThreadBinding, isCurrent: () => boolean) => Promise<TelegramWorkspaceJournalPruneResult>;
144
177
  export declare function isCurrentTelegramWorkspaceBinding(store: Pick<TelegramTopicTargetStore, "listWorkspaceBindings">, expected: TelegramWorkspaceThreadBinding): boolean;
145
178
  export interface TelegramWorkspaceDeadQueueJournalBinding {
146
179
  recoveryKey?: string;
@@ -175,6 +208,7 @@ export declare function createTelegramWorkspaceDeadOwnerQueueReclaimer(deps: {
175
208
  }[];
176
209
  resolveLeaderJournal: () => TelegramWorkspaceDeadQueueJournalBinding | undefined;
177
210
  createFollowerJournalResolver: (journalBindingKey: string) => () => TelegramWorkspaceDeadQueueJournalBinding | undefined;
211
+ createSessionJournalResolver?: (recipientBindingKey: string, sessionId: string) => () => TelegramWorkspaceDeadQueueJournalBinding | undefined;
178
212
  discoverFollowerJournals?: () => {
179
213
  paths: readonly string[];
180
214
  complete: boolean;
@@ -278,6 +312,7 @@ export type TelegramWorkspaceCapacityRunner = <T>(operation: () => Promise<T>) =
278
312
  export interface TelegramWorkspaceSlotRotationPorts extends TelegramWorkspaceOperationGate {
279
313
  getAdmission: () => TelegramWorkspaceAdmissionLedger | undefined;
280
314
  deleteThread: TelegramWorkspaceThreadDeletionTransport;
315
+ pruneJournalEvidence?: (binding: TelegramWorkspaceThreadBinding, isCurrent: () => boolean) => Promise<TelegramWorkspaceJournalPruneResult>;
281
316
  reclaimDeadOwnerQueuedWork?: (binding: TelegramWorkspaceThreadBinding, isCurrent: () => boolean) => Promise<TelegramWorkspaceDeadQueueReclamation>;
282
317
  }
283
318
  /** Retry allocation once, only after the failed operation released all ordinary leases. */