borgmcp-shared 1.1.0 → 2.0.0

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.
@@ -282,7 +282,6 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
282
282
  { id: 'cursor.explicit-expiry', area: 'cursor' },
283
283
  { id: 'acks.idempotent', area: 'acks' },
284
284
  { id: 'acks.status-query', area: 'acks' },
285
- { id: 'claims.durable-noncursor', area: 'claims' },
286
285
  { id: 'decisions.topic-supersession', area: 'decisions' },
287
286
  { id: 'security.drone-management-authorization', area: 'security' },
288
287
  { id: 'security.manage-access-matrix', area: 'security' },
@@ -318,7 +317,6 @@ const FIXTURE_PREREQUISITES: Partial<Record<AdapterConformanceFixtureId, readonl
318
317
  'cursor.explicit-expiry': ['shared.enrolled-principals'],
319
318
  'acks.idempotent': ['shared.enrolled-principals'],
320
319
  'acks.status-query': ['shared.enrolled-principals'],
321
- 'claims.durable-noncursor': ['shared.enrolled-principals'],
322
320
  'decisions.topic-supersession': ['shared.enrolled-principals'],
323
321
  'security.manage-access-matrix': ['shared.enrolled-principals'],
324
322
  'drones.reassign-invariants': ['shared.enrolled-principals'],
