borgmcp 2.0.11 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/assimilate-cmd.d.ts +22 -4
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +109 -110
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts +7 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +69 -25
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/claude.d.ts +3 -1
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +26 -12
- package/dist/claude.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 +25 -7
- package/dist/cli-help.js.map +1 -1
- package/dist/config.d.ts +7 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -4
- package/dist/config.js.map +1 -1
- package/dist/remote-client.js +1 -1
- package/dist/remote-client.js.map +1 -1
- package/dist/repository-cube-init.d.ts +72 -0
- package/dist/repository-cube-init.d.ts.map +1 -0
- package/dist/repository-cube-init.js +341 -0
- package/dist/repository-cube-init.js.map +1 -0
- package/dist/repository-identity.d.ts +29 -0
- package/dist/repository-identity.d.ts.map +1 -0
- package/dist/repository-identity.js +143 -0
- package/dist/repository-identity.js.map +1 -0
- package/dist/server-errors.d.ts +17 -0
- package/dist/server-errors.d.ts.map +1 -1
- package/dist/server-errors.js +32 -0
- package/dist/server-errors.js.map +1 -1
- package/dist/server-facade.d.ts +11 -1
- package/dist/server-facade.d.ts.map +1 -1
- package/dist/server-facade.js +33 -3
- package/dist/server-facade.js.map +1 -1
- package/dist/server-handshake.d.ts +38 -4
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +175 -13
- package/dist/server-handshake.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +9 -7
- package/docs/LOCAL_SERVER.md +44 -9
- package/docs/RELEASING.md +21 -4
- package/package.json +2 -2
- package/src/assimilate-cmd.ts +179 -137
- package/src/assimilate-deps.ts +96 -24
- package/src/claude.ts +34 -14
- package/src/cli-help.ts +28 -7
- package/src/config.ts +30 -8
- package/src/remote-client.ts +1 -1
- package/src/repository-cube-init.ts +452 -0
- package/src/repository-identity.ts +199 -0
- package/src/server-errors.ts +41 -0
- package/src/server-facade.ts +47 -2
- package/src/server-handshake.ts +258 -14
package/src/assimilate-deps.ts
CHANGED
|
@@ -25,10 +25,12 @@ import {
|
|
|
25
25
|
} from './remote-client.js';
|
|
26
26
|
import {
|
|
27
27
|
DEFAULT_LOCAL_SERVER_ORIGIN,
|
|
28
|
+
associateLocalBorgServerRepositoryCube,
|
|
28
29
|
connectLocalBorgServer,
|
|
29
30
|
createLocalBorgServerCube,
|
|
30
31
|
enrollLocalBorgServer,
|
|
31
32
|
probeLocalBorgServer,
|
|
33
|
+
resolveLocalBorgServerRepositoryCube,
|
|
32
34
|
resumeLocalBorgServerEnrollment,
|
|
33
35
|
sendBorgServerAttach,
|
|
34
36
|
} from './server-handshake.js';
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
} from './cubes.js';
|
|
46
48
|
import { loadBorgServerTrust } from './server-trust.js';
|
|
47
49
|
import { defaultProbeSeat } from './seat-probe.js';
|
|
48
|
-
import { BorgServerError } from './server-errors.js';
|
|
50
|
+
import { BorgServerError, CubeCreationConfirmationError } from './server-errors.js';
|
|
49
51
|
import {
|
|
50
52
|
findProjectRoot as cubesFindProjectRoot,
|
|
51
53
|
getActiveCube as cubesGetActive,
|
|
@@ -61,8 +63,47 @@ import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote
|
|
|
61
63
|
import { findLoadedCodexThread } from './codex-app-server.js';
|
|
62
64
|
import { defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
|
|
63
65
|
import { ensurePrivateBorgConfigRoot } from './private-root.js';
|
|
66
|
+
import {
|
|
67
|
+
getOrCreateRepositoryIdentity,
|
|
68
|
+
getRepositoryAssociation,
|
|
69
|
+
resolveGitRepositoryContext,
|
|
70
|
+
saveRepositoryAssociation,
|
|
71
|
+
} from './repository-identity.js';
|
|
72
|
+
import { PromptInterruptedError } from './repository-cube-init.js';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Wraps the readline question operation with the production interruption
|
|
76
|
+
* mapping. Tests inject the question operation; production uses readline.
|
|
77
|
+
*/
|
|
78
|
+
export type PromptQuestion = (message: string) => Promise<string>;
|
|
79
|
+
|
|
80
|
+
async function defaultPromptQuestion(message: string): Promise<string> {
|
|
81
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
82
|
+
try {
|
|
83
|
+
return await rl.question(message);
|
|
84
|
+
} finally {
|
|
85
|
+
rl.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createPromptAdapter(
|
|
90
|
+
question: PromptQuestion = defaultPromptQuestion,
|
|
91
|
+
): (message: string) => Promise<string> {
|
|
92
|
+
return async (message: string): Promise<string> => {
|
|
93
|
+
try {
|
|
94
|
+
return await question(message);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err instanceof Error && (err.message === 'SIGINT' || err.message === 'Interrupted by signal.')) {
|
|
97
|
+
throw new PromptInterruptedError();
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
64
103
|
|
|
65
|
-
export function buildDefaultAssimilateDeps(
|
|
104
|
+
export function buildDefaultAssimilateDeps(
|
|
105
|
+
question: PromptQuestion = defaultPromptQuestion,
|
|
106
|
+
): AssimilateDeps {
|
|
66
107
|
return {
|
|
67
108
|
runSync: (cmd, args, cwd) => {
|
|
68
109
|
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
|
|
@@ -97,14 +138,7 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
|
97
138
|
|
|
98
139
|
stderr: (line) => process.stderr.write(line),
|
|
99
140
|
stdout: (line) => process.stdout.write(line),
|
|
100
|
-
prompt:
|
|
101
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
102
|
-
try {
|
|
103
|
-
return await rl.question(message);
|
|
104
|
-
} finally {
|
|
105
|
-
rl.close();
|
|
106
|
-
}
|
|
107
|
-
},
|
|
141
|
+
prompt: createPromptAdapter(question),
|
|
108
142
|
promptSecret: async (message: string): Promise<string> => {
|
|
109
143
|
const result = await prompts({
|
|
110
144
|
type: 'password',
|
|
@@ -178,6 +212,10 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
|
178
212
|
: { committed: false, reason: 'activation-failed' };
|
|
179
213
|
},
|
|
180
214
|
findProjectRoot: (cwd) => cubesFindProjectRoot(cwd),
|
|
215
|
+
resolveRepositoryContext: resolveGitRepositoryContext,
|
|
216
|
+
getRepositoryIdentity: getOrCreateRepositoryIdentity,
|
|
217
|
+
getRepositoryAssociation,
|
|
218
|
+
saveRepositoryAssociation,
|
|
181
219
|
|
|
182
220
|
// gh#673 P2 (WI-1): project-local SessionStart hook for the launch root.
|
|
183
221
|
installProjectSessionHook: (projectRoot) => {
|
|
@@ -202,6 +240,29 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
|
202
240
|
...(onPending === undefined ? {} : { onPending }),
|
|
203
241
|
}),
|
|
204
242
|
|
|
243
|
+
resolveRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
|
|
244
|
+
if (serverTrustIdentity === undefined) {
|
|
245
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
246
|
+
}
|
|
247
|
+
return resolveLocalBorgServerRepositoryCube(
|
|
248
|
+
apiUrl,
|
|
249
|
+
serverTrustIdentity,
|
|
250
|
+
token,
|
|
251
|
+
input,
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
associateRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
|
|
255
|
+
if (serverTrustIdentity === undefined) {
|
|
256
|
+
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
257
|
+
}
|
|
258
|
+
return associateLocalBorgServerRepositoryCube(
|
|
259
|
+
apiUrl,
|
|
260
|
+
serverTrustIdentity,
|
|
261
|
+
token,
|
|
262
|
+
input,
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
|
|
205
266
|
listCubes: async (apiUrl, token, serverTrustIdentity) => {
|
|
206
267
|
if (serverTrustIdentity === undefined) {
|
|
207
268
|
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
@@ -237,32 +298,43 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
|
|
|
237
298
|
throw new Error('Selected Borg server authority state is missing or unreadable');
|
|
238
299
|
}
|
|
239
300
|
{
|
|
240
|
-
if (!params.name || !params.projectRoot) {
|
|
241
|
-
throw new Error('Local Borg server cube creation requires a repository name and root');
|
|
242
|
-
}
|
|
243
301
|
const created = await createLocalBorgServerCube(
|
|
244
302
|
apiUrl,
|
|
245
303
|
serverTrustIdentity,
|
|
246
304
|
token,
|
|
247
|
-
{
|
|
305
|
+
{
|
|
306
|
+
name: params.name,
|
|
307
|
+
workingRepoName: params.workingRepoName,
|
|
308
|
+
repository: params.repository,
|
|
309
|
+
template: params.template,
|
|
310
|
+
},
|
|
248
311
|
);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
312
|
+
let cube;
|
|
313
|
+
try {
|
|
314
|
+
cube = await remoteGetCube(created.cube_id, {
|
|
315
|
+
apiUrl,
|
|
316
|
+
authToken: token,
|
|
317
|
+
serverTrustIdentity,
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
throw new CubeCreationConfirmationError('The server returned a cube result that could not be read back.');
|
|
321
|
+
}
|
|
254
322
|
if (
|
|
255
323
|
cube.id !== created.cube_id ||
|
|
324
|
+
cube.name !== created.name ||
|
|
256
325
|
!Array.isArray(cube.roles) ||
|
|
257
326
|
!cube.roles.some((role: { id?: string }) => role.id === created.default_worker_role_id)
|
|
258
327
|
) {
|
|
259
|
-
throw new
|
|
328
|
+
throw new CubeCreationConfirmationError('The server returned cube details outside the creation result.');
|
|
260
329
|
}
|
|
261
330
|
return {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
331
|
+
response: created,
|
|
332
|
+
cube: {
|
|
333
|
+
id: cube.id,
|
|
334
|
+
name: cube.name,
|
|
335
|
+
roles: cube.roles,
|
|
336
|
+
drones: cube.drones ?? [],
|
|
337
|
+
},
|
|
266
338
|
};
|
|
267
339
|
}
|
|
268
340
|
},
|
package/src/claude.ts
CHANGED
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
|
|
20
20
|
import { spawn } from 'child_process';
|
|
21
21
|
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import { realpathSync } from 'node:fs';
|
|
22
23
|
import { basename } from 'node:path';
|
|
23
24
|
import { createInterface } from 'node:readline/promises';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
24
26
|
import chalk from 'chalk';
|
|
25
27
|
import { findProjectRoot, getActiveCube, inboxPathForDrone, setCodexWakeTarget, pruneDeadCodexWakeTargets } from './cubes.js';
|
|
26
28
|
import { monitorStateRootForWorktree } from './inbox-monitor.js';
|
|
@@ -81,6 +83,23 @@ import { connectOpenCodeDrone, computeOpenCodePort, createOpenCodeLaunchKickoff,
|
|
|
81
83
|
import { buildOpenCodeLaunchArgs, defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
|
|
82
84
|
import { runEarlyServerFacade } from './server-facade.js';
|
|
83
85
|
|
|
86
|
+
export type AssimilateDepsBuilder = typeof buildDefaultAssimilateDeps;
|
|
87
|
+
|
|
88
|
+
export async function runAssimilateEntry(
|
|
89
|
+
args: readonly string[],
|
|
90
|
+
buildDeps: AssimilateDepsBuilder = buildDefaultAssimilateDeps,
|
|
91
|
+
): Promise<number> {
|
|
92
|
+
const parsed = parseAssimilateArgs([...args]);
|
|
93
|
+
if (!parsed.ok) {
|
|
94
|
+
process.stderr.write(
|
|
95
|
+
chalk.red(`${consolePrefix()}◼ borg assimilate: ${parsed.error}\n`)
|
|
96
|
+
);
|
|
97
|
+
process.stderr.write(`Run \`borg --help\` for usage.\n`);
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
return runAssimilate({ role: parsed.role, flags: parsed.flags }, buildDeps());
|
|
101
|
+
}
|
|
102
|
+
|
|
84
103
|
async function main() {
|
|
85
104
|
const serverExitCode = await runEarlyServerFacade(process.argv);
|
|
86
105
|
if (serverExitCode !== null) process.exit(serverExitCode);
|
|
@@ -128,16 +147,7 @@ async function main() {
|
|
|
128
147
|
process.stdout.write(assimilateHelpText(getPackageVersion()));
|
|
129
148
|
process.exit(0);
|
|
130
149
|
}
|
|
131
|
-
const
|
|
132
|
-
if (!parsed.ok) {
|
|
133
|
-
process.stderr.write(
|
|
134
|
-
chalk.red(`${consolePrefix()}◼ borg assimilate: ${parsed.error}\n`)
|
|
135
|
-
);
|
|
136
|
-
process.stderr.write(`Run \`borg --help\` for usage.\n`);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
}
|
|
139
|
-
const deps = buildDefaultAssimilateDeps();
|
|
140
|
-
const code = await runAssimilate({ role: parsed.role, flags: parsed.flags }, deps);
|
|
150
|
+
const code = await runAssimilateEntry(process.argv.slice(3));
|
|
141
151
|
process.exit(code);
|
|
142
152
|
}
|
|
143
153
|
if (process.argv[2] === 'reset-local-seat') {
|
|
@@ -560,7 +570,17 @@ function ensureDetectedCliConfigured(): void {
|
|
|
560
570
|
}
|
|
561
571
|
}
|
|
562
572
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
573
|
+
function isEntryInvocation(): boolean {
|
|
574
|
+
try {
|
|
575
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
576
|
+
} catch {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (isEntryInvocation()) {
|
|
582
|
+
main().catch((error) => {
|
|
583
|
+
console.error(`${consolePrefix()}${chalk.red(`\n◼ Error: ${error.message}\n`)}`);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
});
|
|
586
|
+
}
|
package/src/cli-help.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function topLevelHelpText(version: string): string {
|
|
|
30
30
|
` borg assimilate [role] Join or create a cube\n` +
|
|
31
31
|
` borg assimilate --host <host> Join or create on an explicit server\n` +
|
|
32
32
|
` borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)\n` +
|
|
33
|
+
` borg server cube init Initialize this repository's cube without creating a drone\n` +
|
|
33
34
|
` borg reset-local-seat Clear ONLY this worktree's saved local seat (offline; after a rejection)\n` +
|
|
34
35
|
` borg sync [--prune] Sync this worktree's branch to origin/main\n` +
|
|
35
36
|
` borg cleanup [--prune] Report (or --prune) worktrees orphaned by evicted drones\n` +
|
|
@@ -53,11 +54,28 @@ export function serverHelpText(): string {
|
|
|
53
54
|
` stop Stop the managed local server.\n` +
|
|
54
55
|
` status Report verified runtime evidence.\n` +
|
|
55
56
|
` update Verify and activate a local server artifact.\n` +
|
|
56
|
-
` invite Create a single-use invitation in an interactive terminal.\n
|
|
57
|
+
` invite Create a single-use invitation in an interactive terminal.\n` +
|
|
58
|
+
` cube init Initialize this Git repository's cube; does not create a drone.\n\n` +
|
|
57
59
|
`Run borg server <command> --help for server command options.\n`
|
|
58
60
|
);
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/** Client-owned help for repository cube initialization without a drone. */
|
|
64
|
+
export function cubeInitHelpText(): string {
|
|
65
|
+
return (
|
|
66
|
+
`borg server cube init — initialize this Git repository's cube without creating a drone\n\n` +
|
|
67
|
+
`Usage:\n` +
|
|
68
|
+
` borg server cube init [options]\n\n` +
|
|
69
|
+
`Options:\n` +
|
|
70
|
+
` --host <host> Borg server host or URL (bare hosts default to HTTPS)\n` +
|
|
71
|
+
` --enroll Prompt for a hidden enrollment invitation\n` +
|
|
72
|
+
` --cube-name <name> Repository cube name (otherwise edit the proposed name)\n` +
|
|
73
|
+
` --template software-dev|starter New-cube template (default: software-dev)\n` +
|
|
74
|
+
` --yes, -y Accept new-cube defaults; never adopt by name\n` +
|
|
75
|
+
` --help, -h Show this help\n`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
/**
|
|
62
80
|
* Help text for `borg assimilate --help` — the home for the full assimilate flag
|
|
63
81
|
* set. Model/provider configuration belongs to the selected agent CLI.
|
|
@@ -76,16 +94,19 @@ export function assimilateHelpText(version: string): string {
|
|
|
76
94
|
`Flags:\n` +
|
|
77
95
|
` --worktree <name> Create + launch the drone in a sibling git worktree\n` +
|
|
78
96
|
` --here Stay in the current worktree (no sibling spawn)\n` +
|
|
79
|
-
` --cube-name <name>
|
|
97
|
+
` --cube-name <name> Repository cube name (otherwise edit the proposed name)\n` +
|
|
80
98
|
` --host <host> Borg server host or URL (bare hosts default to HTTPS)\n` +
|
|
81
99
|
` --enroll Prompt for a hidden enrollment invitation in the operator terminal\n` +
|
|
82
|
-
` --template
|
|
83
|
-
` --no-template
|
|
100
|
+
` --template software-dev|starter New-cube template (default: software-dev)\n` +
|
|
101
|
+
` --no-template Unsupported for repository cube creation\n` +
|
|
84
102
|
` --cli claude|codex|opencode Agent CLI to launch (default: claude)\n` +
|
|
85
103
|
` --model claude:<model> Legacy Claude model override (configure models in the agent CLI)\n` +
|
|
86
|
-
` --yes, -y
|
|
87
|
-
`
|
|
88
|
-
`
|
|
104
|
+
` --yes, -y Accept new-cube defaults; never adopt by name\n\n` +
|
|
105
|
+
`Creation shows repository context, name, template, and one confirmation. An existing\n` +
|
|
106
|
+
`repository association skips all prompts. One accessible exact-name legacy cube requires\n` +
|
|
107
|
+
`explicit interactive adoption; ambiguous matches fail closed. An enrolled owner client may\n` +
|
|
108
|
+
`create an idempotent repository cube; ordinary clients require an explicit cube grant.\n` +
|
|
109
|
+
`Agent seats begin only after enrollment. Preview only.\n` +
|
|
89
110
|
`See docs/LOCAL_SERVER.md for self-hosted setup and current status.\n\n` +
|
|
90
111
|
`For local or provider-specific models, configure the selected agent CLI directly.\n` +
|
|
91
112
|
`OpenCode supports Ollama and other providers through its own model configuration.\n`
|
package/src/config.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* read-compare-write shared by client enrollment and same-machine server setup.
|
|
8
8
|
*/
|
|
9
9
|
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
10
|
-
import type { ServerCapability } from 'borgmcp-shared/protocol';
|
|
10
|
+
import type { CreateCubeRepository, CubeTemplate, ServerCapability } from 'borgmcp-shared/protocol';
|
|
11
11
|
import { withStoreLock } from './seat-store.js';
|
|
12
12
|
import {
|
|
13
13
|
makeFileBackend,
|
|
@@ -17,7 +17,7 @@ import { BORG_USER_ROOT, SERVER_CREDENTIALS_FILE } from './credential-paths.js';
|
|
|
17
17
|
|
|
18
18
|
const SERVER_CREDENTIAL_RECORD_VERSION = 2 as const;
|
|
19
19
|
const SERVER_PENDING_ENROLLMENT_RECORD_VERSION = 1 as const;
|
|
20
|
-
const SERVER_CUBE_RETRY_RECORD_VERSION =
|
|
20
|
+
const SERVER_CUBE_RETRY_RECORD_VERSION = 2 as const;
|
|
21
21
|
// The 0600 credential store (Queen rescope: replaces the OS keychain). A single
|
|
22
22
|
// file holds every parent credential/enrollment record; a single flock
|
|
23
23
|
// serializes every mutator + observer that must (SR-seven #4).
|
|
@@ -57,7 +57,9 @@ export interface PendingServerCubeCreationRecord {
|
|
|
57
57
|
repositoryBinding: string;
|
|
58
58
|
retryKey: string;
|
|
59
59
|
name: string;
|
|
60
|
-
|
|
60
|
+
workingRepoName: string;
|
|
61
|
+
repository: CreateCubeRepository;
|
|
62
|
+
template: CubeTemplate;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/**
|
|
@@ -510,21 +512,29 @@ export async function getOrCreatePendingServerCubeCreation(
|
|
|
510
512
|
origin: string;
|
|
511
513
|
trustIdentity: string;
|
|
512
514
|
clientId: string;
|
|
513
|
-
projectRoot: string;
|
|
514
515
|
name: string;
|
|
515
|
-
|
|
516
|
+
workingRepoName: string;
|
|
517
|
+
repository: CreateCubeRepository;
|
|
518
|
+
template: CubeTemplate;
|
|
516
519
|
},
|
|
517
520
|
): Promise<PendingServerCubeCreationRecord> {
|
|
518
521
|
validateServerCredentialBinding(input.origin, input.trustIdentity);
|
|
519
522
|
validateUuid(input.clientId, 'client identity');
|
|
520
|
-
if (
|
|
523
|
+
if (
|
|
524
|
+
(input.repository.kind !== 'origin' && input.repository.kind !== 'local') ||
|
|
525
|
+
typeof input.repository.value !== 'string' || input.repository.value.length < 1
|
|
526
|
+
) {
|
|
521
527
|
throw new Error('invalid Borg server repository binding');
|
|
522
528
|
}
|
|
523
529
|
if (Buffer.byteLength(input.name, 'utf8') < 1 || Buffer.byteLength(input.name, 'utf8') > 120 ||
|
|
524
530
|
!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(input.name)) {
|
|
525
531
|
throw new Error('invalid Borg server cube name');
|
|
526
532
|
}
|
|
527
|
-
const repositoryBinding = createHash('sha256')
|
|
533
|
+
const repositoryBinding = createHash('sha256')
|
|
534
|
+
.update(input.repository.kind)
|
|
535
|
+
.update('\0')
|
|
536
|
+
.update(input.repository.value)
|
|
537
|
+
.digest('hex');
|
|
528
538
|
const backend = await getServerCredentialBackend();
|
|
529
539
|
const account = serverCubeRetryAccount(
|
|
530
540
|
input.origin,
|
|
@@ -548,6 +558,7 @@ export async function getOrCreatePendingServerCubeCreation(
|
|
|
548
558
|
record.clientId !== input.clientId ||
|
|
549
559
|
record.repositoryBinding !== repositoryBinding ||
|
|
550
560
|
record.name !== input.name ||
|
|
561
|
+
record.workingRepoName !== input.workingRepoName ||
|
|
551
562
|
record.template !== input.template ||
|
|
552
563
|
typeof record.retryKey !== 'string' || !UUID_RE.test(record.retryKey)
|
|
553
564
|
) {
|
|
@@ -560,6 +571,8 @@ export async function getOrCreatePendingServerCubeCreation(
|
|
|
560
571
|
repositoryBinding,
|
|
561
572
|
retryKey: record.retryKey,
|
|
562
573
|
name: input.name,
|
|
574
|
+
workingRepoName: input.workingRepoName,
|
|
575
|
+
repository: input.repository,
|
|
563
576
|
template: input.template,
|
|
564
577
|
};
|
|
565
578
|
} catch {
|
|
@@ -573,12 +586,21 @@ export async function getOrCreatePendingServerCubeCreation(
|
|
|
573
586
|
repositoryBinding,
|
|
574
587
|
retryKey: randomUUID(),
|
|
575
588
|
name: input.name,
|
|
589
|
+
workingRepoName: input.workingRepoName,
|
|
590
|
+
repository: input.repository,
|
|
576
591
|
template: input.template,
|
|
577
592
|
};
|
|
578
593
|
await backend.set(account, JSON.stringify({
|
|
579
594
|
version: SERVER_CUBE_RETRY_RECORD_VERSION,
|
|
580
595
|
state: 'pending',
|
|
581
|
-
|
|
596
|
+
origin: record.origin,
|
|
597
|
+
trustIdentity: record.trustIdentity,
|
|
598
|
+
clientId: record.clientId,
|
|
599
|
+
repositoryBinding: record.repositoryBinding,
|
|
600
|
+
retryKey: record.retryKey,
|
|
601
|
+
name: record.name,
|
|
602
|
+
workingRepoName: record.workingRepoName,
|
|
603
|
+
template: record.template,
|
|
582
604
|
}));
|
|
583
605
|
return record;
|
|
584
606
|
});
|
package/src/remote-client.ts
CHANGED
|
@@ -712,7 +712,7 @@ async function authedFetch(
|
|
|
712
712
|
parsed.error !== null && typeof parsed.error === 'object' &&
|
|
713
713
|
parsed.error.code === ROLE_SECTION_CONFLICT_CODE
|
|
714
714
|
) {
|
|
715
|
-
//
|
|
715
|
+
// The shared protocol intentionally omits this server-local code. Re-validate the whole
|
|
716
716
|
// envelope through the strict shared decoder with only the recognized
|
|
717
717
|
// code substituted; no server-provided diagnostic is ever surfaced.
|
|
718
718
|
decodeProtocolErrorEnvelope({
|