@llblab/pi-kit 0.27.6 → 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.
- package/BACKLOG.md +2 -5
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
- package/node_modules/@llblab/pi-telegram/README.md +15 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
- package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
- package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
- package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
- package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
- package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
- package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
- package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
- package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
- package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
- package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
- package/node_modules/@llblab/pi-telegram/package.json +6 -6
- package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
- package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/package.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
type TelegramWorkspaceDestructiveFenceKind = "pressure-retirement" | "manual-thread-cleanup" | "journal-writer-closure";
|
|
34
34
|
export type TelegramWorkspaceDeletionFenceKind = Exclude<TelegramWorkspaceDestructiveFenceKind, "journal-writer-closure">;
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
+
type TelegramWorkspaceJournalWriterProtocolInstallResult = {
|
|
121
120
|
mode: TelegramWorkspaceJournalWriterProtocolMode;
|
|
122
121
|
resumed: boolean;
|
|
123
122
|
};
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
+
interface TelegramWorkspaceAdmissionLedgerSnapshot {
|
|
141
140
|
profileKey: string;
|
|
142
141
|
leases: TelegramWorkspaceAdmissionLease[];
|
|
143
142
|
fence?: TelegramWorkspaceDestructiveFence;
|
|
144
143
|
writerProtocolMode?: TelegramWorkspaceJournalWriterProtocolMode;
|
|
145
144
|
}
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
202
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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 =
|
|
491
|
+
const botToken = getBotToken();
|
|
464
492
|
if (!botToken)
|
|
465
493
|
return undefined;
|
|
466
|
-
const configuredProfileName =
|
|
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:
|
|
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
|
-
|
|
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
|
|
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" | "
|
|
114
|
+
store: Pick<TelegramTopicTargetStore, "commitWorkspaceJournalEvidence" | "persistWorkspaceJournalEvidence">;
|
|
105
115
|
binding: TelegramWorkspaceThreadBinding;
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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. */
|