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.
@@ -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.2' as const;
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(input, ['retry_key', 'name', 'template'], ['retry_key', 'name', 'template']);
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
- ['cube_id', 'human_seat_role_id', 'default_worker_role_id', 'access'],
461
- ['cube_id', 'human_seat_role_id', 'default_worker_role_id', 'access'],
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
- // ── v3 clean-slate wire types ──────────────────────────────────────────────
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 a v3 attach request. Strict: exact keys, bounded sizes,
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 a v3 attach request envelope. Stamps the canonical protocol version.
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 a v3 attach request envelope. Verifies protocol_version === PROTOCOL_VERSION
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 a v3 attach response. Strict: exact keys and result discriminant.
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 a v3 attach response wrapped in a ProtocolEnvelope.
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> {
@@ -1,4 +1,4 @@
1
- /** Current Borg coordination protocol generation. Clean-slate v3. */
2
- export const PROTOCOL_VERSION = '3' as const;
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
- name: 'software-dev',
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
- name: 'starter',
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 {