@@ -814,6 +812,20 @@ export async function runAdapterConformance(
814
812
  const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
815
813
  expectStatus(createdResponse, 201, 'Owner cube create');
816
814
  const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
815
+ expectError(
816
+ await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-case-variant-duplicate', {
817
+ ...cubeRequest,
818
+ retry_key: '00000000-0000-4000-8000-000000000214',
819
+ name: 'repository one',
820
+ repository: {
821
+ kind: 'origin' as const,
822
+ value: 'https://github.com/Byte-Ventures/repository-one-case-variant',
823
+ },
824
+ })),
825
+ 409,
826
+ ErrorCode.INVALID_INPUT,
827
+ 'Same-owner case-variant cube name',
828
+ );
817
829
  const createdRoleProbes = new Map<string, {
818
830
  role: unknown;
819
831
  droneId: string;
@@ -1158,26 +1170,27 @@ export async function runAdapterConformance(
1158
1170
  same(await resolve('repository-resolve-alternate-after-repository-conflict', alternateResolveRequest), { result: 'none' }),
1159
1171
  'Repository conflict created an alternate binding.',
1160
1172
  );
1161
- expectSecretFreeError(
1162
- await environment.operations.associateRepositoryCube(
1163
- credentialA,
1164
- createProtocolEnvelope('repository-associate-cube-conflict', {
1165
- cube_id: cubeA.id,
1166
- ...alternateResolveRequest,
1167
- }),
1168
- ),
1169
- 409,
1170
- ErrorCode.CUBE_ALREADY_ASSOCIATED,
1171
- 'Cube-to-other-repository conflict',
1172
- [cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository'],
1173
+ const alternateAssociationResponse = await environment.operations.associateRepositoryCube(
1174
+ credentialA,
1175
+ createProtocolEnvelope('repository-associate-second-repository', {
1176
+ cube_id: cubeA.id,
1177
+ ...alternateResolveRequest,
1178
+ }),
1173
1179
  );
1180
+ expectStatus(alternateAssociationResponse, 200, 'Second repository association');
1181
+ const alternateAuthoritative = decodeAssociateRepositoryCubeResponseEnvelope(
1182
+ alternateAssociationResponse.body,
1183
+ ).payload;
1174
1184
  invariant(
1175
- same(await resolve('repository-resolve-after-cube-conflict', resolveRequest), authoritative),
1176
- 'Cube conflict changed the authoritative original binding.',
1185
+ same(await resolve('repository-resolve-after-second-association', resolveRequest), authoritative),
1186
+ 'Second repository association changed the authoritative original binding.',
1177
1187
  );
1178
1188
  invariant(
1179
- same(await resolve('repository-resolve-alternate-after-cube-conflict', alternateResolveRequest), { result: 'none' }),
1180
- 'Cube conflict created the refused alternate binding.',
1189
+ same(
1190
+ await resolve('repository-resolve-alternate-after-second-association', alternateResolveRequest),
1191
+ alternateAuthoritative,
1192
+ ),
1193
+ 'Second repository association was not observable.',
1181
1194
  );
1182
1195
  const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
1183
1196
  name: 'Other Client Legacy Cube',
@@ -1254,7 +1267,7 @@ export async function runAdapterConformance(
1254
1267
  resolved: true,
1255
1268
  idempotent: true,
1256
1269
  repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
1257
- cube_conflict: ErrorCode.CUBE_ALREADY_ASSOCIATED,
1270
+ multiple_repositories_per_cube: true,
1258
1271
  inaccessible: ErrorCode.ACCESS_DENIED,
1259
1272
  inaccessible_binding_hidden: true,
1260
1273
  cross_client_binding_hidden: true,
@@ -2259,42 +2272,6 @@ export async function runAdapterConformance(
2259
2272
  };
2260
2273
  });
2261
2274
 
2262
- await record('claims.durable-noncursor', async () => {
2263
- const principal = await environment.admin.createPrincipal('claim-noncursor');
2264
- const cube = await environment.admin.createCube('claim-noncursor');
2265
- await environment.admin.grantCube(principal, cube);
2266
- const credential = await environment.admin.issueDroneSession(principal);
2267
- const appended = await environment.operations.append(
2268
- credential,
2269
- cube,
2270
- createProtocolEnvelope('claim-fixture-entry', {
2271
- post_id: '00000000-0000-4000-8000-000000000314',
2272
- to: 'broadcast',
2273
- message: 'claim cursor baseline',
2274
- }),
2275
- );
2276
- expectStatus(appended, 201, 'Claim fixture append');
2277
- const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
2278
- const baselineCursor = { id: entry.id, created_at: entry.created_at };
2279
- const claim = await environment.operations.ack(
2280
- credential,
2281
- cube,
2282
- createProtocolEnvelope('claim-fixture-claim', { entry_id: entry.id, kind: 'claim' }),
2283
- );
2284
- expectStatus(claim, 204, 'Claim');
2285
- const read = await environment.operations.read(
2286
- credential,
2287
- cube,
2288
- createProtocolEnvelope('claim-fixture-readback', { cursor: baselineCursor, limit: 10 }),
2289
- );
2290
- expectStatus(read, 200, 'Claim-state read');
2291
- const page = decodeReadLogResultEnvelope(read.body).payload;
2292
- invariant(page.entries.length === 0, 'Claim unexpectedly created a log entry.');
2293
- invariant(page.cursor !== null && compareLogCursor(page.cursor, baselineCursor) === 0, 'Claim advanced the log cursor.');
2294
- invariant(page.claims.some((item) => item.log_entry_id === entry.id), 'Claim was not durable in a later read.');
2295
- return { durable_claims: 1, entries: 0, cursor_advanced: false };
2296
- });
2297
-
2298
2275
  await record('decisions.topic-supersession', async () => {
2299
2276
  const firstResponse = await environment.operations.recordDecision(
2300
2277
  credentialA,
@@ -3233,24 +3210,25 @@ export async function runAdapterConformance(
3233
3210
  name: 'Ambiguous Role',
3234
3211
  detailedDescription,
3235
3212
  });
3236
- await environment.admin.createRole(roleContractCube, {
3237
- roleClass: 'worker',
3238
- isHumanSeat: false,
3239
- name: 'AMBIGUOUS ROLE',
3240
- detailedDescription,
3241
- });
3242
3213
  expectError(
3243
- await environment.operations.roleRationale(
3244
- roleContractReadCredential,
3214
+ await environment.operations.createRole(
3215
+ roleContractCredential,
3245
3216
  roleContractCube,
3246
- createProtocolEnvelope('rationale-ambiguous-role', {
3247
- role: 'ambiguous role',
3248
- section: 'Workflow rationale',
3249
- }),
3217
+ createProtocolEnvelope('role-case-variant-duplicate', { name: 'AMBIGUOUS ROLE' }),
3218
+ ),
3219
+ 409,
3220
+ ErrorCode.INVALID_INPUT,
3221
+ 'Case-variant duplicate role name',
3222
+ );
3223
+ expectError(
3224
+ await environment.operations.createRole(
3225
+ roleContractCredential,
3226
+ roleContractCube,
3227
+ createProtocolEnvelope('role-invalid-character', { name: 'Invalid/Role' }),
3250
3228
  ),
3251
3229
  400,
3252
3230
  ErrorCode.INVALID_INPUT,
3253
- 'Ambiguous role rationale lookup',
3231
+ 'Invalid role name character',
3254
3232
  );
3255
3233
  expectError(
3256
3234
  await environment.operations.roleRationale(
@@ -3273,7 +3251,8 @@ export async function runAdapterConformance(
3273
3251
  unknown_role_code: ErrorCode.ROLE_NOT_FOUND,
3274
3252
  inaccessible_role_code: ErrorCode.ROLE_NOT_FOUND,
3275
3253
  unknown_section_code: ErrorCode.ROLE_SECTION_NOT_FOUND,
3276
- ambiguous_role_code: ErrorCode.INVALID_INPUT,
3254
+ case_variant_duplicate_status: 409,
3255
+ invalid_name_code: ErrorCode.INVALID_INPUT,
3277
3256
  invalid_selector_code: ErrorCode.INVALID_INPUT,
3278
3257
  };
3279
3258
  });
@@ -3861,6 +3840,12 @@ export async function runAdapterConformance(
3861
3840
  );
3862
3841
  expectStatus(log, 200, `${label} deletion log readback`);
3863
3842
  const logPayload = decodeReadLogResultEnvelope(log.body).payload;
3843
+ const claimStatus = await environment.operations.ackStatus(
3844
+ manager.credential,
3845
+ cube,
3846
+ createProtocolEnvelope(`delete-${label}-claim-readback`, { entry_id: entry.id }),
3847
+ );
3848
+ expectStatus(claimStatus, 200, `${label} deletion claim readback`);
3864
3849
  const roster = await environment.operations.listDrones(manager.credential, cube);
3865
3850
  expectStatus(roster, 200, `${label} deletion roster readback`);
3866
3851
  const roleReadback = await environment.operations.attach(
@@ -3877,7 +3862,7 @@ export async function runAdapterConformance(
3877
3862
  return {
3878
3863
  decisions: active,
3879
3864
  entries: logPayload.entries,
3880
- claims: logPayload.claims,
3865
+ claims: decodeAckStatusResultEnvelope(claimStatus.body).payload.claims,
3881
3866
  drone: listedDrone(roster, drone.id),
3882
3867
  role: rolePayload,
3883
3868
  };
@@ -474,7 +474,7 @@ export interface CubeTemplateAcceptanceConformanceVector {
474
474
  accepts: boolean;
475
475
  }
476
476
 
477
- /** Protocol v13's complete closed acceptance set for cube-creation templates. */
477
+ /** Protocol v14's complete closed acceptance set for cube-creation templates. */
478
478
  export const CUBE_TEMPLATE_ACCEPTANCE_CONFORMANCE:
479
479
  readonly CubeTemplateAcceptanceConformanceVector[] = [
480
480
  { name: 'accepts the software-development template', template: 'software-dev', accepts: true },
@@ -656,9 +656,9 @@ export interface AssociateRepositoryCubeConformanceVector {
656
656
  status: 200;
657
657
  }
658
658
  | {
659
- outcome: 'repository_conflict' | 'cube_conflict';
659
+ outcome: 'repository_conflict';
660
660
  status: 409;
661
- error: 'REPOSITORY_ALREADY_ASSOCIATED' | 'CUBE_ALREADY_ASSOCIATED';
661
+ error: 'REPOSITORY_ALREADY_ASSOCIATED';
662
662
  diagnostic_disclosure: 'none';
663
663
  };
664
664
  }
@@ -687,14 +687,12 @@ readonly AssociateRepositoryCubeConformanceVector[] = [
687
687
  },
688
688
  },
689
689
  {
690
- name: 'cube already bound to another repository conflicts without mutation',
690
+ name: 'a second repository may associate with the same explicit cube',
691
691
  initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
692
692
  retry: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_TWO },
693
693
  expected: {
694
- outcome: 'cube_conflict',
695
- status: 409,
696
- error: 'CUBE_ALREADY_ASSOCIATED',
697
- diagnostic_disclosure: 'none',
694
+ outcome: 'resolved',
695
+ status: 200,
698
696
  },
699
697
  },
700
698
  ];
@@ -949,7 +947,6 @@ export interface RoleRationaleConformanceVector {
949
947
  | 'unknown-role'
950
948
  | 'inaccessible-role'
951
949
  | 'unknown-section'
952
- | 'ambiguous-role-name'
953
950
  | 'invalid-selector';
954
951
  expected:
955
952
  | { status: 200; canonical_heading: true; exact_body: true; mutation: 'none' }
@@ -984,11 +981,6 @@ export const ROLE_RATIONALE_CONFORMANCE: readonly RoleRationaleConformanceVector
984
981
  fixture: 'unknown-section',
985
982
  expected: { status: 404, error: 'ROLE_SECTION_NOT_FOUND', mutation: 'none' },
986
983
  },
987
- {
988
- name: 'rejects a role name with multiple case-insensitive matches',
989
- fixture: 'ambiguous-role-name',
990
- expected: { status: 400, error: 'INVALID_INPUT', mutation: 'none' },
991
- },
992
984
  {
993
985
  name: 'rejects malformed or ambiguous selectors',
994
986
  fixture: 'invalid-selector',
@@ -13,7 +13,7 @@ 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 = '1.1.0' as const;
16
+ export const SHARED_PACKAGE_VERSION = '2.0.0' as const;
17
17
  /** Maximum UTF-8 payload for each newly recorded decision text field. */
18
18
  export const DECISION_TEXT_MAX_BYTES = 512 as const;
19
19
  /** Maximum UTF-8 size of role detailed-description text and any returned section slice. */
@@ -26,15 +26,6 @@ export interface ReadLogRequest {
26
26
  limit?: number;
27
27
  }
28
28
 
29
- export interface ClaimRecord {
30
- log_entry_id: string;
31
- claimant_drone_id: string;
32
- claimant_label: string | null;
33
- claimant_role: string | null;
34
- claimed_at: string;
35
- stale: boolean;
36
- }
37
-
38
29
  export interface AppendLogResult extends Omit<AppendLogResponse, 'entry'> {
39
30
  entry: EnrichedStreamEntry;
40
31
  }
@@ -44,7 +35,6 @@ export interface ReadLogResult {
44
35
  cursor: LogCursor | null;
45
36
  behind_by: number;
46
37
  has_more: boolean;
47
- claims: ClaimRecord[];
48
38
  }
49
39
 
50
40
  export interface EntryQueryRequest {
@@ -443,38 +433,6 @@ export function decodeAckStatusResultEnvelope(
443
433
  return decodeProtocolEnvelope(value, decodeAckStatusResult);
444
434
  }
445
435
 
446
- function decodeClaimRecord(value: unknown): ClaimRecord {
447
- const input = object(value);
448
- exact(
449
- input,
450
- [
451
- 'log_entry_id',
452
- 'claimant_drone_id',
453
- 'claimant_label',
454
- 'claimant_role',
455
- 'claimed_at',
456
- 'stale',
457
- ],
458
- [
459
- 'log_entry_id',
460
- 'claimant_drone_id',
461
- 'claimant_label',
462
- 'claimant_role',
463
- 'claimed_at',
464
- 'stale',
465
- ],
466
- );
467
- if (typeof input.stale !== 'boolean') throw new ProtocolContractError('Invalid claim stale flag.');
468
- return {
469
- log_entry_id: decodeUuid(input.log_entry_id, ['log_entry_id']),
470
- claimant_drone_id: decodeUuid(input.claimant_drone_id, ['claimant_drone_id']),
471
- claimant_label: nullableString(input.claimant_label, 'claimant_label', 120),
472
- claimant_role: nullableString(input.claimant_role, 'claimant_role', 120),
473
- claimed_at: decodeCanonicalTimestamp(input.claimed_at, ['claimed_at']),
474
- stale: input.stale,
475
- };
476
- }
477
-
478
436
  function decodeRoutingEcho(value: unknown): RoutingEcho {
479
437
  const input = object(value);
480
438
  exact(
@@ -549,15 +507,12 @@ export function decodeReadLogResult(value: unknown): ReadLogResult {
549
507
  const input = object(value);
550
508
  exact(
551
509
  input,
552
- ['entries', 'cursor', 'behind_by', 'has_more', 'claims'],
553
- ['entries', 'cursor', 'behind_by', 'has_more', 'claims'],
510
+ ['entries', 'cursor', 'behind_by', 'has_more'],
511
+ ['entries', 'cursor', 'behind_by', 'has_more'],
554
512
  );
555
513
  if (!Array.isArray(input.entries) || input.entries.length > 500) {
556
514
  throw new ProtocolContractError('Invalid read-log entries.');
557
515
  }
558
- if (!Array.isArray(input.claims) || input.claims.length > 500) {
559
- throw new ProtocolContractError('Invalid read-log claims.');
560
- }
561
516
  if (typeof input.has_more !== 'boolean') throw new ProtocolContractError('Invalid has_more flag.');
562
517
  const entries = input.entries.map(decodeEnrichedStreamEntry);
563
518
  const cursor = input.cursor === null ? null : decodeLogCursor(input.cursor, ['cursor']);
@@ -579,7 +534,6 @@ export function decodeReadLogResult(value: unknown): ReadLogResult {
579
534
  cursor,
580
535
  behind_by: nonNegativeInteger(input.behind_by, 'behind_by', Number.MAX_SAFE_INTEGER),
581
536
  has_more: input.has_more,
582
- claims: input.claims.map(decodeClaimRecord),
583
537
  };
584
538
  }
585
539
 
@@ -146,23 +146,12 @@ export interface RosterResponse {
146
146
  since?: string | null;
147
147
  }
148
148
 
149
- export interface ReadLogClaim {
150
- log_entry_id: string;
151
- claimant_drone_id: string;
152
- claimant_label: string | null;
153
- claimant_role: string | null;
154
- claimed_at: string;
155
- stale: boolean;
156
- }
157
-
158
149
  export interface ReadLogResponse {
159
150
  entries: ActivityLogEntry[];
160
151
  drones: Drone[];
161
152
  roles: PublicRole[];
162
153
  behind_by?: number;
163
154
  has_more?: boolean;
164
- /** Added with advisory claims; optional when reading older servers. */
165
- claims?: ReadLogClaim[];
166
155
  }
167
156
 
168
157
  export interface RoutingEcho {
@@ -1,4 +1,4 @@
1
- /** Current Borg coordination protocol generation. Clean-slate v13. */
2
- export const PROTOCOL_VERSION = '13' as const;
1
+ /** Current Borg coordination protocol generation. Clean-slate v14. */
2
+ export const PROTOCOL_VERSION = '14' as const;
3
3
 
4
4
  export type ProtocolVersion = typeof PROTOCOL_VERSION;