borgmcp 2.0.2 → 2.0.4
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/README.md +16 -10
- package/dist/assimilate-cmd.d.ts +0 -2
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +51 -43
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +0 -1
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +5 -0
- package/dist/claude.js.map +1 -1
- package/dist/cleanup-cmd.d.ts +1 -1
- package/dist/cleanup-cmd.d.ts.map +1 -1
- package/dist/cleanup-cmd.js +3 -1
- package/dist/cleanup-cmd.js.map +1 -1
- package/dist/cli-help.d.ts +2 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +14 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -18
- package/dist/config.js.map +1 -1
- package/dist/credential-paths.d.ts +3 -0
- package/dist/credential-paths.d.ts.map +1 -0
- package/dist/credential-paths.js +7 -0
- package/dist/credential-paths.js.map +1 -0
- package/dist/cubes.d.ts +0 -2
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +0 -2
- package/dist/cubes.js.map +1 -1
- package/dist/launch-all-cmd.d.ts.map +1 -1
- package/dist/launch-all-cmd.js +13 -7
- package/dist/launch-all-cmd.js.map +1 -1
- package/dist/log-stream.d.ts +0 -2
- package/dist/log-stream.d.ts.map +1 -1
- package/dist/log-stream.js +3 -30
- package/dist/log-stream.js.map +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +8 -23
- package/dist/remote-client.js.map +1 -1
- package/dist/seat-probe.d.ts +4 -4
- package/dist/seat-probe.d.ts.map +1 -1
- package/dist/seat-probe.js +2 -0
- package/dist/seat-probe.js.map +1 -1
- package/dist/seat-store.d.ts +11 -5
- package/dist/seat-store.d.ts.map +1 -1
- package/dist/seat-store.js +147 -14
- package/dist/seat-store.js.map +1 -1
- package/dist/seats.d.ts +0 -74
- package/dist/seats.d.ts.map +1 -1
- package/dist/seats.js +31 -131
- package/dist/seats.js.map +1 -1
- package/dist/server-errors.d.ts +6 -1
- package/dist/server-errors.d.ts.map +1 -1
- package/dist/server-errors.js +9 -0
- package/dist/server-errors.js.map +1 -1
- package/dist/server-facade.d.ts +50 -0
- package/dist/server-facade.d.ts.map +1 -0
- package/dist/server-facade.js +126 -0
- package/dist/server-facade.js.map +1 -0
- package/dist/server-handshake.d.ts +0 -2
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +0 -5
- package/dist/server-handshake.js.map +1 -1
- package/dist/token-store.d.ts +2 -1
- package/dist/token-store.d.ts.map +1 -1
- package/dist/token-store.js +5 -3
- package/dist/token-store.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -0
- package/dist/unknown-subcommand.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +6 -5
- package/docs/LOCAL_SERVER.md +86 -43
- package/docs/RELEASING.md +19 -1
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +54 -51
- package/src/assimilate-deps.ts +0 -1
- package/src/claude.ts +5 -0
- package/src/cleanup-cmd.ts +5 -2
- package/src/cli-help.ts +17 -1
- package/src/config.ts +40 -18
- package/src/credential-paths.ts +8 -0
- package/src/cubes.ts +0 -4
- package/src/launch-all-cmd.ts +15 -7
- package/src/log-stream.ts +2 -30
- package/src/remote-client.ts +10 -22
- package/src/seat-probe.ts +5 -3
- package/src/seat-store.ts +159 -16
- package/src/seats.ts +35 -186
- package/src/server-errors.ts +9 -2
- package/src/server-facade.ts +192 -0
- package/src/server-handshake.ts +1 -10
- package/src/token-store.ts +9 -4
- package/src/unknown-subcommand.ts +1 -0
- package/dist/session-continuity.d.ts +0 -33
- package/dist/session-continuity.d.ts.map +0 -1
- package/dist/session-continuity.js +0 -220
- package/dist/session-continuity.js.map +0 -1
- package/src/session-continuity.ts +0 -280
package/src/server-errors.ts
CHANGED
|
@@ -5,8 +5,7 @@ export type BorgServerErrorCode =
|
|
|
5
5
|
| 'CREATE_CUBE_DENIED'
|
|
6
6
|
| 'ATTACH_CONFLICT'
|
|
7
7
|
| 'SESSION_REJECTED'
|
|
8
|
-
| 'SESSION_REVOKED'
|
|
9
|
-
| 'AUTH_EXPIRED';
|
|
8
|
+
| 'SESSION_REVOKED';
|
|
10
9
|
|
|
11
10
|
/** Safe, non-secret state code for deterministic authority recovery copy. */
|
|
12
11
|
export class BorgServerError extends Error {
|
|
@@ -92,4 +91,12 @@ export class BorgServerUnreachableError extends Error {
|
|
|
92
91
|
this.name = 'BorgServerUnreachableError';
|
|
93
92
|
}
|
|
94
93
|
}
|
|
94
|
+
|
|
95
|
+
/** Exact retired TTL-replacement state: two saved bearers and no safe implicit winner. */
|
|
96
|
+
export class LegacySessionCredentialCollisionError extends Error {
|
|
97
|
+
constructor(public readonly origin: string) {
|
|
98
|
+
super('Local session credential collision detected');
|
|
99
|
+
this.name = 'LegacySessionCredentialCollisionError';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
95
102
|
import type { ErrorCode } from 'borgmcp-shared/protocol';
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { spawn as spawnChild, type SpawnOptions } from 'node:child_process';
|
|
2
|
+
import { constants } from 'node:os';
|
|
3
|
+
import { serverHelpText } from './cli-help.js';
|
|
4
|
+
|
|
5
|
+
export const SERVER_LIFECYCLE_COMMANDS = ['setup', 'start', 'stop', 'status', 'update', 'invite'] as const;
|
|
6
|
+
export type ServerLifecycleCommand = typeof SERVER_LIFECYCLE_COMMANDS[number];
|
|
7
|
+
|
|
8
|
+
export type ParsedServerFacadeArgs =
|
|
9
|
+
| { kind: 'help' }
|
|
10
|
+
| { kind: 'command'; command: ServerLifecycleCommand; args: string[] }
|
|
11
|
+
| { kind: 'error'; reason: 'unknown-command'; command: string };
|
|
12
|
+
|
|
13
|
+
export function parseServerFacadeArgs(args: readonly string[]): ParsedServerFacadeArgs {
|
|
14
|
+
const [command, ...rest] = args;
|
|
15
|
+
if (command === undefined || command === '--help' || command === '-h') {
|
|
16
|
+
return { kind: 'help' };
|
|
17
|
+
}
|
|
18
|
+
if (!(SERVER_LIFECYCLE_COMMANDS as readonly string[]).includes(command)) {
|
|
19
|
+
return { kind: 'error', reason: 'unknown-command', command };
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
kind: 'command',
|
|
23
|
+
command: command as ServerLifecycleCommand,
|
|
24
|
+
args: rest,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ServerFacadeChild {
|
|
29
|
+
once(event: 'error', listener: (error: Error) => void): this;
|
|
30
|
+
once(
|
|
31
|
+
event: 'exit',
|
|
32
|
+
listener: (code: number | null, signal: NodeJS.Signals | null) => void,
|
|
33
|
+
): this;
|
|
34
|
+
kill(signal: NodeJS.Signals): boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ServerFacadeProcessDeps {
|
|
38
|
+
spawn(
|
|
39
|
+
command: string,
|
|
40
|
+
args: readonly string[],
|
|
41
|
+
options: Pick<SpawnOptions, 'shell' | 'stdio'>,
|
|
42
|
+
): ServerFacadeChild;
|
|
43
|
+
addSignalListener(signal: NodeJS.Signals, listener: () => void): void;
|
|
44
|
+
removeSignalListener(signal: NodeJS.Signals, listener: () => void): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ServerFacadeOutputDeps {
|
|
48
|
+
writeStdout(text: string): void;
|
|
49
|
+
writeStderr(text: string): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ServerFacadeProcessResult =
|
|
53
|
+
| { kind: 'exited'; code: number }
|
|
54
|
+
| { kind: 'signaled'; signal: NodeJS.Signals }
|
|
55
|
+
| { kind: 'spawn-error'; error: Error };
|
|
56
|
+
|
|
57
|
+
const defaultProcessDeps: ServerFacadeProcessDeps = {
|
|
58
|
+
spawn: (command, args, options) => spawnChild(command, [...args], options),
|
|
59
|
+
addSignalListener: (signal, listener) => process.on(signal, listener),
|
|
60
|
+
removeSignalListener: (signal, listener) => process.off(signal, listener),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const defaultOutputDeps: ServerFacadeOutputDeps = {
|
|
64
|
+
writeStdout: (text) => process.stdout.write(text),
|
|
65
|
+
writeStderr: (text) => process.stderr.write(text),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const MAX_RENDERED_COMMAND_CODE_POINTS = 80;
|
|
69
|
+
|
|
70
|
+
function inertCommand(command: string): string {
|
|
71
|
+
const rendered: string[] = [];
|
|
72
|
+
let truncated = false;
|
|
73
|
+
for (const codePoint of command) {
|
|
74
|
+
if (rendered.length === MAX_RENDERED_COMMAND_CODE_POINTS) {
|
|
75
|
+
truncated = true;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
rendered.push(/\p{Cc}/u.test(codePoint) ? '?' : codePoint);
|
|
79
|
+
}
|
|
80
|
+
if (truncated) {
|
|
81
|
+
return `${rendered.slice(0, MAX_RENDERED_COMMAND_CODE_POINTS - 3).join('')}...`;
|
|
82
|
+
}
|
|
83
|
+
return rendered.join('');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function unknownServerCommandText(command: string): string {
|
|
87
|
+
return (
|
|
88
|
+
`Unknown server command: ${inertCommand(command)}.\n` +
|
|
89
|
+
`Available commands: setup, start, stop, status, update, invite.\n` +
|
|
90
|
+
`Next: run borg server --help.\n`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function missingServerExecutableText(command: ServerLifecycleCommand): string {
|
|
95
|
+
return (
|
|
96
|
+
`Local server command is unavailable: borg-mcp-server was not found.\n` +
|
|
97
|
+
`Next: install a verified borgmcp-server release, then rerun borg server ${command}.\n` +
|
|
98
|
+
`No checkout fallback is attempted.\n`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function serverCommandStartupFailureText(command: ServerLifecycleCommand): string {
|
|
103
|
+
return (
|
|
104
|
+
`Local server command could not be started.\n` +
|
|
105
|
+
`Next: check local permissions and system resources, then rerun borg server ${inertCommand(command)}.\n` +
|
|
106
|
+
`No server command was started.\n`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isMissingServerExecutable(error: Error): boolean {
|
|
111
|
+
return (error as NodeJS.ErrnoException).code === 'ENOENT';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function runServerFacadeProcess(
|
|
115
|
+
input: { command: ServerLifecycleCommand; args: readonly string[] },
|
|
116
|
+
deps: ServerFacadeProcessDeps = defaultProcessDeps,
|
|
117
|
+
): Promise<ServerFacadeProcessResult> {
|
|
118
|
+
const child = deps.spawn(
|
|
119
|
+
'borg-mcp-server',
|
|
120
|
+
[input.command, ...input.args],
|
|
121
|
+
{ shell: false, stdio: 'inherit' },
|
|
122
|
+
);
|
|
123
|
+
const signals = ['SIGINT', 'SIGTERM'] as const;
|
|
124
|
+
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
let settled = false;
|
|
127
|
+
const forwarders = new Map<NodeJS.Signals, () => void>();
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
for (const [signal, listener] of forwarders) {
|
|
130
|
+
deps.removeSignalListener(signal, listener);
|
|
131
|
+
}
|
|
132
|
+
forwarders.clear();
|
|
133
|
+
};
|
|
134
|
+
const settle = (result: ServerFacadeProcessResult) => {
|
|
135
|
+
if (settled) return;
|
|
136
|
+
settled = true;
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve(result);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
for (const signal of signals) {
|
|
142
|
+
const listener = () => {
|
|
143
|
+
child.kill(signal);
|
|
144
|
+
};
|
|
145
|
+
forwarders.set(signal, listener);
|
|
146
|
+
deps.addSignalListener(signal, listener);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
child.once('error', (error) => settle({ kind: 'spawn-error', error }));
|
|
150
|
+
child.once('exit', (code, signal) => {
|
|
151
|
+
if (signal) {
|
|
152
|
+
settle({ kind: 'signaled', signal });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
settle({ kind: 'exited', code: code ?? 1 });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function processResultExitCode(result: ServerFacadeProcessResult): number {
|
|
161
|
+
if (result.kind === 'exited') return result.code;
|
|
162
|
+
if (result.kind === 'spawn-error') return isMissingServerExecutable(result.error) ? 127 : 1;
|
|
163
|
+
return 128 + (constants.signals[result.signal] ?? 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Routes every facade outcome before client initialization or network work. */
|
|
167
|
+
export async function runEarlyServerFacade(
|
|
168
|
+
argv: readonly string[],
|
|
169
|
+
deps: ServerFacadeProcessDeps = defaultProcessDeps,
|
|
170
|
+
output: ServerFacadeOutputDeps = defaultOutputDeps,
|
|
171
|
+
): Promise<number | null> {
|
|
172
|
+
if (argv[2] !== 'server') return null;
|
|
173
|
+
const parsed = parseServerFacadeArgs(argv.slice(3));
|
|
174
|
+
if (parsed.kind === 'help') {
|
|
175
|
+
output.writeStdout(serverHelpText());
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
if (parsed.kind === 'error') {
|
|
179
|
+
output.writeStderr(unknownServerCommandText(parsed.command));
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const result = await runServerFacadeProcess(parsed, deps);
|
|
184
|
+
if (result.kind === 'spawn-error') {
|
|
185
|
+
output.writeStderr(
|
|
186
|
+
isMissingServerExecutable(result.error)
|
|
187
|
+
? missingServerExecutableText(parsed.command)
|
|
188
|
+
: serverCommandStartupFailureText(parsed.command),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return processResultExitCode(result);
|
|
192
|
+
}
|
package/src/server-handshake.ts
CHANGED
|
@@ -175,7 +175,6 @@ export interface ServerAttachResult {
|
|
|
175
175
|
session: {
|
|
176
176
|
credentialRef: string;
|
|
177
177
|
sessionId: string;
|
|
178
|
-
expiresAt: string;
|
|
179
178
|
};
|
|
180
179
|
result: 'created' | 'reused';
|
|
181
180
|
}
|
|
@@ -202,7 +201,7 @@ export interface PreparedServerAttach {
|
|
|
202
201
|
cube: ServerAttachResult['cube'];
|
|
203
202
|
role: ServerAttachResult['role'];
|
|
204
203
|
drone: ServerAttachResult['drone'];
|
|
205
|
-
session: { sessionId: string
|
|
204
|
+
session: { sessionId: string };
|
|
206
205
|
result: 'created' | 'reused';
|
|
207
206
|
credentialRef: string;
|
|
208
207
|
pendingBearerDigest: string;
|
|
@@ -304,12 +303,6 @@ export async function sendBorgServerAttach(
|
|
|
304
303
|
'Borg server rejected the session: the seat is already bound to another session',
|
|
305
304
|
);
|
|
306
305
|
}
|
|
307
|
-
if (rejectedCode === ErrorCode.AUTH_EXPIRED) {
|
|
308
|
-
throw new BorgServerError(
|
|
309
|
-
'AUTH_EXPIRED',
|
|
310
|
-
'Borg server session expired',
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
306
|
if (rejectedCode === ErrorCode.SESSION_REVOKED) {
|
|
314
307
|
throw new BorgServerError(
|
|
315
308
|
'SESSION_REVOKED',
|
|
@@ -358,7 +351,6 @@ export async function sendBorgServerAttach(
|
|
|
358
351
|
drone: decoded.drone,
|
|
359
352
|
session: {
|
|
360
353
|
sessionId: decoded.session.id,
|
|
361
|
-
expiresAt: decoded.session.expires_at,
|
|
362
354
|
},
|
|
363
355
|
result: decoded.result,
|
|
364
356
|
credentialRef,
|
|
@@ -374,7 +366,6 @@ export async function sendBorgServerAttach(
|
|
|
374
366
|
...seatInput,
|
|
375
367
|
droneId: decoded.drone.id,
|
|
376
368
|
sessionId: decoded.session.id,
|
|
377
|
-
expiresAt: decoded.session.expires_at,
|
|
378
369
|
expectedPendingDigest: pendingBearerDigest,
|
|
379
370
|
worktree: binding.worktree,
|
|
380
371
|
name: binding.name,
|
package/src/token-store.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* private keys; there is no keychain and no obfuscation-grade fallback.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { atomicWrite0600, readStoreFile } from './seat-store.js';
|
|
10
|
+
import { atomicWrite0600, readStoreFile, type SecureStoreOptions } from './seat-store.js';
|
|
11
|
+
import { dirname } from 'node:path';
|
|
11
12
|
|
|
12
13
|
// The OS-keychain backend was retired with the Queen rescope; the only backend
|
|
13
14
|
// name is the 0600 file store.
|
|
@@ -37,9 +38,13 @@ export interface TokenBackend {
|
|
|
37
38
|
* lockfile. Pure reads (get) are safe lock-free because atomicWrite0600's rename
|
|
38
39
|
* guarantees a reader only ever sees a complete file.
|
|
39
40
|
*/
|
|
40
|
-
export function makeFileBackend(
|
|
41
|
+
export function makeFileBackend(
|
|
42
|
+
filePath: string,
|
|
43
|
+
storeOptions: SecureStoreOptions = {},
|
|
44
|
+
): TokenBackend {
|
|
45
|
+
const options = { secureRoot: dirname(filePath), ...storeOptions };
|
|
41
46
|
const load = async (): Promise<Record<string, string>> => {
|
|
42
|
-
const raw = await readStoreFile(filePath);
|
|
47
|
+
const raw = await readStoreFile(filePath, options);
|
|
43
48
|
// CR4 fail-closed: ONLY a missing file initializes empty. A present-but-
|
|
44
49
|
// malformed / wrong-version / schema-invalid credential store MUST NOT read as
|
|
45
50
|
// empty — a subsequent set/delete would OVERWRITE it and erase every stored
|
|
@@ -79,7 +84,7 @@ export function makeFileBackend(filePath: string): TokenBackend {
|
|
|
79
84
|
return { ...(accounts as Record<string, string>) };
|
|
80
85
|
};
|
|
81
86
|
const save = (accounts: Record<string, string>): Promise<void> =>
|
|
82
|
-
atomicWrite0600(filePath, JSON.stringify({ version: 1, accounts }, null, 2) + '\n');
|
|
87
|
+
atomicWrite0600(filePath, JSON.stringify({ version: 1, accounts }, null, 2) + '\n', options);
|
|
83
88
|
return {
|
|
84
89
|
name: 'file',
|
|
85
90
|
async get(account) {
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { type ActiveCube } from './cubes.js';
|
|
2
|
-
import { prepareSeatReplacement, promoteSeatReplacement, refreshActiveSeatSession, scrubSeatReplacement, type SeatRecord } from './seats.js';
|
|
3
|
-
export declare const SESSION_RENEWAL_LEAD_MS: number;
|
|
4
|
-
interface AttachOutcome {
|
|
5
|
-
result: 'created' | 'reused';
|
|
6
|
-
droneId: string;
|
|
7
|
-
sessionId: string;
|
|
8
|
-
expiresAt: string;
|
|
9
|
-
}
|
|
10
|
-
interface SessionContinuityDeps {
|
|
11
|
-
now(): number;
|
|
12
|
-
randomCredential(): string;
|
|
13
|
-
getSeat(ref: string, binding: {
|
|
14
|
-
origin: string;
|
|
15
|
-
trustIdentity: string;
|
|
16
|
-
cubeId: string;
|
|
17
|
-
}): Promise<SeatRecord | null>;
|
|
18
|
-
getActive(): Promise<ActiveCube | null>;
|
|
19
|
-
getParentCredential(origin: string, trustIdentity: string): Promise<string | null>;
|
|
20
|
-
sendAttach(record: SeatRecord, bearer: string, parentCredential: string): Promise<AttachOutcome>;
|
|
21
|
-
prepareReplacement: typeof prepareSeatReplacement;
|
|
22
|
-
refreshSession: typeof refreshActiveSeatSession;
|
|
23
|
-
promoteReplacement: typeof promoteSeatReplacement;
|
|
24
|
-
scrubReplacement: typeof scrubSeatReplacement;
|
|
25
|
-
}
|
|
26
|
-
export declare function ensureLocalSessionFresh(active: ActiveCube): Promise<ActiveCube>;
|
|
27
|
-
export declare function recoverExpiredLocalSession(active: ActiveCube): Promise<ActiveCube>;
|
|
28
|
-
/** @internal */
|
|
29
|
-
export declare function __renewLocalSessionForTest(active: ActiveCube, mode: 'proactive' | 'expired', deps: SessionContinuityDeps): Promise<ActiveCube>;
|
|
30
|
-
/** @internal */
|
|
31
|
-
export declare function __resetSessionContinuityForTest(): void;
|
|
32
|
-
export {};
|
|
33
|
-
//# sourceMappingURL=session-continuity.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-continuity.d.ts","sourceRoot":"","sources":["../src/session-continuity.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAK5D,OAAO,EAEL,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EAEpB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAElD,UAAU,aAAa;IACrB,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,qBAAqB;IAC7B,GAAG,IAAI,MAAM,CAAC;IACd,gBAAgB,IAAI,MAAM,CAAC;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACrH,SAAS,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnF,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACjG,kBAAkB,EAAE,OAAO,sBAAsB,CAAC;IAClD,cAAc,EAAE,OAAO,wBAAwB,CAAC;IAChD,kBAAkB,EAAE,OAAO,sBAAsB,CAAC;IAClD,gBAAgB,EAAE,OAAO,oBAAoB,CAAC;CAC/C;AA8ND,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAE/E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAElF;AAED,gBAAgB;AAChB,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,UAAU,CAAC,CAErB;AAED,gBAAgB;AAChB,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { getServerCredential } from './config.js';
|
|
3
|
-
import { getActiveCube } from './cubes.js';
|
|
4
|
-
import { DroneEvictedError } from './drone-lifecycle.js';
|
|
5
|
-
import { sendBorgServerAttach } from './server-handshake.js';
|
|
6
|
-
import { BorgServerError, BorgServerTrustError } from './server-errors.js';
|
|
7
|
-
import { loadBorgServerTrust } from './server-trust.js';
|
|
8
|
-
import { getActiveSeat, prepareSeatReplacement, promoteSeatReplacement, refreshActiveSeatSession, scrubSeatReplacement, seatRef, } from './seats.js';
|
|
9
|
-
export const SESSION_RENEWAL_LEAD_MS = 5 * 60_000;
|
|
10
|
-
const defaultDeps = {
|
|
11
|
-
now: Date.now,
|
|
12
|
-
randomCredential: () => randomBytes(32).toString('base64url'),
|
|
13
|
-
getSeat: getActiveSeat,
|
|
14
|
-
getActive: getActiveCube,
|
|
15
|
-
getParentCredential: getServerCredential,
|
|
16
|
-
async sendAttach(record, bearer, parentCredential) {
|
|
17
|
-
const trust = await loadBorgServerTrust(record.origin);
|
|
18
|
-
if (trust.identity !== record.trustIdentity) {
|
|
19
|
-
throw new BorgServerTrustError('Borg server trust identity changed; refusing session renewal');
|
|
20
|
-
}
|
|
21
|
-
const attached = await sendBorgServerAttach(record.origin, record.trustIdentity, parentCredential, {
|
|
22
|
-
cubeId: record.cubeId,
|
|
23
|
-
roleId: record.roleId,
|
|
24
|
-
operation: record.operation,
|
|
25
|
-
priorDroneId: record.droneId,
|
|
26
|
-
}, bearer, { fetchImpl: trust.fetchImpl });
|
|
27
|
-
return {
|
|
28
|
-
result: attached.result,
|
|
29
|
-
droneId: attached.drone.id,
|
|
30
|
-
sessionId: attached.session.sessionId,
|
|
31
|
-
expiresAt: attached.session.expiresAt,
|
|
32
|
-
};
|
|
33
|
-
},
|
|
34
|
-
prepareReplacement: prepareSeatReplacement,
|
|
35
|
-
refreshSession: refreshActiveSeatSession,
|
|
36
|
-
promoteReplacement: promoteSeatReplacement,
|
|
37
|
-
scrubReplacement: scrubSeatReplacement,
|
|
38
|
-
};
|
|
39
|
-
const inflight = new Map();
|
|
40
|
-
const digestOf = (value) => createHash('sha256').update(value).digest('hex');
|
|
41
|
-
function sameSeat(active, candidate) {
|
|
42
|
-
return candidate !== null &&
|
|
43
|
-
candidate.apiUrl === active.apiUrl &&
|
|
44
|
-
candidate.serverTrustIdentity === active.serverTrustIdentity &&
|
|
45
|
-
candidate.cubeId === active.cubeId &&
|
|
46
|
-
candidate.droneId === active.droneId &&
|
|
47
|
-
candidate.localSessionCredentialRef === active.localSessionCredentialRef;
|
|
48
|
-
}
|
|
49
|
-
function seatBinding(record) {
|
|
50
|
-
return {
|
|
51
|
-
origin: record.origin,
|
|
52
|
-
trustIdentity: record.trustIdentity,
|
|
53
|
-
cubeId: record.cubeId,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function validActiveRecord(active, record) {
|
|
57
|
-
return record !== null &&
|
|
58
|
-
record.state === 'active' &&
|
|
59
|
-
record.origin === active.apiUrl &&
|
|
60
|
-
record.trustIdentity === active.serverTrustIdentity &&
|
|
61
|
-
record.cubeId === active.cubeId &&
|
|
62
|
-
record.droneId === active.droneId &&
|
|
63
|
-
seatRef(record) === active.localSessionCredentialRef;
|
|
64
|
-
}
|
|
65
|
-
function deterministicAttachFailure(error) {
|
|
66
|
-
return error instanceof BorgServerError ||
|
|
67
|
-
error instanceof BorgServerTrustError ||
|
|
68
|
-
error instanceof DroneEvictedError;
|
|
69
|
-
}
|
|
70
|
-
async function reloadSameSeat(active, deps, requireFreshCredential = false) {
|
|
71
|
-
const current = await deps.getActive();
|
|
72
|
-
if (!sameSeat(active, current) || (requireFreshCredential && current.sessionToken === active.sessionToken)) {
|
|
73
|
-
throw new Error('Session renewal no longer targets the same saved seat');
|
|
74
|
-
}
|
|
75
|
-
return current;
|
|
76
|
-
}
|
|
77
|
-
async function performRenewal(active, mode, deps) {
|
|
78
|
-
if (!active.serverTrustIdentity || !active.localSessionCredentialRef) {
|
|
79
|
-
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
80
|
-
}
|
|
81
|
-
const binding = {
|
|
82
|
-
origin: active.apiUrl,
|
|
83
|
-
trustIdentity: active.serverTrustIdentity,
|
|
84
|
-
cubeId: active.cubeId,
|
|
85
|
-
};
|
|
86
|
-
const record = await deps.getSeat(active.localSessionCredentialRef, binding);
|
|
87
|
-
if (!validActiveRecord(active, record)) {
|
|
88
|
-
throw new Error('Session renewal no longer targets the same saved seat');
|
|
89
|
-
}
|
|
90
|
-
const activeDigest = digestOf(active.sessionToken);
|
|
91
|
-
if (digestOf(record.credential) !== activeDigest) {
|
|
92
|
-
return reloadSameSeat(active, deps, true);
|
|
93
|
-
}
|
|
94
|
-
const expiresIn = Date.parse(record.expiresAt) - deps.now();
|
|
95
|
-
if (mode === 'proactive' && expiresIn > SESSION_RENEWAL_LEAD_MS && record.replacement === undefined) {
|
|
96
|
-
return active;
|
|
97
|
-
}
|
|
98
|
-
const parentCredential = await deps.getParentCredential(record.origin, record.trustIdentity);
|
|
99
|
-
if (!parentCredential)
|
|
100
|
-
throw new Error('Selected Borg server parent credential is missing or unreadable');
|
|
101
|
-
const recordBinding = seatBinding(record);
|
|
102
|
-
let restoreRequired = mode === 'expired' || expiresIn <= 0 || record.replacement !== undefined;
|
|
103
|
-
if (!restoreRequired) {
|
|
104
|
-
let attached = null;
|
|
105
|
-
try {
|
|
106
|
-
attached = await deps.sendAttach(record, record.credential, parentCredential);
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
if (error instanceof BorgServerError && error.code === 'AUTH_EXPIRED') {
|
|
110
|
-
restoreRequired = true;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (!restoreRequired) {
|
|
117
|
-
if (!attached)
|
|
118
|
-
throw new Error('Session renewal returned no result');
|
|
119
|
-
const renewedExpiry = Date.parse(attached.expiresAt);
|
|
120
|
-
if (attached.result !== 'reused' ||
|
|
121
|
-
attached.droneId !== record.droneId ||
|
|
122
|
-
attached.sessionId !== record.sessionId ||
|
|
123
|
-
!Number.isFinite(renewedExpiry) ||
|
|
124
|
-
renewedExpiry <= Date.parse(record.expiresAt)) {
|
|
125
|
-
throw new Error('Session renewal did not preserve the same saved seat');
|
|
126
|
-
}
|
|
127
|
-
const refreshed = await deps.refreshSession({
|
|
128
|
-
ref: active.localSessionCredentialRef,
|
|
129
|
-
binding: recordBinding,
|
|
130
|
-
expectedDroneId: record.droneId,
|
|
131
|
-
expectedActiveDigest: activeDigest,
|
|
132
|
-
expectedSessionId: record.sessionId,
|
|
133
|
-
expiresAt: attached.expiresAt,
|
|
134
|
-
});
|
|
135
|
-
if (!refreshed)
|
|
136
|
-
return reloadSameSeat(active, deps);
|
|
137
|
-
return reloadSameSeat(active, deps);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const replacement = await deps.prepareReplacement({
|
|
141
|
-
ref: active.localSessionCredentialRef,
|
|
142
|
-
binding: recordBinding,
|
|
143
|
-
expectedDroneId: record.droneId,
|
|
144
|
-
expectedActiveDigest: activeDigest,
|
|
145
|
-
replacementCredential: deps.randomCredential(),
|
|
146
|
-
});
|
|
147
|
-
if (!replacement.ok)
|
|
148
|
-
return reloadSameSeat(active, deps, true);
|
|
149
|
-
let attached;
|
|
150
|
-
try {
|
|
151
|
-
attached = await deps.sendAttach(record, replacement.credential, parentCredential);
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
if (deterministicAttachFailure(error)) {
|
|
155
|
-
await deps.scrubReplacement({
|
|
156
|
-
ref: active.localSessionCredentialRef,
|
|
157
|
-
binding: recordBinding,
|
|
158
|
-
expectedActiveDigest: activeDigest,
|
|
159
|
-
expectedReplacementDigest: replacement.digest,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
const restoredExpiry = Date.parse(attached.expiresAt);
|
|
165
|
-
if ((attached.result !== 'created' && attached.result !== 'reused') ||
|
|
166
|
-
attached.droneId !== record.droneId ||
|
|
167
|
-
attached.sessionId === record.sessionId ||
|
|
168
|
-
!Number.isFinite(restoredExpiry) ||
|
|
169
|
-
restoredExpiry <= deps.now()) {
|
|
170
|
-
await deps.scrubReplacement({
|
|
171
|
-
ref: active.localSessionCredentialRef,
|
|
172
|
-
binding: recordBinding,
|
|
173
|
-
expectedActiveDigest: activeDigest,
|
|
174
|
-
expectedReplacementDigest: replacement.digest,
|
|
175
|
-
});
|
|
176
|
-
throw new Error('Session renewal did not preserve the same saved seat');
|
|
177
|
-
}
|
|
178
|
-
const promoted = await deps.promoteReplacement({
|
|
179
|
-
ref: active.localSessionCredentialRef,
|
|
180
|
-
binding: recordBinding,
|
|
181
|
-
expectedDroneId: record.droneId,
|
|
182
|
-
expectedActiveDigest: activeDigest,
|
|
183
|
-
expectedReplacementDigest: replacement.digest,
|
|
184
|
-
sessionId: attached.sessionId,
|
|
185
|
-
expiresAt: attached.expiresAt,
|
|
186
|
-
});
|
|
187
|
-
if (promoted !== 'promoted')
|
|
188
|
-
return reloadSameSeat(active, deps, true);
|
|
189
|
-
return reloadSameSeat(active, deps, true);
|
|
190
|
-
}
|
|
191
|
-
function renewLocalSession(active, mode, deps) {
|
|
192
|
-
const key = active.localSessionCredentialRef;
|
|
193
|
-
if (!key)
|
|
194
|
-
return Promise.reject(new Error('Selected Borg server authority state is missing or unreadable'));
|
|
195
|
-
const existing = inflight.get(key);
|
|
196
|
-
if (existing)
|
|
197
|
-
return existing;
|
|
198
|
-
const renewal = performRenewal(active, mode, deps);
|
|
199
|
-
inflight.set(key, renewal);
|
|
200
|
-
void renewal.finally(() => {
|
|
201
|
-
if (inflight.get(key) === renewal)
|
|
202
|
-
inflight.delete(key);
|
|
203
|
-
}).catch(() => { });
|
|
204
|
-
return renewal;
|
|
205
|
-
}
|
|
206
|
-
export function ensureLocalSessionFresh(active) {
|
|
207
|
-
return renewLocalSession(active, 'proactive', defaultDeps);
|
|
208
|
-
}
|
|
209
|
-
export function recoverExpiredLocalSession(active) {
|
|
210
|
-
return renewLocalSession(active, 'expired', defaultDeps);
|
|
211
|
-
}
|
|
212
|
-
/** @internal */
|
|
213
|
-
export function __renewLocalSessionForTest(active, mode, deps) {
|
|
214
|
-
return renewLocalSession(active, mode, deps);
|
|
215
|
-
}
|
|
216
|
-
/** @internal */
|
|
217
|
-
export function __resetSessionContinuityForTest() {
|
|
218
|
-
inflight.clear();
|
|
219
|
-
}
|
|
220
|
-
//# sourceMappingURL=session-continuity.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-continuity.js","sourceRoot":"","sources":["../src/session-continuity.ts"],"names":[],"mappings":"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"}
|