borgmcp-shared 0.6.3 → 0.6.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 +17 -7
- package/dist/conformance/adapter.d.ts +18 -0
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +119 -1
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +106 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +106 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +47 -1
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +112 -9
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/errors.d.ts +2 -0
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +2 -0
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/docs/compatibility.md +16 -7
- package/docs/enrollment.md +48 -6
- package/docs/releasing.md +4 -3
- package/package.json +1 -1
- package/src/conformance/adapter.ts +261 -0
- package/src/conformance/index.ts +146 -0
- package/src/protocol/contract.ts +166 -9
- package/src/protocol/errors.ts +2 -0
- package/src/protocol/version.ts +2 -2
package/src/conformance/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { BroadcastHwm } from '../log-stream-hwm.js';
|
|
2
2
|
import type {
|
|
3
3
|
AttachResponse,
|
|
4
|
+
AssociateRepositoryCubeRequest,
|
|
4
5
|
CreateCubeRequest,
|
|
5
6
|
EnrollmentExchangeRequest,
|
|
7
|
+
ResolveRepositoryCubeRequest,
|
|
6
8
|
} from '../protocol/contract.js';
|
|
7
9
|
|
|
8
10
|
export * from './adapter.js';
|
|
@@ -291,6 +293,150 @@ readonly CreateCubeAssociationConformanceVector[] = [
|
|
|
291
293
|
},
|
|
292
294
|
];
|
|
293
295
|
|
|
296
|
+
export interface ResolveRepositoryCubeConformanceVector {
|
|
297
|
+
name: string;
|
|
298
|
+
request: ResolveRepositoryCubeRequest;
|
|
299
|
+
associated: boolean;
|
|
300
|
+
expected:
|
|
301
|
+
| { outcome: 'none'; status: 200; authority_state_delta: Record<string, never> }
|
|
302
|
+
| { outcome: 'resolved'; status: 200; authority_state_delta: Record<string, never> };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const REPOSITORY_CUBE_ONE = '00000000-0000-4000-8000-000000000131';
|
|
306
|
+
const REPOSITORY_ONE: ResolveRepositoryCubeRequest = {
|
|
307
|
+
working_repo_name: 'repository-one',
|
|
308
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-one' },
|
|
309
|
+
};
|
|
310
|
+
const REPOSITORY_TWO: ResolveRepositoryCubeRequest = {
|
|
311
|
+
working_repo_name: 'repository-two',
|
|
312
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-two' },
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/** Resolution is read-only and returns only explicit none or authoritative stored fields. */
|
|
316
|
+
export const RESOLVE_REPOSITORY_CUBE_CONFORMANCE:
|
|
317
|
+
readonly ResolveRepositoryCubeConformanceVector[] = [
|
|
318
|
+
{
|
|
319
|
+
name: 'unassociated repository resolves explicit none without mutation',
|
|
320
|
+
request: REPOSITORY_ONE,
|
|
321
|
+
associated: false,
|
|
322
|
+
expected: { outcome: 'none', status: 200, authority_state_delta: {} },
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'associated repository resolves authoritative stored fields without mutation',
|
|
326
|
+
request: { ...REPOSITORY_ONE, working_repo_name: 'ignored-new-display' },
|
|
327
|
+
associated: true,
|
|
328
|
+
expected: { outcome: 'resolved', status: 200, authority_state_delta: {} },
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
export interface AssociateRepositoryCubeConformanceVector {
|
|
333
|
+
name: string;
|
|
334
|
+
initial: AssociateRepositoryCubeRequest;
|
|
335
|
+
retry: AssociateRepositoryCubeRequest;
|
|
336
|
+
expected:
|
|
337
|
+
| {
|
|
338
|
+
outcome: 'resolved';
|
|
339
|
+
status: 200;
|
|
340
|
+
initial_authority_state_delta: { repository_associations: 1 };
|
|
341
|
+
retry_authority_state_delta: Record<string, never>;
|
|
342
|
+
}
|
|
343
|
+
| {
|
|
344
|
+
outcome: 'repository_conflict' | 'cube_conflict';
|
|
345
|
+
status: 409;
|
|
346
|
+
error: 'REPOSITORY_ALREADY_ASSOCIATED' | 'CUBE_ALREADY_ASSOCIATED';
|
|
347
|
+
diagnostic_disclosure: 'none';
|
|
348
|
+
retry_authority_state_delta: Record<string, never>;
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Explicit cube IDs bind atomically; names never select or identify a cube. */
|
|
353
|
+
export const ASSOCIATE_REPOSITORY_CUBE_CONFORMANCE:
|
|
354
|
+
readonly AssociateRepositoryCubeConformanceVector[] = [
|
|
355
|
+
{
|
|
356
|
+
name: 'same explicit cube and repository binding is idempotent',
|
|
357
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
358
|
+
retry: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
359
|
+
expected: {
|
|
360
|
+
outcome: 'resolved',
|
|
361
|
+
status: 200,
|
|
362
|
+
initial_authority_state_delta: { repository_associations: 1 },
|
|
363
|
+
retry_authority_state_delta: {},
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'repository already bound to another cube conflicts without mutation',
|
|
368
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
369
|
+
retry: { cube_id: '00000000-0000-4000-8000-000000000132', ...REPOSITORY_ONE },
|
|
370
|
+
expected: {
|
|
371
|
+
outcome: 'repository_conflict',
|
|
372
|
+
status: 409,
|
|
373
|
+
error: 'REPOSITORY_ALREADY_ASSOCIATED',
|
|
374
|
+
diagnostic_disclosure: 'none',
|
|
375
|
+
retry_authority_state_delta: {},
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'cube already bound to another repository conflicts without mutation',
|
|
380
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
381
|
+
retry: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_TWO },
|
|
382
|
+
expected: {
|
|
383
|
+
outcome: 'cube_conflict',
|
|
384
|
+
status: 409,
|
|
385
|
+
error: 'CUBE_ALREADY_ASSOCIATED',
|
|
386
|
+
diagnostic_disclosure: 'none',
|
|
387
|
+
retry_authority_state_delta: {},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
export const REPOSITORY_CUBE_PERMISSION_CONFORMANCE = [
|
|
393
|
+
{
|
|
394
|
+
name: 'association denies an inaccessible explicit cube without mutation',
|
|
395
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
396
|
+
expected: { status: 403, error: 'ACCESS_DENIED', authority_state_delta: {} },
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: 'same-client binding to an inaccessible cube is non-enumerating',
|
|
400
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
401
|
+
precondition: 'repository_bound_to_inaccessible_cube',
|
|
402
|
+
expected: {
|
|
403
|
+
resolve: { status: 200, outcome: 'none', authority_state_delta: {} },
|
|
404
|
+
associate: {
|
|
405
|
+
status: 403,
|
|
406
|
+
error: 'ACCESS_DENIED',
|
|
407
|
+
diagnostic_disclosure: 'none',
|
|
408
|
+
authority_state_delta: {},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: 'another client binding is neither resolved nor treated as a conflict',
|
|
414
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
415
|
+
precondition: 'repository_bound_by_another_client',
|
|
416
|
+
expected: {
|
|
417
|
+
resolve: { status: 200, outcome: 'none', authority_state_delta: {} },
|
|
418
|
+
associate: {
|
|
419
|
+
status: 200,
|
|
420
|
+
outcome: 'resolved',
|
|
421
|
+
authority_state_delta: { repository_associations: 1 },
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
] as const;
|
|
426
|
+
|
|
427
|
+
export const REPOSITORY_CUBE_AUTHORITATIVE_STATE_CONFORMANCE = [
|
|
428
|
+
{
|
|
429
|
+
name: 'legacy cube with invalid authoritative roles is rejected without mutation',
|
|
430
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
431
|
+
expected: {
|
|
432
|
+
status: 409,
|
|
433
|
+
error: 'INVALID_INPUT',
|
|
434
|
+
diagnostic_disclosure: 'none',
|
|
435
|
+
authority_state_delta: {},
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
] as const;
|
|
439
|
+
|
|
294
440
|
export const ENROLLMENT_AUTHORITY_CONFORMANCE = [
|
|
295
441
|
{
|
|
296
442
|
name: 'ordinary enrollment creates no authority or cube state',
|
package/src/protocol/contract.ts
CHANGED
|
@@ -13,12 +13,14 @@ import type {
|
|
|
13
13
|
} from './types.js';
|
|
14
14
|
|
|
15
15
|
export const SHARED_PACKAGE_NAME = 'borgmcp-shared' as const;
|
|
16
|
-
export const SHARED_PACKAGE_VERSION = '0.6.
|
|
16
|
+
export const SHARED_PACKAGE_VERSION = '0.6.4' as const;
|
|
17
17
|
|
|
18
18
|
export const HEALTH_PATH = '/healthz' as const;
|
|
19
19
|
export const PROTOCOL_INFO_PATH = '/api/protocol' as const;
|
|
20
20
|
export const ENROLLMENT_EXCHANGE_PATH = '/api/enrollment/exchange' as const;
|
|
21
21
|
export const CUBES_PATH = '/api/cubes' as const;
|
|
22
|
+
export const REPOSITORY_CUBE_RESOLVE_PATH = '/api/repository-cubes/resolve' as const;
|
|
23
|
+
export const REPOSITORY_CUBE_ASSOCIATION_PATH = '/api/repository-cubes/association' as const;
|
|
22
24
|
export const ATTACH_PATH = '/api/client/attach' as const;
|
|
23
25
|
export const SELF_RUNTIME_METADATA_PATH = '/api/cubes/:cubeId/drones/self/metadata' as const;
|
|
24
26
|
|
|
@@ -27,6 +29,20 @@ export const PROTOCOL_HTTP_CONTRACT = {
|
|
|
27
29
|
protocol: { method: 'GET', path: PROTOCOL_INFO_PATH, authenticated: false, success_status: 200 },
|
|
28
30
|
enrollment: { method: 'POST', path: ENROLLMENT_EXCHANGE_PATH, authenticated: 'invitation', success_status: 201 },
|
|
29
31
|
cubes: { method: 'POST', path: CUBES_PATH, authenticated: true, success_status: 201 },
|
|
32
|
+
repository_cube_resolve: {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
path: REPOSITORY_CUBE_RESOLVE_PATH,
|
|
35
|
+
authenticated: true,
|
|
36
|
+
success_status: 200,
|
|
37
|
+
mutation: false,
|
|
38
|
+
},
|
|
39
|
+
repository_cube_association: {
|
|
40
|
+
method: 'PUT',
|
|
41
|
+
path: REPOSITORY_CUBE_ASSOCIATION_PATH,
|
|
42
|
+
authenticated: true,
|
|
43
|
+
success_status: 200,
|
|
44
|
+
mutation: true,
|
|
45
|
+
},
|
|
30
46
|
attach: { method: 'POST', path: ATTACH_PATH, authenticated: true, success_status: 200 },
|
|
31
47
|
drone_reassign: {
|
|
32
48
|
method: 'PATCH',
|
|
@@ -148,6 +164,33 @@ export interface CreateCubeResponse {
|
|
|
148
164
|
access: 'manage';
|
|
149
165
|
}
|
|
150
166
|
|
|
167
|
+
export interface ResolveRepositoryCubeRequest {
|
|
168
|
+
working_repo_name: string;
|
|
169
|
+
repository: CreateCubeRepository;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface AssociateRepositoryCubeRequest extends ResolveRepositoryCubeRequest {
|
|
173
|
+
cube_id: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ResolvedRepositoryCube {
|
|
177
|
+
result: 'resolved';
|
|
178
|
+
cube_id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
working_repo_name: string;
|
|
181
|
+
repository: CreateCubeRepository;
|
|
182
|
+
template: CubeTemplate;
|
|
183
|
+
human_seat_role_id: string;
|
|
184
|
+
default_worker_role_id: string;
|
|
185
|
+
access: 'manage';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type ResolveRepositoryCubeResponse =
|
|
189
|
+
| { result: 'none' }
|
|
190
|
+
| ResolvedRepositoryCube;
|
|
191
|
+
|
|
192
|
+
export type AssociateRepositoryCubeResponse = ResolvedRepositoryCube;
|
|
193
|
+
|
|
151
194
|
export interface AckLogRequest {
|
|
152
195
|
entry_id: string;
|
|
153
196
|
kind: 'ack' | 'claim';
|
|
@@ -455,10 +498,7 @@ export function decodeCreateCubeRequest(value: unknown): CreateCubeRequest {
|
|
|
455
498
|
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
456
499
|
fail('Cube name contains unsupported characters.', ['name']);
|
|
457
500
|
}
|
|
458
|
-
const workingRepoName =
|
|
459
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
460
|
-
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
461
|
-
}
|
|
501
|
+
const workingRepoName = decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']);
|
|
462
502
|
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
463
503
|
fail('Unsupported cube template.', ['template']);
|
|
464
504
|
}
|
|
@@ -509,10 +549,7 @@ export function decodeCreateCubeResponse(value: unknown): CreateCubeResponse {
|
|
|
509
549
|
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
510
550
|
fail('Cube name contains unsupported characters.', ['name']);
|
|
511
551
|
}
|
|
512
|
-
const workingRepoName =
|
|
513
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
514
|
-
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
515
|
-
}
|
|
552
|
+
const workingRepoName = decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']);
|
|
516
553
|
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
517
554
|
fail('Unsupported cube template.', ['template']);
|
|
518
555
|
}
|
|
@@ -559,6 +596,126 @@ export function decodeCreateCubeResponseEnvelope(value: unknown): ProtocolEnvelo
|
|
|
559
596
|
return decodeProtocolEnvelope(value, decodeCreateCubeResponse);
|
|
560
597
|
}
|
|
561
598
|
|
|
599
|
+
export function decodeResolveRepositoryCubeRequest(value: unknown): ResolveRepositoryCubeRequest {
|
|
600
|
+
const input = record(value);
|
|
601
|
+
exactKeys(input, ['working_repo_name', 'repository'], ['working_repo_name', 'repository']);
|
|
602
|
+
return {
|
|
603
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
604
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function decodeResolveRepositoryCubeRequestEnvelope(
|
|
609
|
+
value: unknown,
|
|
610
|
+
): ProtocolEnvelope<ResolveRepositoryCubeRequest> {
|
|
611
|
+
return decodeProtocolEnvelope(value, decodeResolveRepositoryCubeRequest);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function decodeAssociateRepositoryCubeRequest(value: unknown): AssociateRepositoryCubeRequest {
|
|
615
|
+
const input = record(value);
|
|
616
|
+
exactKeys(
|
|
617
|
+
input,
|
|
618
|
+
['cube_id', 'working_repo_name', 'repository'],
|
|
619
|
+
['cube_id', 'working_repo_name', 'repository'],
|
|
620
|
+
);
|
|
621
|
+
return {
|
|
622
|
+
cube_id: decodeUuid(input.cube_id, ['cube_id']),
|
|
623
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
624
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function decodeAssociateRepositoryCubeRequestEnvelope(
|
|
629
|
+
value: unknown,
|
|
630
|
+
): ProtocolEnvelope<AssociateRepositoryCubeRequest> {
|
|
631
|
+
return decodeProtocolEnvelope(value, decodeAssociateRepositoryCubeRequest);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function decodeResolvedRepositoryCube(value: unknown): ResolvedRepositoryCube {
|
|
635
|
+
const input = record(value);
|
|
636
|
+
exactKeys(
|
|
637
|
+
input,
|
|
638
|
+
[
|
|
639
|
+
'result',
|
|
640
|
+
'cube_id',
|
|
641
|
+
'name',
|
|
642
|
+
'working_repo_name',
|
|
643
|
+
'repository',
|
|
644
|
+
'template',
|
|
645
|
+
'human_seat_role_id',
|
|
646
|
+
'default_worker_role_id',
|
|
647
|
+
'access',
|
|
648
|
+
],
|
|
649
|
+
[
|
|
650
|
+
'result',
|
|
651
|
+
'cube_id',
|
|
652
|
+
'name',
|
|
653
|
+
'working_repo_name',
|
|
654
|
+
'repository',
|
|
655
|
+
'template',
|
|
656
|
+
'human_seat_role_id',
|
|
657
|
+
'default_worker_role_id',
|
|
658
|
+
'access',
|
|
659
|
+
],
|
|
660
|
+
);
|
|
661
|
+
if (input.result !== 'resolved') fail('Invalid repository cube result.', ['result']);
|
|
662
|
+
const name = boundedString(input.name, 1, 120, ['name']);
|
|
663
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
664
|
+
fail('Cube name contains unsupported characters.', ['name']);
|
|
665
|
+
}
|
|
666
|
+
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
667
|
+
fail('Unsupported cube template.', ['template']);
|
|
668
|
+
}
|
|
669
|
+
if (input.access !== 'manage') fail('Repository cube access must be manage.', ['access']);
|
|
670
|
+
return {
|
|
671
|
+
result: 'resolved',
|
|
672
|
+
cube_id: decodeUuid(input.cube_id, ['cube_id']),
|
|
673
|
+
name,
|
|
674
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
675
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
676
|
+
template: input.template as CubeTemplate,
|
|
677
|
+
human_seat_role_id: decodeUuid(input.human_seat_role_id, ['human_seat_role_id']),
|
|
678
|
+
default_worker_role_id: decodeUuid(input.default_worker_role_id, ['default_worker_role_id']),
|
|
679
|
+
access: 'manage',
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
export function decodeResolveRepositoryCubeResponse(value: unknown): ResolveRepositoryCubeResponse {
|
|
684
|
+
const input = record(value);
|
|
685
|
+
if (input.result === 'none') {
|
|
686
|
+
exactKeys(input, ['result'], ['result']);
|
|
687
|
+
return { result: 'none' };
|
|
688
|
+
}
|
|
689
|
+
return decodeResolvedRepositoryCube(input);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function decodeResolveRepositoryCubeResponseEnvelope(
|
|
693
|
+
value: unknown,
|
|
694
|
+
): ProtocolEnvelope<ResolveRepositoryCubeResponse> {
|
|
695
|
+
return decodeProtocolEnvelope(value, decodeResolveRepositoryCubeResponse);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function decodeAssociateRepositoryCubeResponse(value: unknown): AssociateRepositoryCubeResponse {
|
|
699
|
+
return decodeResolvedRepositoryCube(value);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function decodeAssociateRepositoryCubeResponseEnvelope(
|
|
703
|
+
value: unknown,
|
|
704
|
+
): ProtocolEnvelope<AssociateRepositoryCubeResponse> {
|
|
705
|
+
return decodeProtocolEnvelope(value, decodeAssociateRepositoryCubeResponse);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function decodeWorkingRepositoryName(
|
|
709
|
+
value: unknown,
|
|
710
|
+
path: readonly (string | number)[],
|
|
711
|
+
): string {
|
|
712
|
+
const name = boundedString(value, 1, 120, path);
|
|
713
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
714
|
+
fail('Repository name contains unsupported characters.', path);
|
|
715
|
+
}
|
|
716
|
+
return name;
|
|
717
|
+
}
|
|
718
|
+
|
|
562
719
|
export function decodeAppendLogRequest(value: unknown): import('./types.js').AppendLogRequest {
|
|
563
720
|
const input = record(value);
|
|
564
721
|
exactKeys(
|
package/src/protocol/errors.ts
CHANGED
|
@@ -13,6 +13,8 @@ export enum ErrorCode {
|
|
|
13
13
|
DATABASE_ERROR = 'DATABASE_ERROR',
|
|
14
14
|
EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR',
|
|
15
15
|
NOT_FOUND = 'NOT_FOUND',
|
|
16
|
+
REPOSITORY_ALREADY_ASSOCIATED = 'REPOSITORY_ALREADY_ASSOCIATED',
|
|
17
|
+
CUBE_ALREADY_ASSOCIATED = 'CUBE_ALREADY_ASSOCIATED',
|
|
16
18
|
ROLE_IN_USE = 'ROLE_IN_USE',
|
|
17
19
|
ROLE_HAS_FROZEN_DRONES = 'ROLE_HAS_FROZEN_DRONES',
|
|
18
20
|
DRONE_EVICTED = 'DRONE_EVICTED',
|
package/src/protocol/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Current Borg coordination protocol generation. Clean-slate
|
|
2
|
-
export const PROTOCOL_VERSION = '
|
|
1
|
+
/** Current Borg coordination protocol generation. Clean-slate v5. */
|
|
2
|
+
export const PROTOCOL_VERSION = '5' as const;
|
|
3
3
|
|
|
4
4
|
export type ProtocolVersion = typeof PROTOCOL_VERSION;
|