borgmcp-shared 0.13.1 → 1.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.
Files changed (44) hide show
  1. package/README.md +4 -3
  2. package/RELEASES.md +7 -0
  3. package/dist/conformance/adapter.d.ts +15 -2
  4. package/dist/conformance/adapter.d.ts.map +1 -1
  5. package/dist/conformance/adapter.js +187 -41
  6. package/dist/conformance/adapter.js.map +1 -1
  7. package/dist/conformance/index.d.ts +49 -3
  8. package/dist/conformance/index.d.ts.map +1 -1
  9. package/dist/conformance/index.js +95 -18
  10. package/dist/conformance/index.js.map +1 -1
  11. package/dist/protocol/contract.d.ts +1 -1
  12. package/dist/protocol/contract.d.ts.map +1 -1
  13. package/dist/protocol/contract.js +13 -18
  14. package/dist/protocol/contract.js.map +1 -1
  15. package/dist/protocol/coordination.d.ts +36 -1
  16. package/dist/protocol/coordination.d.ts.map +1 -1
  17. package/dist/protocol/coordination.js +72 -6
  18. package/dist/protocol/coordination.js.map +1 -1
  19. package/dist/protocol/errors.d.ts +1 -1
  20. package/dist/protocol/errors.d.ts.map +1 -1
  21. package/dist/protocol/errors.js +1 -1
  22. package/dist/protocol/errors.js.map +1 -1
  23. package/dist/protocol/types.d.ts +1 -5
  24. package/dist/protocol/types.d.ts.map +1 -1
  25. package/dist/protocol/version.d.ts +1 -1
  26. package/dist/protocol/version.js +1 -1
  27. package/dist/templates.d.ts +1 -3
  28. package/dist/templates.d.ts.map +1 -1
  29. package/dist/templates.js +8 -79
  30. package/dist/templates.js.map +1 -1
  31. package/docs/compatibility.md +15 -0
  32. package/docs/release-records.json +28 -0
  33. package/docs/releases/0.14.0.md +8 -0
  34. package/docs/releases/1.0.0.md +11 -0
  35. package/docs/template-lifecycle.md +1 -1
  36. package/package.json +1 -1
  37. package/src/conformance/adapter.ts +369 -50
  38. package/src/conformance/index.ts +122 -22
  39. package/src/protocol/contract.ts +15 -21
  40. package/src/protocol/coordination.ts +136 -7
  41. package/src/protocol/errors.ts +1 -1
  42. package/src/protocol/types.ts +1 -5
  43. package/src/protocol/version.ts +2 -2
  44. package/src/templates.ts +8 -81
