borgmcp-shared 0.6.2 → 0.6.3
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 -7
- package/dist/conformance/adapter.d.ts +9 -1
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +65 -16
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +34 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +87 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +16 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +74 -5
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +16 -3
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +8 -1
- package/docs/enrollment.md +54 -24
- package/docs/releasing.md +23 -22
- package/package.json +1 -1
- package/src/conformance/adapter.ts +137 -22
- package/src/conformance/index.ts +125 -1
- package/src/protocol/contract.ts +97 -11
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +20 -3
package/src/protocol/contract.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ErrorCode } from './errors.js';
|
|
2
2
|
import { PROTOCOL_VERSION, type ProtocolVersion } from './version.js';
|
|
3
3
|
import {
|
|
4
|
+
canonicalizeRepositoryIdentity,
|
|
4
5
|
RuntimeMetadataValidationError,
|
|
5
6
|
validateRuntimeMetadata,
|
|
6
7
|
validateRuntimeMetadataPatch,
|
|
@@ -12,7 +13,7 @@ import type {
|
|
|
12
13
|
} from './types.js';
|
|
13
14
|
|
|
14
15
|
export const SHARED_PACKAGE_NAME = 'borgmcp-shared' as const;
|
|
15
|
-
export const SHARED_PACKAGE_VERSION = '0.6.
|
|
16
|
+
export const SHARED_PACKAGE_VERSION = '0.6.3' as const;
|
|
16
17
|
|
|
17
18
|
export const HEALTH_PATH = '/healthz' as const;
|
|
18
19
|
export const PROTOCOL_INFO_PATH = '/api/protocol' as const;
|
|
@@ -120,17 +121,28 @@ export type EnrollmentExchangeResponse =
|
|
|
120
121
|
| ClientEnrollmentExchangeResponse
|
|
121
122
|
| OwnerEnrollmentExchangeResponse;
|
|
122
123
|
|
|
123
|
-
export const CUBE_TEMPLATES = ['default'] as const;
|
|
124
|
+
export const CUBE_TEMPLATES = ['default', 'software-dev', 'starter'] as const;
|
|
124
125
|
export type CubeTemplate = (typeof CUBE_TEMPLATES)[number];
|
|
125
126
|
|
|
127
|
+
export type CreateCubeRepository =
|
|
128
|
+
| { kind: 'origin'; value: string }
|
|
129
|
+
| { kind: 'local'; value: string };
|
|
130
|
+
|
|
126
131
|
export interface CreateCubeRequest {
|
|
127
132
|
retry_key: string;
|
|
128
133
|
name: string;
|
|
134
|
+
working_repo_name: string;
|
|
135
|
+
repository: CreateCubeRepository;
|
|
129
136
|
template: CubeTemplate;
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
export interface CreateCubeResponse {
|
|
140
|
+
result: 'created' | 'resolved';
|
|
133
141
|
cube_id: string;
|
|
142
|
+
name: string;
|
|
143
|
+
working_repo_name: string;
|
|
144
|
+
repository: CreateCubeRepository;
|
|
145
|
+
template: CubeTemplate;
|
|
134
146
|
human_seat_role_id: string;
|
|
135
147
|
default_worker_role_id: string;
|
|
136
148
|
access: 'manage';
|
|
@@ -434,17 +446,27 @@ function decodeExactServerCapabilities(
|
|
|
434
446
|
|
|
435
447
|
export function decodeCreateCubeRequest(value: unknown): CreateCubeRequest {
|
|
436
448
|
const input = record(value);
|
|
437
|
-
exactKeys(
|
|
449
|
+
exactKeys(
|
|
450
|
+
input,
|
|
451
|
+
['retry_key', 'name', 'working_repo_name', 'repository', 'template'],
|
|
452
|
+
['retry_key', 'name', 'working_repo_name', 'repository', 'template'],
|
|
453
|
+
);
|
|
438
454
|
const name = boundedString(input.name, 1, 120, ['name']);
|
|
439
455
|
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
440
456
|
fail('Cube name contains unsupported characters.', ['name']);
|
|
441
457
|
}
|
|
458
|
+
const workingRepoName = boundedString(input.working_repo_name, 1, 120, ['working_repo_name']);
|
|
459
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
460
|
+
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
461
|
+
}
|
|
442
462
|
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
443
463
|
fail('Unsupported cube template.', ['template']);
|
|
444
464
|
}
|
|
445
465
|
return {
|
|
446
466
|
retry_key: decodeUuid(input.retry_key, ['retry_key']),
|
|
447
467
|
name,
|
|
468
|
+
working_repo_name: workingRepoName,
|
|
469
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
448
470
|
template: input.template as CubeTemplate,
|
|
449
471
|
};
|
|
450
472
|
}
|
|
@@ -457,18 +479,82 @@ export function decodeCreateCubeResponse(value: unknown): CreateCubeResponse {
|
|
|
457
479
|
const input = record(value);
|
|
458
480
|
exactKeys(
|
|
459
481
|
input,
|
|
460
|
-
[
|
|
461
|
-
|
|
482
|
+
[
|
|
483
|
+
'result',
|
|
484
|
+
'cube_id',
|
|
485
|
+
'name',
|
|
486
|
+
'working_repo_name',
|
|
487
|
+
'repository',
|
|
488
|
+
'template',
|
|
489
|
+
'human_seat_role_id',
|
|
490
|
+
'default_worker_role_id',
|
|
491
|
+
'access',
|
|
492
|
+
],
|
|
493
|
+
[
|
|
494
|
+
'result',
|
|
495
|
+
'cube_id',
|
|
496
|
+
'name',
|
|
497
|
+
'working_repo_name',
|
|
498
|
+
'repository',
|
|
499
|
+
'template',
|
|
500
|
+
'human_seat_role_id',
|
|
501
|
+
'default_worker_role_id',
|
|
502
|
+
'access',
|
|
503
|
+
],
|
|
462
504
|
);
|
|
505
|
+
if (input.result !== 'created' && input.result !== 'resolved') {
|
|
506
|
+
fail('Invalid cube creation result.', ['result']);
|
|
507
|
+
}
|
|
508
|
+
const name = boundedString(input.name, 1, 120, ['name']);
|
|
509
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
510
|
+
fail('Cube name contains unsupported characters.', ['name']);
|
|
511
|
+
}
|
|
512
|
+
const workingRepoName = boundedString(input.working_repo_name, 1, 120, ['working_repo_name']);
|
|
513
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
514
|
+
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
515
|
+
}
|
|
516
|
+
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
517
|
+
fail('Unsupported cube template.', ['template']);
|
|
518
|
+
}
|
|
463
519
|
if (input.access !== 'manage') fail('Created cube access must be manage.', ['access']);
|
|
464
520
|
return {
|
|
521
|
+
result: input.result,
|
|
465
522
|
cube_id: decodeUuid(input.cube_id, ['cube_id']),
|
|
523
|
+
name,
|
|
524
|
+
working_repo_name: workingRepoName,
|
|
525
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
526
|
+
template: input.template as CubeTemplate,
|
|
466
527
|
human_seat_role_id: decodeUuid(input.human_seat_role_id, ['human_seat_role_id']),
|
|
467
528
|
default_worker_role_id: decodeUuid(input.default_worker_role_id, ['default_worker_role_id']),
|
|
468
529
|
access: 'manage',
|
|
469
530
|
};
|
|
470
531
|
}
|
|
471
532
|
|
|
533
|
+
function decodeCreateCubeRepository(
|
|
534
|
+
value: unknown,
|
|
535
|
+
path: readonly (string | number)[],
|
|
536
|
+
): CreateCubeRepository {
|
|
537
|
+
const input = record(value, path);
|
|
538
|
+
exactKeys(input, ['kind', 'value'], ['kind', 'value'], path);
|
|
539
|
+
if (input.kind === 'local') {
|
|
540
|
+
return { kind: 'local', value: decodeUuid(input.value, [...path, 'value']) };
|
|
541
|
+
}
|
|
542
|
+
if (input.kind !== 'origin') fail('Unsupported repository identity kind.', [...path, 'kind']);
|
|
543
|
+
const origin = boundedString(input.value, 1, 512, [...path, 'value']);
|
|
544
|
+
try {
|
|
545
|
+
if (canonicalizeRepositoryIdentity(origin).working_repo_origin !== origin) {
|
|
546
|
+
fail('Repository origin must be canonical.', [...path, 'value']);
|
|
547
|
+
}
|
|
548
|
+
} catch (error) {
|
|
549
|
+
if (error instanceof ProtocolContractError) throw error;
|
|
550
|
+
if (error instanceof RuntimeMetadataValidationError) {
|
|
551
|
+
fail('Repository origin must be canonical.', [...path, 'value']);
|
|
552
|
+
}
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
return { kind: 'origin', value: origin };
|
|
556
|
+
}
|
|
557
|
+
|
|
472
558
|
export function decodeCreateCubeResponseEnvelope(value: unknown): ProtocolEnvelope<CreateCubeResponse> {
|
|
473
559
|
return decodeProtocolEnvelope(value, decodeCreateCubeResponse);
|
|
474
560
|
}
|
|
@@ -698,7 +784,7 @@ export function decodeUpdateDroneRuntimeMetadataResponseEnvelope(
|
|
|
698
784
|
return decodeProtocolEnvelope(value, decodeUpdateDroneRuntimeMetadataResponse);
|
|
699
785
|
}
|
|
700
786
|
|
|
701
|
-
// ──
|
|
787
|
+
// ── Clean-slate attach wire types (introduced in v3) ──────────────────────
|
|
702
788
|
|
|
703
789
|
export interface AttachRequest {
|
|
704
790
|
cube_id: string;
|
|
@@ -800,7 +886,7 @@ function decodeAttachSession(value: unknown, path: readonly (string | number)[])
|
|
|
800
886
|
}
|
|
801
887
|
|
|
802
888
|
/**
|
|
803
|
-
* Decode
|
|
889
|
+
* Decode an attach request. Strict: exact keys, bounded sizes,
|
|
804
890
|
* session_credential is token-safe and never echoed in errors.
|
|
805
891
|
*/
|
|
806
892
|
export function decodeAttachRequest(value: unknown): AttachRequest {
|
|
@@ -825,7 +911,7 @@ export function decodeAttachRequest(value: unknown): AttachRequest {
|
|
|
825
911
|
}
|
|
826
912
|
|
|
827
913
|
/**
|
|
828
|
-
* Create
|
|
914
|
+
* Create an attach request envelope. Stamps the canonical protocol version.
|
|
829
915
|
*/
|
|
830
916
|
export function createAttachRequestEnvelope(
|
|
831
917
|
requestId: string,
|
|
@@ -839,7 +925,7 @@ export function createAttachRequestEnvelope(
|
|
|
839
925
|
}
|
|
840
926
|
|
|
841
927
|
/**
|
|
842
|
-
* Decode
|
|
928
|
+
* Decode an attach request envelope. Verifies protocol_version === PROTOCOL_VERSION
|
|
843
929
|
* BEFORE decoding the payload — a wrong tag never invokes the payload decoder
|
|
844
930
|
* and never exposes or returns the supplied session_credential.
|
|
845
931
|
* Uses a static token-safe diagnostic; does not interpolate attacker-controlled text.
|
|
@@ -869,7 +955,7 @@ export function decodeAttachRequestEnvelope(
|
|
|
869
955
|
}
|
|
870
956
|
|
|
871
957
|
/**
|
|
872
|
-
* Decode
|
|
958
|
+
* Decode an attach response. Strict: exact keys and result discriminant.
|
|
873
959
|
*/
|
|
874
960
|
export function decodeAttachResponse(value: unknown): AttachResponse {
|
|
875
961
|
const input = record(value);
|
|
@@ -893,7 +979,7 @@ export function decodeAttachResponse(value: unknown): AttachResponse {
|
|
|
893
979
|
}
|
|
894
980
|
|
|
895
981
|
/**
|
|
896
|
-
* Decode
|
|
982
|
+
* Decode an attach response wrapped in a ProtocolEnvelope.
|
|
897
983
|
* Verifies protocol_version === PROTOCOL_VERSION before decoding payload.
|
|
898
984
|
*/
|
|
899
985
|
export function decodeAttachResponseEnvelope(value: unknown): ProtocolEnvelope<AttachResponse> {
|
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 v4. */
|
|
2
|
+
export const PROTOCOL_VERSION = '4' as const;
|
|
3
3
|
|
|
4
4
|
export type ProtocolVersion = typeof PROTOCOL_VERSION;
|
package/src/templates.ts
CHANGED
|
@@ -30,12 +30,29 @@ export type MessageTaxonomy = MessageTaxonomyClass[];
|
|
|
30
30
|
|
|
31
31
|
export interface Template {
|
|
32
32
|
name: string;
|
|
33
|
+
label: string;
|
|
34
|
+
short_description: string;
|
|
33
35
|
description: string;
|
|
34
36
|
roles: TemplateRole[];
|
|
35
37
|
cube_directive?: string;
|
|
36
38
|
message_taxonomy?: MessageTaxonomy;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
export const LEGACY_DEFAULT_TEMPLATE_LABEL = 'Default (legacy)';
|
|
42
|
+
|
|
43
|
+
export const NEW_CUBE_TEMPLATE_PRESENTATIONS = [
|
|
44
|
+
{
|
|
45
|
+
name: 'software-dev',
|
|
46
|
+
label: 'Software Development',
|
|
47
|
+
short_description: 'Recommended for code repositories.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'starter',
|
|
51
|
+
label: 'Starter',
|
|
52
|
+
short_description: 'Minimal roles for general projects.',
|
|
53
|
+
},
|
|
54
|
+
] as const;
|
|
55
|
+
|
|
39
56
|
export const ESCALATION_DISCIPLINE = `
|
|
40
57
|
|
|
41
58
|
Escalation:
|
|
@@ -318,7 +335,7 @@ const SECURITY_AUDITOR = `Perform only the routed security review of an exact so
|
|
|
318
335
|
- Do not implement fixes, merge, deploy, publish, tag, or release.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
319
336
|
|
|
320
337
|
const SOFTWARE_DEV: Template = {
|
|
321
|
-
|
|
338
|
+
...NEW_CUBE_TEMPLATE_PRESENTATIONS[0],
|
|
322
339
|
description: 'Scope-first multi-agent software development with one human Coordinator, implementation, and proportionate review roles.',
|
|
323
340
|
cube_directive: SOFTWARE_DEV_DIRECTIVE,
|
|
324
341
|
message_taxonomy: SOFTWARE_DEV_TAXONOMY,
|
|
@@ -432,7 +449,7 @@ const STARTER_TAXONOMY: MessageTaxonomy = [
|
|
|
432
449
|
];
|
|
433
450
|
|
|
434
451
|
const STARTER: Template = {
|
|
435
|
-
|
|
452
|
+
...NEW_CUBE_TEMPLATE_PRESENTATIONS[1],
|
|
436
453
|
description: 'Minimal scope-first template for general projects: a human Coordinator, a Worker, and a Reviewer.',
|
|
437
454
|
cube_directive: `## Scope and coordination
|
|
438
455
|
|
|
@@ -487,8 +504,8 @@ const STARTER: Template = {
|
|
|
487
504
|
};
|
|
488
505
|
|
|
489
506
|
export const TEMPLATES: Record<string, Template> = {
|
|
490
|
-
starter: STARTER,
|
|
491
507
|
'software-dev': SOFTWARE_DEV,
|
|
508
|
+
starter: STARTER,
|
|
492
509
|
};
|
|
493
510
|
|
|
494
511
|
export function getTemplate(name: string): Template | null {
|