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/server-facade.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { spawn as spawnChild, type SpawnOptions } from 'node:child_process';
|
|
2
2
|
import { constants } from 'node:os';
|
|
3
|
-
import { serverHelpText } from './cli-help.js';
|
|
3
|
+
import { cubeInitHelpText, isHelpFlag, serverHelpText } from './cli-help.js';
|
|
4
4
|
|
|
5
5
|
export const SERVER_LIFECYCLE_COMMANDS = ['setup', 'start', 'stop', 'status', 'update', 'invite'] as const;
|
|
6
6
|
export type ServerLifecycleCommand = typeof SERVER_LIFECYCLE_COMMANDS[number];
|
|
7
7
|
|
|
8
8
|
export type ParsedServerFacadeArgs =
|
|
9
9
|
| { kind: 'help' }
|
|
10
|
+
| { kind: 'cube-init-help' }
|
|
11
|
+
| { kind: 'cube-init'; args: string[] }
|
|
10
12
|
| { kind: 'command'; command: ServerLifecycleCommand; args: string[] }
|
|
11
13
|
| { kind: 'error'; reason: 'unknown-command'; command: string };
|
|
12
14
|
|
|
@@ -15,6 +17,12 @@ export function parseServerFacadeArgs(args: readonly string[]): ParsedServerFaca
|
|
|
15
17
|
if (command === undefined || command === '--help' || command === '-h') {
|
|
16
18
|
return { kind: 'help' };
|
|
17
19
|
}
|
|
20
|
+
if (command === 'cube' && rest[0] === 'init') {
|
|
21
|
+
const args = rest.slice(1);
|
|
22
|
+
return args.some(isHelpFlag)
|
|
23
|
+
? { kind: 'cube-init-help' }
|
|
24
|
+
: { kind: 'cube-init', args };
|
|
25
|
+
}
|
|
18
26
|
if (!(SERVER_LIFECYCLE_COMMANDS as readonly string[]).includes(command)) {
|
|
19
27
|
return { kind: 'error', reason: 'unknown-command', command };
|
|
20
28
|
}
|
|
@@ -49,6 +57,12 @@ export interface ServerFacadeOutputDeps {
|
|
|
49
57
|
writeStderr(text: string): void;
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
export interface ServerFacadeClientDeps {
|
|
61
|
+
cubeInit(args: readonly string[]): Promise<number>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type AssimilateDepsBuilder = typeof import('./assimilate-deps.js').buildDefaultAssimilateDeps;
|
|
65
|
+
|
|
52
66
|
export type ServerFacadeProcessResult =
|
|
53
67
|
| { kind: 'exited'; code: number }
|
|
54
68
|
| { kind: 'signaled'; signal: NodeJS.Signals }
|
|
@@ -65,6 +79,31 @@ const defaultOutputDeps: ServerFacadeOutputDeps = {
|
|
|
65
79
|
writeStderr: (text) => process.stderr.write(text),
|
|
66
80
|
};
|
|
67
81
|
|
|
82
|
+
export function buildDefaultServerFacadeClientDeps(
|
|
83
|
+
buildDeps?: AssimilateDepsBuilder,
|
|
84
|
+
): ServerFacadeClientDeps {
|
|
85
|
+
return {
|
|
86
|
+
cubeInit: async (args) => {
|
|
87
|
+
const [{ parseAssimilateArgs }, { buildDefaultAssimilateDeps }, { runAssimilate }] = await Promise.all([
|
|
88
|
+
import('./parse-assimilate-args.js'),
|
|
89
|
+
import('./assimilate-deps.js'),
|
|
90
|
+
import('./assimilate-cmd.js'),
|
|
91
|
+
]);
|
|
92
|
+
const parsed = parseAssimilateArgs([...args]);
|
|
93
|
+
if (!parsed.ok || parsed.role !== undefined) {
|
|
94
|
+
process.stderr.write(`${parsed.ok ? 'borg server cube init does not accept a role' : parsed.error}\n`);
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
return runAssimilate(
|
|
98
|
+
{ role: undefined, flags: parsed.flags, mode: 'cube-init' },
|
|
99
|
+
(buildDeps ?? buildDefaultAssimilateDeps)(),
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const defaultClientDeps = buildDefaultServerFacadeClientDeps();
|
|
106
|
+
|
|
68
107
|
const MAX_RENDERED_COMMAND_CODE_POINTS = 80;
|
|
69
108
|
|
|
70
109
|
function inertCommand(command: string): string {
|
|
@@ -86,7 +125,7 @@ function inertCommand(command: string): string {
|
|
|
86
125
|
export function unknownServerCommandText(command: string): string {
|
|
87
126
|
return (
|
|
88
127
|
`Unknown server command: ${inertCommand(command)}.\n` +
|
|
89
|
-
`Available commands: setup, start, stop, status, update, invite.\n` +
|
|
128
|
+
`Available commands: setup, start, stop, status, update, invite, cube init.\n` +
|
|
90
129
|
`Next: run borg server --help.\n`
|
|
91
130
|
);
|
|
92
131
|
}
|
|
@@ -168,6 +207,7 @@ export async function runEarlyServerFacade(
|
|
|
168
207
|
argv: readonly string[],
|
|
169
208
|
deps: ServerFacadeProcessDeps = defaultProcessDeps,
|
|
170
209
|
output: ServerFacadeOutputDeps = defaultOutputDeps,
|
|
210
|
+
client: ServerFacadeClientDeps = defaultClientDeps,
|
|
171
211
|
): Promise<number | null> {
|
|
172
212
|
if (argv[2] !== 'server') return null;
|
|
173
213
|
const parsed = parseServerFacadeArgs(argv.slice(3));
|
|
@@ -175,10 +215,15 @@ export async function runEarlyServerFacade(
|
|
|
175
215
|
output.writeStdout(serverHelpText());
|
|
176
216
|
return 0;
|
|
177
217
|
}
|
|
218
|
+
if (parsed.kind === 'cube-init-help') {
|
|
219
|
+
output.writeStdout(cubeInitHelpText());
|
|
220
|
+
return 0;
|
|
221
|
+
}
|
|
178
222
|
if (parsed.kind === 'error') {
|
|
179
223
|
output.writeStderr(unknownServerCommandText(parsed.command));
|
|
180
224
|
return 1;
|
|
181
225
|
}
|
|
226
|
+
if (parsed.kind === 'cube-init') return client.cubeInit(parsed.args);
|
|
182
227
|
|
|
183
228
|
const result = await runServerFacadeProcess(parsed, deps);
|
|
184
229
|
if (result.kind === 'spawn-error') {
|
package/src/server-handshake.ts
CHANGED
|
@@ -4,8 +4,12 @@ import {
|
|
|
4
4
|
ENROLLMENT_EXCHANGE_PATH,
|
|
5
5
|
HEALTH_PATH,
|
|
6
6
|
PROTOCOL_INFO_PATH,
|
|
7
|
+
REPOSITORY_CUBE_ASSOCIATION_PATH,
|
|
8
|
+
REPOSITORY_CUBE_RESOLVE_PATH,
|
|
7
9
|
createAttachRequestEnvelope,
|
|
8
10
|
createProtocolEnvelope,
|
|
11
|
+
decodeAssociateRepositoryCubeRequest,
|
|
12
|
+
decodeAssociateRepositoryCubeResponseEnvelope,
|
|
9
13
|
decodeAttachResponseEnvelope,
|
|
10
14
|
decodeCreateCubeRequest,
|
|
11
15
|
decodeCreateCubeResponseEnvelope,
|
|
@@ -13,10 +17,16 @@ import {
|
|
|
13
17
|
decodeEnrollmentExchangeResponseEnvelope,
|
|
14
18
|
decodeProtocolErrorEnvelope,
|
|
15
19
|
decodeProtocolTagPreflight,
|
|
20
|
+
decodeResolveRepositoryCubeRequest,
|
|
21
|
+
decodeResolveRepositoryCubeResponseEnvelope,
|
|
16
22
|
ErrorCode,
|
|
23
|
+
type AssociateRepositoryCubeResponse,
|
|
24
|
+
type CreateCubeRepository,
|
|
17
25
|
type CreateCubeResponse,
|
|
26
|
+
type CubeTemplate,
|
|
18
27
|
type DroneRuntimeMetadata,
|
|
19
28
|
type ProtocolTagPreflight,
|
|
29
|
+
type ResolveRepositoryCubeResponse,
|
|
20
30
|
type ServerCapability,
|
|
21
31
|
} from 'borgmcp-shared/protocol';
|
|
22
32
|
import { createHash, randomUUID } from 'node:crypto';
|
|
@@ -45,6 +55,11 @@ import {
|
|
|
45
55
|
BorgServerError,
|
|
46
56
|
BorgServerTrustError,
|
|
47
57
|
BorgServerUnreachableError,
|
|
58
|
+
CubeCreationConfirmationError,
|
|
59
|
+
CubeCreationOutcomeUnknownError,
|
|
60
|
+
RepositoryAssociationOperationError,
|
|
61
|
+
RepositoryAssociationOutcomeUnknownError,
|
|
62
|
+
RepositoryAssociationResolutionError,
|
|
48
63
|
} from './server-errors.js';
|
|
49
64
|
import { DroneEvictedError, DRONE_EVICTED_CODE } from './drone-lifecycle.js';
|
|
50
65
|
import { readBoundedResponseBody } from './server-response.js';
|
|
@@ -181,7 +196,7 @@ export interface ServerAttachResult {
|
|
|
181
196
|
}
|
|
182
197
|
|
|
183
198
|
/**
|
|
184
|
-
* Attach an enrolled client principal to one granted cube/role over protocol
|
|
199
|
+
* Attach an enrolled client principal to one granted cube/role over protocol v5.
|
|
185
200
|
* The client CSPRNG-generates the session bearer and persists it PENDING in the
|
|
186
201
|
* OS keychain (keyed by the stable per-seat identity) BEFORE this request, so an
|
|
187
202
|
* interrupted/lost response is recovered by re-sending the exact same bearer —
|
|
@@ -554,6 +569,210 @@ export async function resumeBorgServerEnrollment(
|
|
|
554
569
|
});
|
|
555
570
|
}
|
|
556
571
|
|
|
572
|
+
async function requireMatchingServerCredential(
|
|
573
|
+
origin: string,
|
|
574
|
+
trustIdentity: string,
|
|
575
|
+
parentCredential: string,
|
|
576
|
+
loadCredentialRecord: typeof getServerCredentialRecord,
|
|
577
|
+
): Promise<void> {
|
|
578
|
+
const active = await loadCredentialRecord(origin, trustIdentity);
|
|
579
|
+
if (!active || active.credential !== parentCredential || !active.clientId) {
|
|
580
|
+
throw new BorgServerError('CREDENTIAL_REJECTED', 'stored Borg server credential was rejected');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function protocolErrorCode(response: Response): Promise<ErrorCode | undefined> {
|
|
585
|
+
try {
|
|
586
|
+
return decodeProtocolErrorEnvelope(
|
|
587
|
+
JSON.parse(await readHandshakeBodyWithTimeout(response)),
|
|
588
|
+
).error.code;
|
|
589
|
+
} catch {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/** Resolve one client-scoped repository association without mutation. */
|
|
595
|
+
export async function resolveBorgServerRepositoryCube(
|
|
596
|
+
origin: string,
|
|
597
|
+
trustIdentity: string,
|
|
598
|
+
parentCredential: string,
|
|
599
|
+
input: { workingRepoName: string; repository: CreateCubeRepository },
|
|
600
|
+
deps: {
|
|
601
|
+
fetchImpl?: FetchLike;
|
|
602
|
+
loadCredentialRecord?: typeof getServerCredentialRecord;
|
|
603
|
+
} = {},
|
|
604
|
+
): Promise<ResolveRepositoryCubeResponse> {
|
|
605
|
+
await requireMatchingServerCredential(
|
|
606
|
+
origin,
|
|
607
|
+
trustIdentity,
|
|
608
|
+
parentCredential,
|
|
609
|
+
deps.loadCredentialRecord ?? getServerCredentialRecord,
|
|
610
|
+
);
|
|
611
|
+
const request = decodeResolveRepositoryCubeRequest({
|
|
612
|
+
working_repo_name: input.workingRepoName,
|
|
613
|
+
repository: input.repository,
|
|
614
|
+
});
|
|
615
|
+
const controller = new AbortController();
|
|
616
|
+
const timeout = setTimeout(() => controller.abort(), HANDSHAKE_TIMEOUT_MS);
|
|
617
|
+
let response: Response;
|
|
618
|
+
try {
|
|
619
|
+
response = await (deps.fetchImpl ?? fetch)(handshakeUrl(origin, REPOSITORY_CUBE_RESOLVE_PATH), {
|
|
620
|
+
method: 'POST',
|
|
621
|
+
redirect: 'error',
|
|
622
|
+
signal: controller.signal,
|
|
623
|
+
headers: {
|
|
624
|
+
Accept: 'application/json',
|
|
625
|
+
'Content-Type': 'application/json',
|
|
626
|
+
Authorization: `Bearer ${parentCredential}`,
|
|
627
|
+
},
|
|
628
|
+
body: JSON.stringify(createProtocolEnvelope(randomUUID(), request)),
|
|
629
|
+
});
|
|
630
|
+
} catch (error) {
|
|
631
|
+
throw new RepositoryAssociationResolutionError();
|
|
632
|
+
} finally {
|
|
633
|
+
clearTimeout(timeout);
|
|
634
|
+
}
|
|
635
|
+
if (response.status === 401 || response.status === 403) {
|
|
636
|
+
throw new BorgServerError('CREDENTIAL_REJECTED', 'Borg server enrollment was rejected');
|
|
637
|
+
}
|
|
638
|
+
if (response.status !== 200) {
|
|
639
|
+
throw new RepositoryAssociationResolutionError();
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const decoded = decodeResolveRepositoryCubeResponseEnvelope(
|
|
643
|
+
JSON.parse(await readHandshakeBodyWithTimeout(response)),
|
|
644
|
+
).payload;
|
|
645
|
+
if (
|
|
646
|
+
decoded.result === 'resolved' &&
|
|
647
|
+
(decoded.repository.kind !== request.repository.kind ||
|
|
648
|
+
decoded.repository.value !== request.repository.value ||
|
|
649
|
+
decoded.working_repo_name !== request.working_repo_name)
|
|
650
|
+
) {
|
|
651
|
+
throw new Error('association mismatch');
|
|
652
|
+
}
|
|
653
|
+
return decoded;
|
|
654
|
+
} catch {
|
|
655
|
+
throw new RepositoryAssociationResolutionError();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export async function resolveLocalBorgServerRepositoryCube(
|
|
660
|
+
origin: string,
|
|
661
|
+
trustIdentity: string,
|
|
662
|
+
parentCredential: string,
|
|
663
|
+
input: { workingRepoName: string; repository: CreateCubeRepository },
|
|
664
|
+
deps: { loadTrust?: typeof loadBorgServerTrust } = {},
|
|
665
|
+
): Promise<ResolveRepositoryCubeResponse> {
|
|
666
|
+
const trust = await (deps.loadTrust ?? loadBorgServerTrust)(origin);
|
|
667
|
+
if (trust.identity !== trustIdentity) {
|
|
668
|
+
throw new BorgServerTrustError('Borg server trust identity changed; refusing repository resolution');
|
|
669
|
+
}
|
|
670
|
+
return resolveBorgServerRepositoryCube(origin, trustIdentity, parentCredential, input, {
|
|
671
|
+
fetchImpl: trust.fetchImpl,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Atomically associate an explicit accessible cube after local confirmation. */
|
|
676
|
+
export async function associateBorgServerRepositoryCube(
|
|
677
|
+
origin: string,
|
|
678
|
+
trustIdentity: string,
|
|
679
|
+
parentCredential: string,
|
|
680
|
+
input: { cubeId: string; workingRepoName: string; repository: CreateCubeRepository },
|
|
681
|
+
deps: {
|
|
682
|
+
fetchImpl?: FetchLike;
|
|
683
|
+
loadCredentialRecord?: typeof getServerCredentialRecord;
|
|
684
|
+
} = {},
|
|
685
|
+
): Promise<AssociateRepositoryCubeResponse> {
|
|
686
|
+
await requireMatchingServerCredential(
|
|
687
|
+
origin,
|
|
688
|
+
trustIdentity,
|
|
689
|
+
parentCredential,
|
|
690
|
+
deps.loadCredentialRecord ?? getServerCredentialRecord,
|
|
691
|
+
);
|
|
692
|
+
const request = decodeAssociateRepositoryCubeRequest({
|
|
693
|
+
cube_id: input.cubeId,
|
|
694
|
+
working_repo_name: input.workingRepoName,
|
|
695
|
+
repository: input.repository,
|
|
696
|
+
});
|
|
697
|
+
const controller = new AbortController();
|
|
698
|
+
const timeout = setTimeout(() => controller.abort(), HANDSHAKE_TIMEOUT_MS);
|
|
699
|
+
let response: Response;
|
|
700
|
+
try {
|
|
701
|
+
response = await (deps.fetchImpl ?? fetch)(handshakeUrl(origin, REPOSITORY_CUBE_ASSOCIATION_PATH), {
|
|
702
|
+
method: 'PUT',
|
|
703
|
+
redirect: 'error',
|
|
704
|
+
signal: controller.signal,
|
|
705
|
+
headers: {
|
|
706
|
+
Accept: 'application/json',
|
|
707
|
+
'Content-Type': 'application/json',
|
|
708
|
+
Authorization: `Bearer ${parentCredential}`,
|
|
709
|
+
},
|
|
710
|
+
body: JSON.stringify(createProtocolEnvelope(randomUUID(), request)),
|
|
711
|
+
});
|
|
712
|
+
} catch {
|
|
713
|
+
throw new RepositoryAssociationOutcomeUnknownError();
|
|
714
|
+
} finally {
|
|
715
|
+
clearTimeout(timeout);
|
|
716
|
+
}
|
|
717
|
+
if (response.status === 401) {
|
|
718
|
+
throw new BorgServerError('CREDENTIAL_REJECTED', 'Borg server enrollment was rejected');
|
|
719
|
+
}
|
|
720
|
+
if (response.status === 403) {
|
|
721
|
+
if (await protocolErrorCode(response) !== ErrorCode.ACCESS_DENIED) {
|
|
722
|
+
throw new RepositoryAssociationOutcomeUnknownError();
|
|
723
|
+
}
|
|
724
|
+
throw new RepositoryAssociationOperationError('access-denied');
|
|
725
|
+
}
|
|
726
|
+
if (response.status === 409) {
|
|
727
|
+
const code = await protocolErrorCode(response);
|
|
728
|
+
if (code === ErrorCode.REPOSITORY_ALREADY_ASSOCIATED) {
|
|
729
|
+
throw new RepositoryAssociationOperationError('repository-already-associated');
|
|
730
|
+
}
|
|
731
|
+
if (code === ErrorCode.CUBE_ALREADY_ASSOCIATED) {
|
|
732
|
+
throw new RepositoryAssociationOperationError('cube-already-associated');
|
|
733
|
+
}
|
|
734
|
+
if (code === ErrorCode.INVALID_INPUT) {
|
|
735
|
+
throw new RepositoryAssociationOperationError('invalid-cube');
|
|
736
|
+
}
|
|
737
|
+
throw new RepositoryAssociationOutcomeUnknownError();
|
|
738
|
+
}
|
|
739
|
+
if (response.status !== 200) {
|
|
740
|
+
throw new RepositoryAssociationOutcomeUnknownError();
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
const decoded = decodeAssociateRepositoryCubeResponseEnvelope(
|
|
744
|
+
JSON.parse(await readHandshakeBodyWithTimeout(response)),
|
|
745
|
+
).payload;
|
|
746
|
+
if (
|
|
747
|
+
decoded.cube_id !== request.cube_id ||
|
|
748
|
+
decoded.repository.kind !== request.repository.kind ||
|
|
749
|
+
decoded.repository.value !== request.repository.value ||
|
|
750
|
+
decoded.working_repo_name !== request.working_repo_name
|
|
751
|
+
) {
|
|
752
|
+
throw new Error('association mismatch');
|
|
753
|
+
}
|
|
754
|
+
return decoded;
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new RepositoryAssociationOutcomeUnknownError();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export async function associateLocalBorgServerRepositoryCube(
|
|
761
|
+
origin: string,
|
|
762
|
+
trustIdentity: string,
|
|
763
|
+
parentCredential: string,
|
|
764
|
+
input: { cubeId: string; workingRepoName: string; repository: CreateCubeRepository },
|
|
765
|
+
deps: { loadTrust?: typeof loadBorgServerTrust } = {},
|
|
766
|
+
): Promise<AssociateRepositoryCubeResponse> {
|
|
767
|
+
const trust = await (deps.loadTrust ?? loadBorgServerTrust)(origin);
|
|
768
|
+
if (trust.identity !== trustIdentity) {
|
|
769
|
+
throw new BorgServerTrustError('Borg server trust identity changed; refusing repository association');
|
|
770
|
+
}
|
|
771
|
+
return associateBorgServerRepositoryCube(origin, trustIdentity, parentCredential, input, {
|
|
772
|
+
fetchImpl: trust.fetchImpl,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
557
776
|
/**
|
|
558
777
|
* Create one repository cube through the narrow owner capability. The retry
|
|
559
778
|
* key is persisted in the OS keychain before network I/O and reused exactly
|
|
@@ -564,7 +783,12 @@ export async function createBorgServerCube(
|
|
|
564
783
|
origin: string,
|
|
565
784
|
trustIdentity: string,
|
|
566
785
|
parentCredential: string,
|
|
567
|
-
input: {
|
|
786
|
+
input: {
|
|
787
|
+
name: string;
|
|
788
|
+
workingRepoName: string;
|
|
789
|
+
repository: CreateCubeRepository;
|
|
790
|
+
template: CubeTemplate;
|
|
791
|
+
},
|
|
568
792
|
deps: {
|
|
569
793
|
fetchImpl?: FetchLike;
|
|
570
794
|
loadCredentialRecord?: typeof getServerCredentialRecord;
|
|
@@ -589,13 +813,16 @@ export async function createBorgServerCube(
|
|
|
589
813
|
origin,
|
|
590
814
|
trustIdentity,
|
|
591
815
|
clientId: active.clientId,
|
|
592
|
-
projectRoot: input.projectRoot,
|
|
593
816
|
name: input.name,
|
|
594
|
-
|
|
817
|
+
workingRepoName: input.workingRepoName,
|
|
818
|
+
repository: input.repository,
|
|
819
|
+
template: input.template,
|
|
595
820
|
});
|
|
596
821
|
const request = decodeCreateCubeRequest({
|
|
597
822
|
retry_key: pending.retryKey,
|
|
598
823
|
name: pending.name,
|
|
824
|
+
working_repo_name: pending.workingRepoName,
|
|
825
|
+
repository: pending.repository,
|
|
599
826
|
template: pending.template,
|
|
600
827
|
});
|
|
601
828
|
|
|
@@ -623,7 +850,10 @@ export async function createBorgServerCube(
|
|
|
623
850
|
clearTimeout(timeout);
|
|
624
851
|
}
|
|
625
852
|
}
|
|
626
|
-
if (!response)
|
|
853
|
+
if (!response) {
|
|
854
|
+
void lastTransportError;
|
|
855
|
+
throw new CubeCreationOutcomeUnknownError();
|
|
856
|
+
}
|
|
627
857
|
if (response.status === 401 || response.status === 403) {
|
|
628
858
|
throw new BorgServerError('CREDENTIAL_REJECTED', 'Borg server enrollment was rejected');
|
|
629
859
|
}
|
|
@@ -634,23 +864,32 @@ export async function createBorgServerCube(
|
|
|
634
864
|
);
|
|
635
865
|
}
|
|
636
866
|
if (response.status === 409) {
|
|
637
|
-
throw new
|
|
867
|
+
throw new CubeCreationConfirmationError('The Borg server rejected the repository cube operation identity.');
|
|
638
868
|
}
|
|
639
869
|
if (response.status !== 201) {
|
|
640
|
-
throw new
|
|
870
|
+
throw new CubeCreationOutcomeUnknownError();
|
|
641
871
|
}
|
|
642
872
|
let decoded: ReturnType<typeof decodeCreateCubeResponseEnvelope>;
|
|
643
873
|
try {
|
|
644
874
|
decoded = decodeCreateCubeResponseEnvelope(
|
|
645
875
|
JSON.parse(await readHandshakeBodyWithTimeout(response)),
|
|
646
876
|
);
|
|
647
|
-
} catch
|
|
648
|
-
|
|
649
|
-
|
|
877
|
+
} catch {
|
|
878
|
+
throw new CubeCreationOutcomeUnknownError();
|
|
879
|
+
}
|
|
880
|
+
if (
|
|
881
|
+
decoded.payload.repository.kind !== pending.repository.kind ||
|
|
882
|
+
decoded.payload.repository.value !== pending.repository.value
|
|
883
|
+
) {
|
|
884
|
+
throw new CubeCreationConfirmationError();
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
await (deps.clearCubeCreation ?? clearPendingServerCubeCreation)(
|
|
888
|
+
pending as PendingServerCubeCreationRecord,
|
|
889
|
+
);
|
|
890
|
+
} catch {
|
|
891
|
+
throw new CubeCreationConfirmationError('The server confirmed the cube, but local retry state could not be finalized.');
|
|
650
892
|
}
|
|
651
|
-
await (deps.clearCubeCreation ?? clearPendingServerCubeCreation)(
|
|
652
|
-
pending as PendingServerCubeCreationRecord,
|
|
653
|
-
);
|
|
654
893
|
return decoded.payload;
|
|
655
894
|
}
|
|
656
895
|
|
|
@@ -658,7 +897,12 @@ export async function createLocalBorgServerCube(
|
|
|
658
897
|
origin: string,
|
|
659
898
|
trustIdentity: string,
|
|
660
899
|
parentCredential: string,
|
|
661
|
-
input: {
|
|
900
|
+
input: {
|
|
901
|
+
name: string;
|
|
902
|
+
workingRepoName: string;
|
|
903
|
+
repository: CreateCubeRepository;
|
|
904
|
+
template: CubeTemplate;
|
|
905
|
+
},
|
|
662
906
|
deps: { loadTrust?: typeof loadBorgServerTrust } = {},
|
|
663
907
|
): Promise<CreateCubeResponse> {
|
|
664
908
|
const trust = await (deps.loadTrust ?? loadBorgServerTrust)(origin);
|