@@ -100,8 +100,6 @@ const APPEND_LOG_ENTRY: EnrichedStreamEntry = {
100
100
  const APPEND_LOG_ROUTING = {
101
101
  class: 'review',
102
102
  recipients: ['one-of-one-reviewer'],
103
- fellOpen: false,
104
- message: 'Routing applied.',
105
103
  } as const;
106
104
 
107
105
  export interface AppendLogResultConformanceVector {
@@ -119,31 +117,71 @@ export interface AppendLogRequestConformanceVector {
119
117
  export interface AppendLogIdempotencyConformanceVector {
120
118
  name: string;
121
119
  actor: 'same' | 'different';
122
- mutation: 'none' | 'message' | 'visibility' | 'recipient_set' | 'ignored_request_shape' | 'resolved_class_routing';
120
+ mutation: 'none' | 'message' | 'audience' | 'recipient_set' | 'classification';
123
121
  expected: 'deduplicated' | 'POST_ID_CONFLICT' | 'created';
124
122
  }
125
123
 
126
- /** Author-scoped post identity and exact resolved-routing retry outcomes. */
124
+ /** Author-scoped post identity and exact resolved-delivery retry outcomes. */
127
125
  export const APPEND_LOG_IDEMPOTENCY_CONFORMANCE: readonly AppendLogIdempotencyConformanceVector[] = [
128
126
  { name: 'deduplicates an exact same-author retry', actor: 'same', mutation: 'none', expected: 'deduplicated' },
129
127
  { name: 'rejects a same-author message collision', actor: 'same', mutation: 'message', expected: 'POST_ID_CONFLICT' },
130
- { name: 'rejects a same-author visibility collision', actor: 'same', mutation: 'visibility', expected: 'POST_ID_CONFLICT' },
128
+ { name: 'rejects a same-author audience collision', actor: 'same', mutation: 'audience', expected: 'POST_ID_CONFLICT' },
131
129
  { name: 'rejects a same-author recipient-set collision', actor: 'same', mutation: 'recipient_set', expected: 'POST_ID_CONFLICT' },
132
- { name: 'deduplicates ignored class selectors with identical resolved delivery', actor: 'same', mutation: 'ignored_request_shape', expected: 'deduplicated' },
133
- { name: 'rejects a same-author resolved class-routing collision', actor: 'same', mutation: 'resolved_class_routing', expected: 'POST_ID_CONFLICT' },
130
+ { name: 'deduplicates changed classification with identical explicit delivery', actor: 'same', mutation: 'classification', expected: 'deduplicated' },
134
131
  { name: 'creates an independent cross-author post', actor: 'different', mutation: 'none', expected: 'created' },
135
132
  ];
136
133
 
137
134
  const APPEND_LOG_REQUEST = {
138
135
  post_id: '00000000-0000-4000-8000-000000000205',
139
136
  message: 'REVIEW-READY: example',
137
+ to: 'broadcast',
140
138
  } satisfies AppendLogRequest;
141
139
 
142
140
  export const APPEND_LOG_REQUEST_CONFORMANCE: readonly AppendLogRequestConformanceVector[] = [
143
141
  { name: 'accepts a canonical post UUID', request: APPEND_LOG_REQUEST, accepts: true },
144
- { name: 'rejects an omitted post UUID', request: { message: APPEND_LOG_REQUEST.message }, accepts: false },
142
+ { name: 'rejects an omitted post UUID', request: { message: APPEND_LOG_REQUEST.message, to: 'broadcast' }, accepts: false },
145
143
  { name: 'rejects an invalid post UUID', request: { ...APPEND_LOG_REQUEST, post_id: 'not-a-uuid' }, accepts: false },
146
144
  { name: 'rejects unknown request fields', request: { ...APPEND_LOG_REQUEST, extra: true }, accepts: false },
145
+ {
146
+ name: 'rejects omitted addressing',
147
+ request: { post_id: APPEND_LOG_REQUEST.post_id, message: APPEND_LOG_REQUEST.message },
148
+ accepts: false,
149
+ },
150
+ {
151
+ name: 'accepts explicit broadcast addressing',
152
+ request: APPEND_LOG_REQUEST,
153
+ accepts: true,
154
+ },
155
+ {
156
+ name: 'accepts a non-empty recipient selector union branch',
157
+ request: { ...APPEND_LOG_REQUEST, to: ['Builder', 'id:3336cde1'] },
158
+ accepts: true,
159
+ },
160
+ {
161
+ name: 'rejects null addressing',
162
+ request: { ...APPEND_LOG_REQUEST, to: null },
163
+ accepts: false,
164
+ },
165
+ {
166
+ name: 'rejects an empty recipient selector array',
167
+ request: { ...APPEND_LOG_REQUEST, to: [] },
168
+ accepts: false,
169
+ },
170
+ {
171
+ name: 'rejects an arbitrary scalar audience',
172
+ request: { ...APPEND_LOG_REQUEST, to: 'all' },
173
+ accepts: false,
174
+ },
175
+ {
176
+ name: 'rejects retired visibility without falling open',
177
+ request: { ...APPEND_LOG_REQUEST, visibility: 'broadcast' },
178
+ accepts: false,
179
+ },
180
+ {
181
+ name: 'rejects retired recipient ids without falling open',
182
+ request: { ...APPEND_LOG_REQUEST, recipientDroneIds: ['00000000-0000-4000-8000-000000000204'] },
183
+ accepts: false,
184
+ },
147
185
  ];
148
186
 
149
187
  /** Runtime response vectors keep the append-log decoder aligned with its public type. */
@@ -214,13 +252,8 @@ export const APPEND_LOG_RESULT_CONFORMANCE: readonly AppendLogResultConformanceV
214
252
  accepts: false,
215
253
  },
216
254
  {
217
- name: 'rejects an invalid routing fell-open flag',
218
- response: { entry: APPEND_LOG_ENTRY, deduplicated: false, routing: { ...APPEND_LOG_ROUTING, fellOpen: 'false' } },
219
- accepts: false,
220
- },
221
- {
222
- name: 'rejects an invalid routing message',
223
- response: { entry: APPEND_LOG_ENTRY, deduplicated: false, routing: { ...APPEND_LOG_ROUTING, message: 7 } },
255
+ name: 'rejects legacy fall-open routing metadata',
256
+ response: { entry: APPEND_LOG_ENTRY, deduplicated: false, routing: { ...APPEND_LOG_ROUTING, fellOpen: false } },
224
257
  accepts: false,
225
258
  },
226
259
  {
@@ -883,7 +916,6 @@ export interface RoleDeleteConformanceVector {
883
916
  | 'default'
884
917
  | 'mandatory'
885
918
  | 'human-seat'
886
- | 'taxonomy-reference'
887
919
  | 'unknown';
888
920
  expected:
889
921
  | {
@@ -895,7 +927,7 @@ export interface RoleDeleteConformanceVector {
895
927
  }
896
928
  | {
897
929
  status: 409;
898
- error: 'ROLE_IN_USE' | 'DEFAULT_ROLE_REQUIRED' | 'ROLE_REQUIRED' | 'ROLE_REFERENCED';
930
+ error: 'ROLE_IN_USE' | 'DEFAULT_ROLE_REQUIRED' | 'ROLE_REQUIRED';
899
931
  mutation: 'none';
900
932
  message?: typeof ROLE_IN_USE_DELETE_MESSAGE;
901
933
  }
@@ -945,11 +977,6 @@ export const ROLE_DELETE_CONFORMANCE: readonly RoleDeleteConformanceVector[] = [
945
977
  fixture: 'human-seat',
946
978
  expected: { status: 409, error: 'ROLE_REQUIRED', mutation: 'none' },
947
979
  },
948
- {
949
- name: 'refuses a taxonomy default-recipient role',
950
- fixture: 'taxonomy-reference',
951
- expected: { status: 409, error: 'ROLE_REFERENCED', mutation: 'none' },
952
- },
953
980
  {
954
981
  name: 'hides an unknown or inaccessible role',
955
982
  fixture: 'unknown',
@@ -1012,6 +1039,79 @@ export const ROLE_RATIONALE_CONFORMANCE: readonly RoleRationaleConformanceVector
1012
1039
  },
1013
1040
  ];
1014
1041
 
1042
+ export interface AckStatusConformanceVector {
1043
+ name: string;
1044
+ fixture: 'missing-ack' | 'ack-and-claim' | 'unread-preserved' | 'unknown-entry';
1045
+ expected:
1046
+ | { status: 200; acknowledged_at: null; mutation: 'none' }
1047
+ | { status: 200; acknowledgements: 1; claims: 1; distinct: true; mutation: 'none' }
1048
+ | { status: 200; unread_entry_available: true; mutation: 'none' }
1049
+ | { status: 404; error: 'NOT_FOUND'; mutation: 'none' };
1050
+ }
1051
+
1052
+ /** Portable acknowledgement-status outcomes for one exact log entry. */
1053
+ export const ACK_STATUS_CONFORMANCE: readonly AckStatusConformanceVector[] = [
1054
+ {
1055
+ name: 'reports an intended recipient with no acknowledgement',
1056
+ fixture: 'missing-ack',
1057
+ expected: { status: 200, acknowledged_at: null, mutation: 'none' },
1058
+ },
1059
+ {
1060
+ name: 'keeps acknowledgements and claims in separate result collections',
1061
+ fixture: 'ack-and-claim',
1062
+ expected: { status: 200, acknowledgements: 1, claims: 1, distinct: true, mutation: 'none' },
1063
+ },
1064
+ {
1065
+ name: 'does not consume the queried entry from unread delivery',
1066
+ fixture: 'unread-preserved',
1067
+ expected: { status: 200, unread_entry_available: true, mutation: 'none' },
1068
+ },
1069
+ {
1070
+ name: 'returns not found for an unknown entry instead of missing acknowledgement state',
1071
+ fixture: 'unknown-entry',
1072
+ expected: { status: 404, error: 'NOT_FOUND', mutation: 'none' },
1073
+ },
1074
+ ];
1075
+
1076
+ export interface EntryQueryConformanceVector {
1077
+ name: string;
1078
+ fixture: 'full-uuid' | 'unique-prefix' | 'unknown' | 'ambiguous-prefix' | 'read-only';
1079
+ expected:
1080
+ | { status: 200; exact_entry: true; mutation: 'none' }
1081
+ | { status: 404; error: 'NOT_FOUND'; mutation: 'none' }
1082
+ | { status: 409; error: 'LOG_ENTRY_PREFIX_AMBIGUOUS'; mutation: 'none' }
1083
+ | { status: 200; unread_entry_available: true; mutation: 'none' };
1084
+ }
1085
+
1086
+ /** Portable exact-entry lookup outcomes for canonical UUID and short-prefix selectors. */
1087
+ export const ENTRY_QUERY_CONFORMANCE: readonly EntryQueryConformanceVector[] = [
1088
+ {
1089
+ name: 'resolves one canonical full UUID',
1090
+ fixture: 'full-uuid',
1091
+ expected: { status: 200, exact_entry: true, mutation: 'none' },
1092
+ },
1093
+ {
1094
+ name: 'resolves one unique eight-hex prefix',
1095
+ fixture: 'unique-prefix',
1096
+ expected: { status: 200, exact_entry: true, mutation: 'none' },
1097
+ },
1098
+ {
1099
+ name: 'hides an unknown or inaccessible selector',
1100
+ fixture: 'unknown',
1101
+ expected: { status: 404, error: 'NOT_FOUND', mutation: 'none' },
1102
+ },
1103
+ {
1104
+ name: 'refuses an ambiguous eight-hex prefix',
1105
+ fixture: 'ambiguous-prefix',
1106
+ expected: { status: 409, error: 'LOG_ENTRY_PREFIX_AMBIGUOUS', mutation: 'none' },
1107
+ },
1108
+ {
1109
+ name: 'does not advance unread or mutate acknowledgement, claim, log, or authority state',
1110
+ fixture: 'read-only',
1111
+ expected: { status: 200, unread_entry_available: true, mutation: 'none' },
1112
+ },
1113
+ ];
1114
+
1015
1115
  /** One canonical corpus consumed unchanged by shared, server, and client tests. */
1016
1116
  export const RUNTIME_METADATA_REPOSITORY_CONFORMANCE:
1017
1117
  readonly RuntimeMetadataRepositoryConformanceVector[] = [
@@ -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 = '0.13.1' as const;
16
+ export const SHARED_PACKAGE_VERSION = '1.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. */
@@ -590,7 +590,7 @@ export function decodeProtocolTagPreflight(value: unknown): ProtocolTagPreflight
590
590
  exactKeys(input, ['protocol_version'], ['protocol_version']);
591
591
  if (input.protocol_version !== PROTOCOL_VERSION) {
592
592
  throw new ProtocolContractError(
593
- 'This client requires protocol v10. The peer presents a different version. Update `borgmcp-server` and `borgmcp` to matching releases — server first, then client.',
593
+ 'This client requires protocol v12. The peer presents a different version. Update `borgmcp-server` and `borgmcp` to matching releases — server first, then client.',
594
594
  ErrorCode.UNSUPPORTED_PROTOCOL_VERSION,
595
595
  ['protocol_version'],
596
596
  );
@@ -1007,33 +1007,17 @@ export function decodeAppendLogRequest(value: unknown): import('./types.js').App
1007
1007
  const input = record(value);
1008
1008
  exactKeys(
1009
1009
  input,
1010
- ['post_id', 'message', 'visibility', 'recipientDroneIds', 'class', 'to', 'documents'],
1011
- ['post_id', 'message'],
1010
+ ['post_id', 'message', 'to', 'class', 'documents'],
1011
+ ['post_id', 'message', 'to'],
1012
1012
  );
1013
1013
  const output: import('./types.js').AppendLogRequest = {
1014
1014
  post_id: decodeUuid(input.post_id, ['post_id']),
1015
1015
  message: boundedString(input.message, 1, PROTOCOL_LIMIT_CEILINGS.max_log_message_bytes, ['message']),
1016
+ to: input.to === 'broadcast' ? 'broadcast' : decodeRecipientSelectors(input.to),
1016
1017
  };
1017
- if (input.visibility !== undefined) {
1018
- if (input.visibility !== 'broadcast' && input.visibility !== 'direct') {
1019
- fail('Invalid log visibility.', ['visibility']);
1020
- }
1021
- output.visibility = input.visibility;
1022
- }
1023
- if (input.recipientDroneIds !== undefined) {
1024
- if (!Array.isArray(input.recipientDroneIds) || input.recipientDroneIds.length === 0 || input.recipientDroneIds.length > 100) {
1025
- fail('Expected recipientDroneIds to contain 1-100 UUIDs.', ['recipientDroneIds']);
1026
- }
1027
- output.recipientDroneIds = input.recipientDroneIds.map((id, index) =>
1028
- decodeUuid(id, ['recipientDroneIds', index])
1029
- );
1030
- }
1031
1018
  if (input.class !== undefined) {
1032
1019
  output.class = boundedString(input.class, 1, 64, ['class']);
1033
1020
  }
1034
- if (input.to !== undefined) {
1035
- output.to = decodeStringArray(input.to, 'to', 100, 120);
1036
- }
1037
1021
  if (input.documents !== undefined) {
1038
1022
  output.documents = decodeStringArray(input.documents, 'documents', 100, 128)
1039
1023
  .map((id, index) => decodeOpaqueIdentifier(id, ['documents', index]));
@@ -1041,6 +1025,16 @@ export function decodeAppendLogRequest(value: unknown): import('./types.js').App
1041
1025
  return output;
1042
1026
  }
1043
1027
 
1028
+ function decodeRecipientSelectors(value: unknown): string[] {
1029
+ const selectors = decodeStringArray(value, 'to', 100, 120);
1030
+ for (const [index, selector] of selectors.entries()) {
1031
+ if (selector !== selector.trim() || /[\u0000-\u001f\u007f-\u009f]/.test(selector)) {
1032
+ fail('Recipient selectors must be trimmed and control-free.', ['to', index]);
1033
+ }
1034
+ }
1035
+ return selectors;
1036
+ }
1037
+
1044
1038
  function decodeStringArray(value: unknown, field: string, maxItems: number, maxLength: number): string[] {
1045
1039
  if (!Array.isArray(value) || value.length === 0 || value.length > maxItems) {
1046
1040
  fail(`Expected ${field} to contain 1-${maxItems} values.`, [field]);
@@ -16,6 +16,7 @@ import type {
16
16
  AppendLogResponse,
17
17
  Decision,
18
18
  EnrichedStreamEntry,
19
+ LogVisibility,
19
20
  RoutingEcho,
20
21
  } from './types.js';
21
22
  import { isLabelLine, parseRoleSections } from '../role-section.js';
@@ -46,6 +47,39 @@ export interface ReadLogResult {
46
47
  claims: ClaimRecord[];
47
48
  }
48
49
 
50
+ export interface EntryQueryRequest {
51
+ entry_id: string;
52
+ }
53
+
54
+ export interface EntryQueryResult {
55
+ entry: EnrichedStreamEntry;
56
+ }
57
+
58
+ export interface AckStatusRequest {
59
+ entry_id: string;
60
+ }
61
+
62
+ export interface AckStatusRecipient {
63
+ drone_id: string;
64
+ drone_label: string | null;
65
+ drone_role: string | null;
66
+ acknowledged_at: string | null;
67
+ }
68
+
69
+ export interface AckStatusClaim {
70
+ drone_id: string;
71
+ drone_label: string | null;
72
+ drone_role: string | null;
73
+ claimed_at: string;
74
+ }
75
+
76
+ export interface AckStatusResult {
77
+ entry_id: string;
78
+ visibility: LogVisibility;
79
+ recipients: AckStatusRecipient[];
80
+ claims: AckStatusClaim[];
81
+ }
82
+
49
83
  export interface ReassignDroneRequest {
50
84
  role_id: string;
51
85
  }
@@ -309,6 +343,106 @@ export function decodeReadLogRequestEnvelope(
309
343
  return decodeProtocolEnvelope(value, decodeReadLogRequest);
310
344
  }
311
345
 
346
+ export function decodeEntryQueryRequest(value: unknown): EntryQueryRequest {
347
+ const input = object(value);
348
+ exact(input, ['entry_id'], ['entry_id']);
349
+ const entryId = boundedString(input.entry_id, 'entry_id', 36);
350
+ if (/^[0-9a-f]{8}$/i.test(entryId)) return { entry_id: entryId.toLowerCase() };
351
+ return { entry_id: decodeUuid(entryId, ['entry_id']) };
352
+ }
353
+
354
+ export function decodeEntryQueryRequestEnvelope(
355
+ value: unknown,
356
+ ): ProtocolEnvelope<EntryQueryRequest> {
357
+ return decodeProtocolEnvelope(value, decodeEntryQueryRequest);
358
+ }
359
+
360
+ export function decodeEntryQueryResult(value: unknown): EntryQueryResult {
361
+ const input = object(value);
362
+ exact(input, ['entry'], ['entry']);
363
+ return { entry: decodeEnrichedStreamEntry(input.entry) };
364
+ }
365
+
366
+ export function decodeEntryQueryResultEnvelope(
367
+ value: unknown,
368
+ ): ProtocolEnvelope<EntryQueryResult> {
369
+ return decodeProtocolEnvelope(value, decodeEntryQueryResult);
370
+ }
371
+
372
+ export function decodeAckStatusRequest(value: unknown): AckStatusRequest {
373
+ const input = object(value);
374
+ exact(input, ['entry_id'], ['entry_id']);
375
+ return { entry_id: decodeUuid(input.entry_id, ['entry_id']) };
376
+ }
377
+
378
+ export function decodeAckStatusRequestEnvelope(
379
+ value: unknown,
380
+ ): ProtocolEnvelope<AckStatusRequest> {
381
+ return decodeProtocolEnvelope(value, decodeAckStatusRequest);
382
+ }
383
+
384
+ function decodeAckStatusRecipient(value: unknown): AckStatusRecipient {
385
+ const input = object(value);
386
+ exact(
387
+ input,
388
+ ['drone_id', 'drone_label', 'drone_role', 'acknowledged_at'],
389
+ ['drone_id', 'drone_label', 'drone_role', 'acknowledged_at'],
390
+ );
391
+ return {
392
+ drone_id: decodeUuid(input.drone_id, ['drone_id']),
393
+ drone_label: nullableString(input.drone_label, 'drone_label', 120),
394
+ drone_role: nullableString(input.drone_role, 'drone_role', 120),
395
+ acknowledged_at: input.acknowledged_at === null
396
+ ? null
397
+ : decodeCanonicalTimestamp(input.acknowledged_at, ['acknowledged_at']),
398
+ };
399
+ }
400
+
401
+ function decodeAckStatusClaim(value: unknown): AckStatusClaim {
402
+ const input = object(value);
403
+ exact(
404
+ input,
405
+ ['drone_id', 'drone_label', 'drone_role', 'claimed_at'],
406
+ ['drone_id', 'drone_label', 'drone_role', 'claimed_at'],
407
+ );
408
+ return {
409
+ drone_id: decodeUuid(input.drone_id, ['drone_id']),
410
+ drone_label: nullableString(input.drone_label, 'drone_label', 120),
411
+ drone_role: nullableString(input.drone_role, 'drone_role', 120),
412
+ claimed_at: decodeCanonicalTimestamp(input.claimed_at, ['claimed_at']),
413
+ };
414
+ }
415
+
416
+ export function decodeAckStatusResult(value: unknown): AckStatusResult {
417
+ const input = object(value);
418
+ exact(
419
+ input,
420
+ ['entry_id', 'visibility', 'recipients', 'claims'],
421
+ ['entry_id', 'visibility', 'recipients', 'claims'],
422
+ );
423
+ if (input.visibility !== 'broadcast' && input.visibility !== 'direct') {
424
+ throw new ProtocolContractError('Invalid acknowledgement-status visibility.');
425
+ }
426
+ if (!Array.isArray(input.recipients) || input.recipients.length > 500) {
427
+ throw new ProtocolContractError('Invalid acknowledgement-status recipients.');
428
+ }
429
+ if (!Array.isArray(input.claims) || input.claims.length > 500) {
430
+ throw new ProtocolContractError('Invalid acknowledgement-status claims.');
431
+ }
432
+ return {
433
+ entry_id: decodeUuid(input.entry_id, ['entry_id']),
434
+ visibility: input.visibility,
435
+ recipients: input.recipients.map(decodeAckStatusRecipient),
436
+ claims: input.claims.map(decodeAckStatusClaim),
437
+ };
438
+ }
439
+
440
+ export function decodeAckStatusResultEnvelope(
441
+ value: unknown,
442
+ ): ProtocolEnvelope<AckStatusResult> {
443
+ return decodeProtocolEnvelope(value, decodeAckStatusResult);
444
+ }
445
+
312
446
  function decodeClaimRecord(value: unknown): ClaimRecord {
313
447
  const input = object(value);
314
448
  exact(
@@ -345,22 +479,17 @@ function decodeRoutingEcho(value: unknown): RoutingEcho {
345
479
  const input = object(value);
346
480
  exact(
347
481
  input,
348
- ['class', 'recipients', 'fellOpen', 'message'],
349
- ['class', 'recipients', 'fellOpen', 'message'],
482
+ ['class', 'recipients'],
483
+ ['class', 'recipients'],
350
484
  );
351
485
  if (!Array.isArray(input.recipients) || input.recipients.length > 100) {
352
486
  throw new ProtocolContractError('Invalid routing recipient list.');
353
487
  }
354
- if (typeof input.fellOpen !== 'boolean') {
355
- throw new ProtocolContractError('Invalid routing fell-open flag.');
356
- }
357
488
  return {
358
489
  class: nullableString(input.class, 'routing.class', 64),
359
490
  recipients: input.recipients.map((recipient) =>
360
491
  boundedString(recipient, 'routing.recipients', 120)
361
492
  ),
362
- fellOpen: input.fellOpen,
363
- message: nullableString(input.message, 'routing.message', 512),
364
493
  };
365
494
  }
366
495
 
@@ -15,10 +15,10 @@ export enum ErrorCode {
15
15
  REPOSITORY_ALREADY_ASSOCIATED = 'REPOSITORY_ALREADY_ASSOCIATED',
16
16
  CUBE_ALREADY_ASSOCIATED = 'CUBE_ALREADY_ASSOCIATED',
17
17
  POST_ID_CONFLICT = 'POST_ID_CONFLICT',
18
+ LOG_ENTRY_PREFIX_AMBIGUOUS = 'LOG_ENTRY_PREFIX_AMBIGUOUS',
18
19
  ROLE_IN_USE = 'ROLE_IN_USE',
19
20
  DEFAULT_ROLE_REQUIRED = 'DEFAULT_ROLE_REQUIRED',
20
21
  ROLE_REQUIRED = 'ROLE_REQUIRED',
21
- ROLE_REFERENCED = 'ROLE_REFERENCED',
22
22
  ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
23
23
  ROLE_SECTION_NOT_FOUND = 'ROLE_SECTION_NOT_FOUND',
24
24
  ROLE_HAS_FROZEN_DRONES = 'ROLE_HAS_FROZEN_DRONES',
@@ -168,17 +168,13 @@ export interface ReadLogResponse {
168
168
  export interface RoutingEcho {
169
169
  class: string | null;
170
170
  recipients: string[];
171
- fellOpen: boolean;
172
- message: string | null;
173
171
  }
174
172
 
175
173
  export interface AppendLogRequest {
176
174
  post_id: string;
177
175
  message: string;
178
- visibility?: LogVisibility;
179
- recipientDroneIds?: string[];
176
+ to: 'broadcast' | string[];
180
177
  class?: string;
181
- to?: string[];
182
178
  documents?: string[];
183
179
  }
184
180
 
@@ -1,4 +1,4 @@
1
- /** Current Borg coordination protocol generation. Clean-slate v10. */
2
- export const PROTOCOL_VERSION = '10' as const;
1
+ /** Current Borg coordination protocol generation. Clean-slate v12. */
2
+ export const PROTOCOL_VERSION = '12' as const;
3
3
 
4
4
  export type ProtocolVersion = typeof PROTOCOL_VERSION;