@llblab/pi-telegram 0.51.5 → 0.52.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.
- package/AGENTS.md +10 -8
- package/BACKLOG.md +1 -8
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +16 -6
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +5 -7
- package/dist/lib/bindings.d.ts +9 -2
- package/dist/lib/bindings.js +22 -13
- package/dist/lib/bus-api.js +4 -2
- package/dist/lib/bus-follower.d.ts +55 -57
- package/dist/lib/bus-follower.js +213 -140
- package/dist/lib/bus-leader.d.ts +21 -2
- package/dist/lib/bus-leader.js +181 -25
- package/dist/lib/bus-transport.d.ts +4 -1
- package/dist/lib/bus-transport.js +28 -4
- package/dist/lib/bus.d.ts +52 -56
- package/dist/lib/bus.js +78 -246
- package/dist/lib/channel-posts.d.ts +1 -3
- package/dist/lib/channel-posts.js +23 -26
- package/dist/lib/command-templates.d.ts +0 -5
- package/dist/lib/command-templates.js +5 -5
- package/dist/lib/commands.d.ts +12 -26
- package/dist/lib/commands.js +152 -98
- package/dist/lib/config.d.ts +0 -17
- package/dist/lib/config.js +12 -14
- package/dist/lib/delivery.d.ts +0 -4
- package/dist/lib/delivery.js +2 -5
- package/dist/lib/extension.js +281 -61
- package/dist/lib/generative-apps.d.ts +0 -19
- package/dist/lib/inbound.d.ts +0 -2
- package/dist/lib/inbound.js +2 -2
- package/dist/lib/journal.d.ts +177 -16
- package/dist/lib/journal.js +1115 -249
- package/dist/lib/keyboard.d.ts +0 -2
- package/dist/lib/keyboard.js +2 -2
- package/dist/lib/lifecycle.d.ts +57 -2
- package/dist/lib/lifecycle.js +110 -5
- package/dist/lib/locks.d.ts +100 -12
- package/dist/lib/locks.js +380 -14
- package/dist/lib/logging.d.ts +30 -12
- package/dist/lib/logging.js +129 -72
- package/dist/lib/media.d.ts +28 -5
- package/dist/lib/media.js +26 -8
- package/dist/lib/menu-model.d.ts +0 -11
- package/dist/lib/menu-model.js +8 -8
- package/dist/lib/menu-settings.d.ts +0 -18
- package/dist/lib/menu-settings.js +18 -18
- package/dist/lib/menu-status.d.ts +4 -4
- package/dist/lib/menu-status.js +12 -7
- package/dist/lib/menu-thinking.d.ts +1 -1
- package/dist/lib/menu-thinking.js +1 -1
- package/dist/lib/menu.d.ts +1 -0
- package/dist/lib/menu.js +4 -3
- package/dist/lib/model.js +2 -4
- package/dist/lib/outbound-attachments.d.ts +0 -1
- package/dist/lib/outbound-attachments.js +1 -1
- package/dist/lib/outbound-buttons.js +1 -8
- package/dist/lib/outbound-markup.d.ts +1 -6
- package/dist/lib/outbound-markup.js +4 -21
- package/dist/lib/outbound.d.ts +0 -8
- package/dist/lib/outbound.js +5 -5
- package/dist/lib/paths.d.ts +50 -7
- package/dist/lib/paths.js +155 -17
- package/dist/lib/pi.d.ts +1 -0
- package/dist/lib/pi.js +2 -1
- package/dist/lib/polling.d.ts +0 -5
- package/dist/lib/polling.js +5 -5
- package/dist/lib/preview.d.ts +0 -3
- package/dist/lib/preview.js +3 -3
- package/dist/lib/prompt-templates.d.ts +0 -1
- package/dist/lib/prompt-templates.js +1 -1
- package/dist/lib/queue.d.ts +3 -15
- package/dist/lib/queue.js +69 -8
- package/dist/lib/recovery.d.ts +29 -9
- package/dist/lib/recovery.js +104 -33
- package/dist/lib/replies.d.ts +0 -4
- package/dist/lib/replies.js +1 -1
- package/dist/lib/routing.d.ts +73 -8
- package/dist/lib/routing.js +2206 -307
- package/dist/lib/sections.d.ts +0 -2
- package/dist/lib/sections.js +1 -1
- package/dist/lib/status.d.ts +46 -9
- package/dist/lib/status.js +179 -27
- package/dist/lib/sync.d.ts +0 -5
- package/dist/lib/sync.js +9 -8
- package/dist/lib/telegram-api.d.ts +11 -3
- package/dist/lib/telegram-api.js +34 -23
- package/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/dist/lib/thread-cleanup-manager.js +18 -21
- package/dist/lib/thread-naming.d.ts +32 -5
- package/dist/lib/thread-naming.js +191 -7
- package/dist/lib/thread-reconciler.d.ts +2 -0
- package/dist/lib/thread-reconciler.js +1 -1
- package/dist/lib/threads.d.ts +292 -59
- package/dist/lib/threads.js +1664 -373
- package/dist/lib/turns.d.ts +0 -1
- package/dist/lib/turns.js +1 -1
- package/dist/lib/updates.d.ts +184 -28
- package/dist/lib/updates.js +1032 -114
- package/dist/lib/wire.d.ts +12 -0
- package/dist/lib/wire.js +21 -0
- package/dist/lib/workspace-admission.d.ts +25 -17
- package/dist/lib/workspace-admission.js +95 -20
- package/dist/lib/workspace-identity.d.ts +20 -0
- package/dist/lib/workspace-identity.js +103 -0
- package/dist/lib/workspace-retirement.d.ts +45 -10
- package/dist/lib/workspace-retirement.js +196 -64
- package/dist/package.json +6 -6
- package/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/docs/README.md +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +158 -39
- package/docs/callback-namespaces.md +1 -1
- package/docs/delivery.md +1 -1
- package/docs/generative-apps.md +2 -2
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +127 -19
- package/docs/public-api.md +5 -3
- package/docs/ui-style.md +26 -8
- package/docs/updates.md +1 -1
- package/lib/activity-verbosity.ts +5 -9
- package/lib/bindings.ts +23 -10
- package/lib/bus-api.ts +5 -2
- package/lib/bus-follower.ts +251 -226
- package/lib/bus-leader.ts +178 -31
- package/lib/bus-transport.ts +30 -8
- package/lib/bus.ts +110 -334
- package/lib/channel-posts.ts +29 -29
- package/lib/command-templates.ts +5 -5
- package/lib/commands.ts +150 -112
- package/lib/config.ts +12 -17
- package/lib/delivery.ts +2 -8
- package/lib/extension.ts +282 -72
- package/lib/generative-apps.ts +0 -22
- package/lib/inbound.ts +2 -2
- package/lib/journal.ts +1182 -326
- package/lib/keyboard.ts +2 -2
- package/lib/lifecycle.ts +141 -5
- package/lib/locks.ts +402 -23
- package/lib/logging.ts +151 -101
- package/lib/media.ts +51 -9
- package/lib/menu-model.ts +8 -8
- package/lib/menu-settings.ts +18 -18
- package/lib/menu-status.ts +11 -0
- package/lib/menu-thinking.ts +2 -2
- package/lib/menu.ts +5 -1
- package/lib/model.ts +2 -4
- package/lib/outbound-attachments.ts +1 -1
- package/lib/outbound-buttons.ts +1 -10
- package/lib/outbound-markup.ts +5 -24
- package/lib/outbound.ts +5 -5
- package/lib/paths.ts +177 -17
- package/lib/pi.ts +5 -2
- package/lib/polling.ts +5 -5
- package/lib/preview.ts +3 -3
- package/lib/prompt-templates.ts +1 -1
- package/lib/queue.ts +67 -9
- package/lib/recovery.ts +104 -44
- package/lib/replies.ts +1 -1
- package/lib/routing.ts +1955 -375
- package/lib/sections.ts +1 -1
- package/lib/status.ts +185 -36
- package/lib/sync.ts +8 -8
- package/lib/telegram-api.ts +48 -33
- package/lib/thread-cleanup-manager.ts +24 -21
- package/lib/thread-naming.ts +267 -9
- package/lib/thread-reconciler.ts +3 -1
- package/lib/threads.ts +1718 -491
- package/lib/turns.ts +1 -1
- package/lib/updates.ts +1072 -151
- package/lib/wire.ts +28 -0
- package/lib/workspace-admission.ts +97 -47
- package/lib/workspace-identity.ts +147 -0
- package/lib/workspace-retirement.ts +234 -102
- package/package.json +6 -6
- package/scripts/audit-exports.mjs +100 -0
- package/scripts/check-downgrade.mjs +80 -43
- package/skills/generated-control-surface/SKILL.md +3 -3
- package/skills/telegram-bridge/references/diagnosis.md +3 -3
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import { isDeepStrictEqual } from "node:util";
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
|
+
TelegramJournalNamespaceInspection,
|
|
11
12
|
TelegramUpdateJournalDeadQueueOwnerRecoveryInput,
|
|
12
13
|
TelegramUpdateJournalDeadQueueOwnerRecoveryResult,
|
|
13
14
|
TelegramUpdateJournalEntry,
|
|
14
15
|
TelegramUpdateJournalQueueOwnerIdentity,
|
|
15
16
|
} from "./journal.ts";
|
|
16
|
-
import type
|
|
17
|
+
import { areTelegramTargetsEqual as sameTarget, type TelegramTarget } from "./target.ts";
|
|
17
18
|
import {
|
|
18
19
|
getTelegramApiErrorRequestTarget,
|
|
19
20
|
isTelegramApiRequestRejected,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
} from "./telegram-api.ts";
|
|
22
23
|
import type {
|
|
23
24
|
TelegramTopicTargetStore,
|
|
25
|
+
TelegramWorkspaceJournalSource,
|
|
24
26
|
TelegramWorkspaceExternalProtectionEvidence,
|
|
25
27
|
TelegramWorkspaceRetirementIntent,
|
|
26
28
|
TelegramWorkspaceProtectionState,
|
|
@@ -104,8 +106,8 @@ export interface TelegramWorkspaceJournalProtectionCapture {
|
|
|
104
106
|
|
|
105
107
|
interface TelegramWorkspaceJournalReader {
|
|
106
108
|
recoveryKey?: string;
|
|
107
|
-
journal: { read: () => { entries: readonly { update: unknown }[] } };
|
|
108
|
-
readForProtection?: () => { entries: readonly { update: unknown }[] };
|
|
109
|
+
journal: { read: () => { entries: readonly { update: unknown }[]; exists?: boolean } };
|
|
110
|
+
readForProtection?: () => { entries: readonly { update: unknown }[]; exists?: boolean };
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
export function captureTelegramWorkspaceJournalProtectionSources(input: {
|
|
@@ -114,6 +116,10 @@ export function captureTelegramWorkspaceJournalProtectionSources(input: {
|
|
|
114
116
|
createFollowerResolver: (
|
|
115
117
|
journalBindingKey: string,
|
|
116
118
|
) => () => TelegramWorkspaceJournalReader | undefined;
|
|
119
|
+
createSessionResolver?: (
|
|
120
|
+
recipientBindingKey: string,
|
|
121
|
+
sessionId: string,
|
|
122
|
+
) => () => TelegramWorkspaceJournalReader | undefined;
|
|
117
123
|
withJournalReference?: <T>(binding: TelegramWorkspaceJournalReader,
|
|
118
124
|
operation: () => T) => T;
|
|
119
125
|
discovery?: {
|
|
@@ -140,7 +146,8 @@ export function captureTelegramWorkspaceJournalProtectionSources(input: {
|
|
|
140
146
|
const snapshot = input.withJournalReference
|
|
141
147
|
? input.withJournalReference(binding, read)
|
|
142
148
|
: read();
|
|
143
|
-
sources.push({ kind: "available", scope, entries: snapshot.entries
|
|
149
|
+
sources.push({ kind: "available", scope, entries: snapshot.entries,
|
|
150
|
+
...(snapshot.exists === undefined ? {} : { exists: snapshot.exists }) });
|
|
144
151
|
} catch {
|
|
145
152
|
complete = false;
|
|
146
153
|
sources.push({ kind: "unknown", scope });
|
|
@@ -157,6 +164,11 @@ export function captureTelegramWorkspaceJournalProtectionSources(input: {
|
|
|
157
164
|
input.createFollowerResolver(journalBindingKey),
|
|
158
165
|
);
|
|
159
166
|
}
|
|
167
|
+
for (const source of input.binding.journalSources ?? []) {
|
|
168
|
+
capture({ kind: "binding", bindingKey: input.binding.bindingKey,
|
|
169
|
+
journalBindingKey: source.recipientBindingKey, sessionId: source.sessionId },
|
|
170
|
+
input.createSessionResolver?.(source.recipientBindingKey, source.sessionId) ?? (() => undefined));
|
|
171
|
+
}
|
|
160
172
|
for (const path of input.discovery?.paths ?? []) {
|
|
161
173
|
capture({ kind: "discovered", path }, input.discovery!.createResolver(path));
|
|
162
174
|
}
|
|
@@ -168,15 +180,17 @@ export type TelegramWorkspaceJournalProtectionSource =
|
|
|
168
180
|
kind: "available";
|
|
169
181
|
scope:
|
|
170
182
|
| { kind: "shared" }
|
|
171
|
-
| { kind: "binding"; bindingKey: string; journalBindingKey: string }
|
|
183
|
+
| { kind: "binding"; bindingKey: string; journalBindingKey: string; sessionId?: string }
|
|
172
184
|
| { kind: "discovered"; path: string };
|
|
173
185
|
entries: readonly { update: unknown }[];
|
|
186
|
+
/** Positive filesystem presence is distinct from a complete namespace with a missing historical family. */
|
|
187
|
+
exists?: boolean;
|
|
174
188
|
}
|
|
175
189
|
| {
|
|
176
190
|
kind: "unknown";
|
|
177
191
|
scope:
|
|
178
192
|
| { kind: "shared" }
|
|
179
|
-
| { kind: "binding"; bindingKey: string; journalBindingKey: string }
|
|
193
|
+
| { kind: "binding"; bindingKey: string; journalBindingKey: string; sessionId?: string }
|
|
180
194
|
| { kind: "discovered"; path: string };
|
|
181
195
|
};
|
|
182
196
|
|
|
@@ -206,18 +220,13 @@ function getJournalUpdateTarget(update: unknown):
|
|
|
206
220
|
};
|
|
207
221
|
}
|
|
208
222
|
|
|
209
|
-
function sameTarget(
|
|
210
|
-
left: { chatId: number; threadId?: number },
|
|
211
|
-
right: { chatId: number; threadId?: number },
|
|
212
|
-
): boolean {
|
|
213
|
-
return left.chatId === right.chatId && left.threadId === right.threadId;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
223
|
export function resolveTelegramWorkspaceAcceptedWorkProtection(input: {
|
|
217
224
|
binding: TelegramWorkspaceThreadBinding;
|
|
218
225
|
localAcceptedTargets: readonly { chatId: number; threadId?: number }[];
|
|
219
226
|
journalSources: readonly TelegramWorkspaceJournalProtectionSource[];
|
|
220
227
|
sourcesComplete: boolean;
|
|
228
|
+
/** Relocation cannot infer execution ownership from the original Telegram message target. */
|
|
229
|
+
requireBindingProvenance?: boolean;
|
|
221
230
|
}): TelegramWorkspaceProtectionState {
|
|
222
231
|
if (input.localAcceptedTargets.some((target) => sameTarget(target, input.binding.target))) {
|
|
223
232
|
return "protected";
|
|
@@ -231,9 +240,17 @@ export function resolveTelegramWorkspaceAcceptedWorkProtection(input: {
|
|
|
231
240
|
unknown = true;
|
|
232
241
|
continue;
|
|
233
242
|
}
|
|
243
|
+
if (input.requireBindingProvenance && source.scope.kind === "binding" && source.exists === false) {
|
|
244
|
+
unknown = true;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
234
247
|
if (source.scope.kind === "binding" && source.entries.length > 0) {
|
|
235
248
|
return "protected";
|
|
236
249
|
}
|
|
250
|
+
if (input.requireBindingProvenance && source.entries.length > 0) {
|
|
251
|
+
unknown = true;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
237
254
|
for (const entry of source.entries) {
|
|
238
255
|
const target = getJournalUpdateTarget(entry.update);
|
|
239
256
|
if (!target) {
|
|
@@ -251,19 +268,22 @@ export type TelegramWorkspaceJournalPruneResult =
|
|
|
251
268
|
kind: "committed";
|
|
252
269
|
binding: TelegramWorkspaceThreadBinding;
|
|
253
270
|
removedKeys: string[];
|
|
271
|
+
removedSources?: TelegramWorkspaceJournalSource[];
|
|
254
272
|
}
|
|
255
273
|
| {
|
|
256
274
|
kind: "blocked";
|
|
257
|
-
reason: "incomplete-evidence" | "writer-not-quiescent" | "state-changed";
|
|
275
|
+
reason: "incomplete-evidence" | "writer-not-quiescent" | "state-changed" | "publication-refused";
|
|
258
276
|
};
|
|
259
277
|
|
|
260
278
|
export async function pruneTelegramWorkspaceJournalEvidence(input: {
|
|
261
279
|
store: Pick<
|
|
262
280
|
TelegramTopicTargetStore,
|
|
263
|
-
"commitWorkspaceJournalEvidence" | "
|
|
281
|
+
"commitWorkspaceJournalEvidence" | "persistWorkspaceJournalEvidence"
|
|
264
282
|
>;
|
|
265
283
|
binding: TelegramWorkspaceThreadBinding;
|
|
266
|
-
|
|
284
|
+
/** Capture fresh source evidence after admission, not a snapshot taken before the lease. */
|
|
285
|
+
capture: () => TelegramWorkspaceJournalProtectionCapture;
|
|
286
|
+
runExclusive: TelegramWorkspaceOperationGate["runExclusive"];
|
|
267
287
|
getJournalWriterProtection: (
|
|
268
288
|
journalBindingKey: string,
|
|
269
289
|
) => TelegramWorkspaceProtectionState;
|
|
@@ -289,72 +309,68 @@ export async function pruneTelegramWorkspaceJournalEvidence(input: {
|
|
|
289
309
|
"Telegram Workspace journal pruning requires current leader authority.",
|
|
290
310
|
);
|
|
291
311
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
shared[0]?.kind !== "available" ||
|
|
311
|
-
bindingSources.length !==
|
|
312
|
-
(input.binding.journalBindingKeys ?? []).length ||
|
|
313
|
-
sourceByKey.size !== (input.binding.journalBindingKeys ?? []).length ||
|
|
314
|
-
Array.from(sourceByKey.values()).some(
|
|
315
|
-
(source) => source.kind !== "available",
|
|
316
|
-
)
|
|
317
|
-
) {
|
|
312
|
+
let capture: TelegramWorkspaceJournalProtectionCapture;
|
|
313
|
+
try { capture = input.capture(); }
|
|
314
|
+
catch { return { kind: "blocked", reason: "incomplete-evidence" }; }
|
|
315
|
+
const address = (key: string, sessionId?: string) => JSON.stringify([sessionId ?? null, key]);
|
|
316
|
+
const keys = input.binding.journalBindingKeys ?? [];
|
|
317
|
+
const sessions = input.binding.journalSources ?? [];
|
|
318
|
+
const expected = [
|
|
319
|
+
...keys.map(key => address(key)),
|
|
320
|
+
...sessions.map(source => address(source.recipientBindingKey, source.sessionId)),
|
|
321
|
+
];
|
|
322
|
+
const shared = capture.sources.filter(source => source.scope.kind === "shared");
|
|
323
|
+
const bindingSources = capture.sources.filter(source => source.scope.kind === "binding" &&
|
|
324
|
+
source.scope.bindingKey === input.binding.bindingKey);
|
|
325
|
+
const sourceByAddress = new Map(bindingSources.flatMap(source => source.scope.kind === "binding"
|
|
326
|
+
? [[address(source.scope.journalBindingKey, source.scope.sessionId), source] as const] : []));
|
|
327
|
+
if (!capture.complete || shared.length !== 1 || shared[0]?.kind !== "available" ||
|
|
328
|
+
bindingSources.length !== expected.length || sourceByAddress.size !== expected.length ||
|
|
329
|
+
expected.some(key => sourceByAddress.get(key)?.kind !== "available")) {
|
|
318
330
|
return { kind: "blocked", reason: "incomplete-evidence" };
|
|
319
331
|
}
|
|
320
|
-
const
|
|
321
|
-
const source =
|
|
332
|
+
const empty = new Set(expected.filter(key => {
|
|
333
|
+
const source = sourceByAddress.get(key);
|
|
322
334
|
return source?.kind === "available" && source.entries.length === 0;
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
335
|
+
}));
|
|
336
|
+
const emptyWriterKeys = new Set([
|
|
337
|
+
...keys.filter(key => empty.has(address(key))),
|
|
338
|
+
...sessions.filter(source => empty.has(address(source.recipientBindingKey, source.sessionId)))
|
|
339
|
+
.map(source => source.recipientBindingKey),
|
|
340
|
+
]);
|
|
341
|
+
const quiescent = new Set([...emptyWriterKeys].filter(key => input.getJournalWriterProtection(key) === "clear"));
|
|
342
|
+
const removedKeys = keys.filter(key => empty.has(address(key)) && quiescent.has(key));
|
|
343
|
+
const removedSources = sessions.filter(source => empty.has(address(source.recipientBindingKey, source.sessionId)) &&
|
|
344
|
+
quiescent.has(source.recipientBindingKey));
|
|
345
|
+
if (empty.size > 0 && removedKeys.length === 0 && removedSources.length === 0) {
|
|
329
346
|
return { kind: "blocked", reason: "writer-not-quiescent" };
|
|
330
347
|
}
|
|
331
|
-
const retainedKeys = (
|
|
332
|
-
|
|
333
|
-
);
|
|
348
|
+
const retainedKeys = keys.filter(key => !removedKeys.includes(key));
|
|
349
|
+
const retainedSources = sessions.filter(source => !removedSources.includes(source));
|
|
334
350
|
if (!isCurrent()) {
|
|
335
351
|
throw new Error("Telegram Workspace journal pruning lost leader authority.");
|
|
336
352
|
}
|
|
337
353
|
const binding = input.store.commitWorkspaceJournalEvidence(
|
|
338
|
-
input.binding,
|
|
339
|
-
retainedKeys,
|
|
340
|
-
input.binding.journalBindingsComplete === true,
|
|
354
|
+
input.binding, retainedKeys, input.binding.journalBindingsComplete === true, retainedSources,
|
|
341
355
|
);
|
|
342
356
|
if (!binding) return { kind: "blocked", reason: "state-changed" };
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
357
|
+
if (removedKeys.length > 0 || removedSources.length > 0) {
|
|
358
|
+
const published = await input.store.persistWorkspaceJournalEvidence(binding, isCurrent);
|
|
359
|
+
if (!isCurrent()) throw new Error("Telegram Workspace journal pruning lost leader authority.");
|
|
360
|
+
if (!published) return { kind: "blocked", reason: "publication-refused" };
|
|
361
|
+
}
|
|
347
362
|
if (!isCurrent()) {
|
|
348
363
|
throw new Error("Telegram Workspace journal pruning lost leader authority.");
|
|
349
364
|
}
|
|
350
|
-
return { kind: "committed", binding, removedKeys
|
|
365
|
+
return { kind: "committed", binding, removedKeys,
|
|
366
|
+
...(removedSources.length ? { removedSources: removedSources.map(source => ({ ...source })) } : {}) };
|
|
351
367
|
};
|
|
352
368
|
return runWithTelegramWorkspaceAdmissionsAsync({
|
|
353
369
|
ledger: input.admission,
|
|
354
370
|
operationId: createTelegramWorkspaceAdmissionOperationId(),
|
|
355
371
|
operationKind: "workspace.prune-journal-evidence",
|
|
356
372
|
scopes: [{ kind: "target", target: input.binding.target }],
|
|
357
|
-
operation,
|
|
373
|
+
operation: () => input.runExclusive(operation),
|
|
358
374
|
onReleaseError(error) {
|
|
359
375
|
input.onAdmissionReleaseError?.(error);
|
|
360
376
|
},
|
|
@@ -363,6 +379,7 @@ export async function pruneTelegramWorkspaceJournalEvidence(input: {
|
|
|
363
379
|
|
|
364
380
|
export function captureTelegramWorkspaceExternalProtection(input: {
|
|
365
381
|
binding: TelegramWorkspaceThreadBinding;
|
|
382
|
+
requireBindingProvenance?: boolean;
|
|
366
383
|
getLiveOwnerProtection: (
|
|
367
384
|
binding: TelegramWorkspaceThreadBinding,
|
|
368
385
|
) => TelegramWorkspaceProtectionState;
|
|
@@ -392,6 +409,7 @@ export function captureTelegramWorkspaceExternalProtection(input: {
|
|
|
392
409
|
localAcceptedTargets: local.targets,
|
|
393
410
|
journalSources: journals.sources,
|
|
394
411
|
sourcesComplete: journals.complete && local.complete,
|
|
412
|
+
requireBindingProvenance: input.requireBindingProvenance,
|
|
395
413
|
});
|
|
396
414
|
} catch {
|
|
397
415
|
// Unavailable queue or journal evidence must not clear accepted work.
|
|
@@ -404,7 +422,7 @@ export function captureTelegramWorkspaceExternalProtection(input: {
|
|
|
404
422
|
return { liveOwner, acceptedWork, deliveryAuthority };
|
|
405
423
|
}
|
|
406
424
|
|
|
407
|
-
|
|
425
|
+
interface TelegramWorkspaceProtectionObserverDeps {
|
|
408
426
|
listFollowers: () => readonly { target?: TelegramTarget }[];
|
|
409
427
|
getActiveTurnTarget: () => TelegramTarget | undefined;
|
|
410
428
|
getQueuedItems: () => readonly { chatId: number; target?: TelegramTarget }[];
|
|
@@ -412,7 +430,13 @@ export function createTelegramWorkspaceExternalProtectionCapture(deps: {
|
|
|
412
430
|
createFollowerJournalResolver: (
|
|
413
431
|
journalBindingKey: string,
|
|
414
432
|
) => () => TelegramWorkspaceJournalReader | undefined;
|
|
433
|
+
createSessionJournalResolver?: (
|
|
434
|
+
recipientBindingKey: string,
|
|
435
|
+
sessionId: string,
|
|
436
|
+
) => () => TelegramWorkspaceJournalReader | undefined;
|
|
415
437
|
discoverFollowerJournals?: () => { paths: readonly string[]; complete: boolean };
|
|
438
|
+
/** Strict read-only namespace evidence; it never substitutes for writer closure. */
|
|
439
|
+
inspectJournalNamespace?: () => TelegramJournalNamespaceInspection;
|
|
416
440
|
createJournalPathResolver?: (
|
|
417
441
|
path: string,
|
|
418
442
|
) => () => TelegramWorkspaceJournalReader | undefined;
|
|
@@ -424,19 +448,56 @@ export function createTelegramWorkspaceExternalProtectionCapture(deps: {
|
|
|
424
448
|
getDeliveryAuthorityProtection?: (
|
|
425
449
|
binding: TelegramWorkspaceThreadBinding,
|
|
426
450
|
) => TelegramWorkspaceProtectionState;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export interface TelegramWorkspaceProtectionObserver {
|
|
454
|
+
capture: (binding: TelegramWorkspaceThreadBinding,
|
|
455
|
+
options?: { requireBindingProvenance?: boolean }) => TelegramWorkspaceExternalProtectionEvidence;
|
|
456
|
+
captureJournalSources: (binding: TelegramWorkspaceThreadBinding) => TelegramWorkspaceJournalProtectionCapture;
|
|
457
|
+
getJournalWriterProtection: (journalBindingKey: string) => TelegramWorkspaceProtectionState;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Shared read-only observer: protection and metadata pruning use the same scoped evidence path. */
|
|
461
|
+
export function createTelegramWorkspaceProtectionObserver(deps: TelegramWorkspaceProtectionObserverDeps): TelegramWorkspaceProtectionObserver {
|
|
462
|
+
const captureJournalSources: TelegramWorkspaceProtectionObserver["captureJournalSources"] = (candidate) => {
|
|
463
|
+
const namespace = deps.inspectJournalNamespace?.();
|
|
464
|
+
if (deps.inspectJournalNamespace && (!namespace || namespace.sources.filter(source => source.role === "polling").length !== 1))
|
|
465
|
+
throw new Error("Telegram strict namespace evidence is unavailable or incomplete.");
|
|
466
|
+
if (namespace?.retainedInputs?.some(original => original.state !== "committed")) throw new Error(
|
|
467
|
+
"Telegram namespace contains uncommitted private retention evidence.",
|
|
468
|
+
);
|
|
469
|
+
const discovery = namespace
|
|
470
|
+
? { paths: namespace.sources.filter(source => source.role !== "polling" && source.evidence.kind === "present")
|
|
471
|
+
.map(source => source.path), complete: true }
|
|
472
|
+
: candidate.journalBindingsComplete === true ? undefined : deps.discoverFollowerJournals?.();
|
|
473
|
+
if (namespace && !deps.createJournalPathResolver) throw new Error(
|
|
474
|
+
"Telegram strict namespace protection requires discovered source resolution.",
|
|
475
|
+
);
|
|
476
|
+
return captureTelegramWorkspaceJournalProtectionSources({
|
|
477
|
+
binding: candidate,
|
|
478
|
+
resolveLeader: deps.resolveLeaderJournal,
|
|
479
|
+
createFollowerResolver: deps.createFollowerJournalResolver,
|
|
480
|
+
...(deps.createSessionJournalResolver ? { createSessionResolver: deps.createSessionJournalResolver } : {}),
|
|
481
|
+
...(deps.withJournalReference ? { withJournalReference: deps.withJournalReference } : {}),
|
|
482
|
+
...(discovery && deps.createJournalPathResolver
|
|
483
|
+
? { discovery: { ...discovery, createResolver: deps.createJournalPathResolver } } : {}),
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
const capture: TelegramWorkspaceProtectionObserver["capture"] = function (binding, options) {
|
|
431
487
|
return captureTelegramWorkspaceExternalProtection({
|
|
432
488
|
binding,
|
|
489
|
+
requireBindingProvenance: options?.requireBindingProvenance,
|
|
433
490
|
getLiveOwnerProtection(candidate) {
|
|
434
491
|
if (deps.listFollowers().some((follower) =>
|
|
435
492
|
!!follower.target && sameTarget(follower.target, candidate.target),
|
|
436
493
|
)) return "protected";
|
|
437
494
|
if (!deps.getJournalWriterProtection) return "unknown";
|
|
438
495
|
let unknown = candidate.journalBindingsComplete !== true;
|
|
439
|
-
|
|
496
|
+
const writerKeys = new Set([
|
|
497
|
+
...(candidate.journalBindingKeys ?? []),
|
|
498
|
+
...(candidate.journalSources ?? []).map(source => source.recipientBindingKey),
|
|
499
|
+
]);
|
|
500
|
+
for (const journalBindingKey of writerKeys) {
|
|
440
501
|
const protection = deps.getJournalWriterProtection(journalBindingKey);
|
|
441
502
|
if (protection === "protected") return "protected";
|
|
442
503
|
if (protection === "unknown") unknown = true;
|
|
@@ -455,29 +516,40 @@ export function createTelegramWorkspaceExternalProtectionCapture(deps: {
|
|
|
455
516
|
}
|
|
456
517
|
return { targets, complete };
|
|
457
518
|
},
|
|
458
|
-
captureJournalSources
|
|
459
|
-
const discovery = candidate.journalBindingsComplete === true
|
|
460
|
-
? undefined
|
|
461
|
-
: deps.discoverFollowerJournals?.();
|
|
462
|
-
return captureTelegramWorkspaceJournalProtectionSources({
|
|
463
|
-
binding: candidate,
|
|
464
|
-
resolveLeader: deps.resolveLeaderJournal,
|
|
465
|
-
createFollowerResolver: deps.createFollowerJournalResolver,
|
|
466
|
-
...(deps.withJournalReference
|
|
467
|
-
? { withJournalReference: deps.withJournalReference } : {}),
|
|
468
|
-
...(discovery && deps.createJournalPathResolver
|
|
469
|
-
? { discovery: {
|
|
470
|
-
...discovery,
|
|
471
|
-
createResolver: deps.createJournalPathResolver,
|
|
472
|
-
} }
|
|
473
|
-
: {}),
|
|
474
|
-
});
|
|
475
|
-
},
|
|
519
|
+
captureJournalSources,
|
|
476
520
|
...(deps.getDeliveryAuthorityProtection
|
|
477
521
|
? { getDeliveryAuthorityProtection: deps.getDeliveryAuthorityProtection }
|
|
478
522
|
: {}),
|
|
479
523
|
});
|
|
480
524
|
};
|
|
525
|
+
return { capture, captureJournalSources,
|
|
526
|
+
getJournalWriterProtection: deps.getJournalWriterProtection ?? (() => "unknown") };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Callable protection view retained for callers that need no metadata observation ports. */
|
|
530
|
+
export function createTelegramWorkspaceExternalProtectionCapture(deps: TelegramWorkspaceProtectionObserverDeps): TelegramWorkspaceProtectionObserver["capture"] {
|
|
531
|
+
return createTelegramWorkspaceProtectionObserver(deps).capture;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function createTelegramWorkspaceJournalEvidencePruner(deps: TelegramWorkspaceOperationGate & {
|
|
535
|
+
store: TelegramTopicTargetStore;
|
|
536
|
+
getAdmission: () => TelegramWorkspaceAdmissionLedger | undefined;
|
|
537
|
+
getLeaderEpoch: () => number | string | undefined;
|
|
538
|
+
protection: Pick<TelegramWorkspaceProtectionObserver, "captureJournalSources" | "getJournalWriterProtection">;
|
|
539
|
+
}): (binding: TelegramWorkspaceThreadBinding, isCurrent: () => boolean) => Promise<TelegramWorkspaceJournalPruneResult> {
|
|
540
|
+
return async (binding, isCurrent) => {
|
|
541
|
+
const admission = deps.getAdmission();
|
|
542
|
+
if (!admission) return { kind: "blocked", reason: "state-changed" };
|
|
543
|
+
const profileKey = admission.getProfileKey();
|
|
544
|
+
return pruneTelegramWorkspaceJournalEvidence({
|
|
545
|
+
store: deps.store, binding, admission, runExclusive: deps.runExclusive,
|
|
546
|
+
capture: () => deps.protection.captureJournalSources(binding),
|
|
547
|
+
getJournalWriterProtection: deps.protection.getJournalWriterProtection,
|
|
548
|
+
getLeaderEpoch: deps.getLeaderEpoch,
|
|
549
|
+
getProfileKey: () => deps.getAdmission()?.getProfileKey() ?? "",
|
|
550
|
+
isCurrent: () => isCurrent() && deps.getAdmission()?.getProfileKey() === profileKey,
|
|
551
|
+
});
|
|
552
|
+
};
|
|
481
553
|
}
|
|
482
554
|
|
|
483
555
|
export function isCurrentTelegramWorkspaceBinding(
|
|
@@ -530,6 +602,10 @@ export function createTelegramWorkspaceDeadOwnerQueueReclaimer(deps: {
|
|
|
530
602
|
createFollowerJournalResolver: (
|
|
531
603
|
journalBindingKey: string,
|
|
532
604
|
) => () => TelegramWorkspaceDeadQueueJournalBinding | undefined;
|
|
605
|
+
createSessionJournalResolver?: (
|
|
606
|
+
recipientBindingKey: string,
|
|
607
|
+
sessionId: string,
|
|
608
|
+
) => () => TelegramWorkspaceDeadQueueJournalBinding | undefined;
|
|
533
609
|
discoverFollowerJournals?: () => { paths: readonly string[]; complete: boolean };
|
|
534
610
|
createJournalPathResolver?: (
|
|
535
611
|
path: string,
|
|
@@ -564,6 +640,9 @@ export function createTelegramWorkspaceDeadOwnerQueueReclaimer(deps: {
|
|
|
564
640
|
if (!isCurrent() || !deps.isBindingCurrent(binding)) {
|
|
565
641
|
return { kind: "blocked", reason: "authority-changed" };
|
|
566
642
|
}
|
|
643
|
+
// Session addresses require exact resolution; never substitute a flat same-hash journal.
|
|
644
|
+
if (binding.journalSources?.length && !deps.createSessionJournalResolver)
|
|
645
|
+
return { kind: "blocked", reason: "incomplete-source" };
|
|
567
646
|
const initial = deps.getExternalProtection(binding);
|
|
568
647
|
if (initial.liveOwner !== "clear" || initial.deliveryAuthority !== "clear") {
|
|
569
648
|
return { kind: "blocked", reason: "live-owner" };
|
|
@@ -607,6 +686,9 @@ export function createTelegramWorkspaceDeadOwnerQueueReclaimer(deps: {
|
|
|
607
686
|
for (const key of binding.journalBindingKeys ?? []) {
|
|
608
687
|
capture("binding", deps.createFollowerJournalResolver(key));
|
|
609
688
|
}
|
|
689
|
+
for (const source of binding.journalSources ?? []) {
|
|
690
|
+
capture("binding", deps.createSessionJournalResolver!(source.recipientBindingKey, source.sessionId));
|
|
691
|
+
}
|
|
610
692
|
for (const path of discovery?.paths ?? []) {
|
|
611
693
|
if (!deps.createJournalPathResolver) {
|
|
612
694
|
complete = false;
|
|
@@ -884,6 +966,7 @@ export async function executeTelegramWorkspaceRetirement(input: {
|
|
|
884
966
|
| "listWorkspaceRetirementIntents"
|
|
885
967
|
| "commitWorkspaceRetirement"
|
|
886
968
|
| "removeWorkspaceRetirementIntent"
|
|
969
|
+
| "markStaleByTarget"
|
|
887
970
|
| "persist"
|
|
888
971
|
>;
|
|
889
972
|
admission: Pick<
|
|
@@ -971,6 +1054,12 @@ export async function executeTelegramWorkspaceRetirement(input: {
|
|
|
971
1054
|
}
|
|
972
1055
|
return { kind: "retained", reason: "stale-intent" };
|
|
973
1056
|
}
|
|
1057
|
+
if (fence?.phase === "commit-ready") {
|
|
1058
|
+
// Exact confirmed absence retires any same-target routing record that
|
|
1059
|
+
// survived the deletion attempt. The fence prevents replacement target
|
|
1060
|
+
// publication while this stale local projection is removed.
|
|
1061
|
+
input.store.markStaleByTarget(binding.target, "deleted");
|
|
1062
|
+
}
|
|
974
1063
|
const eligible = () => input.store.captureWorkspaceSlotOccupancy(
|
|
975
1064
|
input.getExternalProtection,
|
|
976
1065
|
{ expectedRetirement: input.intent },
|
|
@@ -1040,6 +1129,9 @@ export async function executeTelegramWorkspaceRetirement(input: {
|
|
|
1040
1129
|
fence = input.admission.confirmRetirementAbsence(fence);
|
|
1041
1130
|
}
|
|
1042
1131
|
if (!isCurrent()) return { kind: "retained", reason: "authority-changed" };
|
|
1132
|
+
if (fence.phase === "commit-ready") {
|
|
1133
|
+
input.store.markStaleByTarget(binding.target, "deleted");
|
|
1134
|
+
}
|
|
1043
1135
|
if (!eligible()) return { kind: "retained", reason: "protection-changed" };
|
|
1044
1136
|
if (!await input.store.commitWorkspaceRetirement(input.intent, isCurrent)) {
|
|
1045
1137
|
return { kind: "retained", reason: "commit-rejected" };
|
|
@@ -1212,7 +1304,7 @@ export type TelegramWorkspaceRetirementLifecycleResult =
|
|
|
1212
1304
|
export async function runTelegramWorkspaceRetirementLifecycle(input: {
|
|
1213
1305
|
store: TelegramWorkspaceRetirementPreparationDeps["store"] & Pick<
|
|
1214
1306
|
TelegramTopicTargetStore,
|
|
1215
|
-
"replaceWorkspaceRetirementIntent" | "commitWorkspaceRetirement"
|
|
1307
|
+
"replaceWorkspaceRetirementIntent" | "commitWorkspaceRetirement" | "markStaleByTarget"
|
|
1216
1308
|
>;
|
|
1217
1309
|
getExternalProtection: (
|
|
1218
1310
|
binding: TelegramWorkspaceThreadBinding,
|
|
@@ -1257,19 +1349,42 @@ export async function runTelegramWorkspaceRetirementLifecycle(input: {
|
|
|
1257
1349
|
});
|
|
1258
1350
|
}
|
|
1259
1351
|
if (intent) {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1352
|
+
if (retainedFence && isTelegramWorkspaceRetirementFence(retainedFence) &&
|
|
1353
|
+
retainedFence.phase === "commit-ready" &&
|
|
1354
|
+
matchesTelegramWorkspaceRetirementFence(retainedFence, intent)) {
|
|
1355
|
+
const resumed = await input.runExclusive(async () => {
|
|
1356
|
+
const epoch = input.getLeaderEpoch();
|
|
1357
|
+
const profileKey = input.getProfileKey();
|
|
1358
|
+
const isCurrent = () => epoch !== undefined && input.getLeaderEpoch() === epoch &&
|
|
1359
|
+
input.getProfileKey() === profileKey && input.isCurrent?.() !== false;
|
|
1360
|
+
if (!isCurrent() || intent!.profileKey !== profileKey) return undefined;
|
|
1361
|
+
const intents = input.store.listWorkspaceRetirementIntents();
|
|
1362
|
+
const binding = input.store.listWorkspaceBindings().find(candidate =>
|
|
1363
|
+
candidate.bindingKey === intent!.binding.bindingKey);
|
|
1364
|
+
if (intents.length !== 1 || !isDeepStrictEqual(intents[0], intent) ||
|
|
1365
|
+
!binding || !isDeepStrictEqual(binding, intent.binding)) return undefined;
|
|
1366
|
+
if (intent!.leaderEpoch === epoch) return intent;
|
|
1367
|
+
const replacement = { ...intent!, leaderEpoch: epoch! };
|
|
1368
|
+
return await input.store.replaceWorkspaceRetirementIntent(intent!, replacement, isCurrent)
|
|
1369
|
+
? replacement : undefined;
|
|
1370
|
+
});
|
|
1371
|
+
if (!resumed) return { kind: "blocked", stage: "adoption", reason: "commit-rejected" };
|
|
1372
|
+
intent = resumed;
|
|
1373
|
+
} else {
|
|
1374
|
+
const adoption = await adoptTelegramWorkspaceRetirementIntent({
|
|
1375
|
+
store: input.store,
|
|
1376
|
+
intent,
|
|
1377
|
+
getExternalProtection: input.getExternalProtection,
|
|
1378
|
+
getLeaderEpoch: input.getLeaderEpoch,
|
|
1379
|
+
getProfileKey: input.getProfileKey,
|
|
1380
|
+
isCurrent: input.isCurrent,
|
|
1381
|
+
runExclusive: input.runExclusive,
|
|
1382
|
+
});
|
|
1383
|
+
if (adoption.kind !== "adopted") {
|
|
1384
|
+
return { kind: "blocked", stage: "adoption", reason: adoption.reason };
|
|
1385
|
+
}
|
|
1386
|
+
intent = adoption.intent;
|
|
1271
1387
|
}
|
|
1272
|
-
intent = adoption.intent;
|
|
1273
1388
|
} else {
|
|
1274
1389
|
const preparation = await prepareTelegramWorkspaceRetirement(
|
|
1275
1390
|
{
|
|
@@ -1306,6 +1421,8 @@ export type TelegramWorkspaceCapacityRunner = <T>(operation: () => Promise<T>) =
|
|
|
1306
1421
|
export interface TelegramWorkspaceSlotRotationPorts extends TelegramWorkspaceOperationGate {
|
|
1307
1422
|
getAdmission: () => TelegramWorkspaceAdmissionLedger | undefined;
|
|
1308
1423
|
deleteThread: TelegramWorkspaceThreadDeletionTransport;
|
|
1424
|
+
pruneJournalEvidence?: (binding: TelegramWorkspaceThreadBinding,
|
|
1425
|
+
isCurrent: () => boolean) => Promise<TelegramWorkspaceJournalPruneResult>;
|
|
1309
1426
|
reclaimDeadOwnerQueuedWork?: (
|
|
1310
1427
|
binding: TelegramWorkspaceThreadBinding,
|
|
1311
1428
|
isCurrent: () => boolean,
|
|
@@ -1384,14 +1501,16 @@ export function createTelegramWorkspaceSlotRotation(input: TelegramWorkspaceSlot
|
|
|
1384
1501
|
try { return await operation(); }
|
|
1385
1502
|
catch (error) {
|
|
1386
1503
|
if (!(error instanceof TelegramWorkspaceSlotUnavailableError)) throw error;
|
|
1387
|
-
if (input.reclaimDeadOwnerQueuedWork) {
|
|
1504
|
+
if (input.pruneJournalEvidence || input.reclaimDeadOwnerQueuedWork) {
|
|
1388
1505
|
const candidates = await input.runExclusive(async () => {
|
|
1389
1506
|
await input.store.load();
|
|
1390
1507
|
if (!isCurrent()) throw new Error("Telegram Workspace reclamation lost leader authority.");
|
|
1391
1508
|
const snapshot = input.store.captureWorkspaceSlotOccupancy(input.getExternalProtection);
|
|
1392
1509
|
const allocation = planTelegramWorkspaceSlotAllocation({ ...snapshot, nowMs: Date.now() });
|
|
1393
|
-
if (allocation.kind
|
|
1510
|
+
if (allocation.kind === "free" || (allocation.kind === "blocked" && allocation.reason !== "protected-capacity")) return [];
|
|
1511
|
+
if (allocation.kind === "reclaim" && !input.pruneJournalEvidence) return [];
|
|
1394
1512
|
return input.store.listWorkspaceBindings().filter((binding) =>
|
|
1513
|
+
(allocation.kind !== "reclaim" || binding.bindingKey === allocation.candidate.bindingKey) &&
|
|
1395
1514
|
typeof binding.inactiveSinceMs === "number" &&
|
|
1396
1515
|
Number.isFinite(binding.inactiveSinceMs) &&
|
|
1397
1516
|
binding.inactiveSinceMs >= 0,
|
|
@@ -1400,7 +1519,20 @@ export function createTelegramWorkspaceSlotRotation(input: TelegramWorkspaceSlot
|
|
|
1400
1519
|
(left.slot ?? "").localeCompare(right.slot ?? ""),
|
|
1401
1520
|
);
|
|
1402
1521
|
});
|
|
1403
|
-
for (const
|
|
1522
|
+
for (const original of candidates) {
|
|
1523
|
+
let binding = original;
|
|
1524
|
+
if (input.pruneJournalEvidence && ((binding.journalBindingKeys?.length ?? 0) + (binding.journalSources?.length ?? 0) > 0)) {
|
|
1525
|
+
const pruned = await input.pruneJournalEvidence(binding, isCurrent);
|
|
1526
|
+
if (!isCurrent()) throw new Error("Telegram Workspace pruning lost leader authority.");
|
|
1527
|
+
if (pruned.kind !== "committed") continue;
|
|
1528
|
+
binding = pruned.binding;
|
|
1529
|
+
const removed = pruned.removedKeys.length + (pruned.removedSources?.length ?? 0);
|
|
1530
|
+
if (removed > 0) {
|
|
1531
|
+
try { input.recordEvent("Telegram Workspace journal evidence pruned.", { slot: binding.slot, removed }); }
|
|
1532
|
+
catch { /* Diagnostics cannot revoke a published metadata subset. */ }
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (!input.reclaimDeadOwnerQueuedWork) continue;
|
|
1404
1536
|
const evidence = input.getExternalProtection(binding);
|
|
1405
1537
|
if (evidence.liveOwner !== "clear" ||
|
|
1406
1538
|
evidence.acceptedWork !== "protected" ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"node": ">=22.19.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"test": "node --experimental-strip-types --test --test-concurrency=4 --test-reporter=dot tests/*.test.ts",
|
|
31
|
-
"test:verbose": "node --experimental-strip-types --test --test-reporter=spec tests/*.test.ts",
|
|
30
|
+
"test": "node --experimental-strip-types --import ./tests/fixtures/canonical-tmpdir.ts --test --test-concurrency=4 --test-reporter=dot tests/*.test.ts",
|
|
31
|
+
"test:verbose": "node --experimental-strip-types --import ./tests/fixtures/canonical-tmpdir.ts --test --test-reporter=spec tests/*.test.ts",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
33
|
"build": "node scripts/build-dist.mjs",
|
|
34
34
|
"build:check": "node scripts/build-dist.mjs --check",
|
|
@@ -109,9 +109,9 @@
|
|
|
109
109
|
"image": "https://raw.githubusercontent.com/llblab/pi-telegram/main/screenshot.png"
|
|
110
110
|
},
|
|
111
111
|
"peerDependencies": {
|
|
112
|
-
"@earendil-works/pi-agent-core": ">=0.
|
|
113
|
-
"@earendil-works/pi-ai": ">=0.
|
|
114
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
112
|
+
"@earendil-works/pi-agent-core": ">=1.0.0",
|
|
113
|
+
"@earendil-works/pi-ai": ">=1.0.0",
|
|
114
|
+
"@earendil-works/pi-coding-agent": ">=1.0.0",
|
|
115
115
|
"@sinclair/typebox": "*"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|