borgmcp-shared 0.14.0 → 1.0.1

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 +9 -2
  4. package/dist/conformance/adapter.d.ts.map +1 -1
  5. package/dist/conformance/adapter.js +128 -43
  6. package/dist/conformance/adapter.js.map +1 -1
  7. package/dist/conformance/index.d.ts +25 -3
  8. package/dist/conformance/index.d.ts.map +1 -1
  9. package/dist/conformance/index.js +73 -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 +10 -0
  16. package/dist/protocol/coordination.d.ts.map +1 -1
  17. package/dist/protocol/coordination.js +20 -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 +7 -79
  30. package/dist/templates.js.map +1 -1
  31. package/docs/compatibility.md +9 -0
  32. package/docs/release-records.json +28 -0
  33. package/docs/releases/1.0.0.md +11 -0
  34. package/docs/releases/1.0.1.md +5 -0
  35. package/docs/template-lifecycle.md +1 -1
  36. package/package.json +1 -1
  37. package/src/conformance/adapter.ts +232 -52
  38. package/src/conformance/index.ts +88 -22
  39. package/src/protocol/contract.ts +15 -21
  40. package/src/protocol/coordination.ts +36 -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 +7 -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',
@@ -1046,6 +1073,45 @@ export const ACK_STATUS_CONFORMANCE: readonly AckStatusConformanceVector[] = [
1046
1073
  },
1047
1074
  ];
1048
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
+
1049
1115
  /** One canonical corpus consumed unchanged by shared, server, and client tests. */
1050
1116
  export const RUNTIME_METADATA_REPOSITORY_CONFORMANCE:
1051
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.14.0' as const;
16
+ export const SHARED_PACKAGE_VERSION = '1.0.1' 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 v11. The peer presents a different version. Update `borgmcp-server` and `borgmcp` to matching releases — server first, then client.',
593
+ `This client requires protocol v${PROTOCOL_VERSION}. 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]);
@@ -47,6 +47,14 @@ export interface ReadLogResult {
47
47
  claims: ClaimRecord[];
48
48
  }
49
49
 
50
+ export interface EntryQueryRequest {
51
+ entry_id: string;
52
+ }
53
+
54
+ export interface EntryQueryResult {
55
+ entry: EnrichedStreamEntry;
56
+ }
57
+
50
58
  export interface AckStatusRequest {
51
59
  entry_id: string;
52
60
  }
@@ -335,6 +343,32 @@ export function decodeReadLogRequestEnvelope(
335
343
  return decodeProtocolEnvelope(value, decodeReadLogRequest);
336
344
  }
337
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
+
338
372
  export function decodeAckStatusRequest(value: unknown): AckStatusRequest {
339
373
  const input = object(value);
340
374
  exact(input, ['entry_id'], ['entry_id']);
@@ -445,22 +479,17 @@ function decodeRoutingEcho(value: unknown): RoutingEcho {
445
479
  const input = object(value);
446
480
  exact(
447
481
  input,
448
- ['class', 'recipients', 'fellOpen', 'message'],
449
- ['class', 'recipients', 'fellOpen', 'message'],
482
+ ['class', 'recipients'],
483
+ ['class', 'recipients'],
450
484
  );
451
485
  if (!Array.isArray(input.recipients) || input.recipients.length > 100) {
452
486
  throw new ProtocolContractError('Invalid routing recipient list.');
453
487
  }
454
- if (typeof input.fellOpen !== 'boolean') {
455
- throw new ProtocolContractError('Invalid routing fell-open flag.');
456
- }
457
488
  return {
458
489
  class: nullableString(input.class, 'routing.class', 64),
459
490
  recipients: input.recipients.map((recipient) =>
460
491
  boundedString(recipient, 'routing.recipients', 120)
461
492
  ),
462
- fellOpen: input.fellOpen,
463
- message: nullableString(input.message, 'routing.message', 512),
464
493
  };
465
494
  }
466
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 v11. */
2
- export const PROTOCOL_VERSION = '11' 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;
package/src/templates.ts CHANGED
@@ -21,8 +21,6 @@ export interface TemplateRole {
21
21
  export interface MessageTaxonomyClass {
22
22
  class: string;
23
23
  prefixes?: string[];
24
- routing: 'broadcast' | 'directed';
25
- default_to?: string[];
26
24
  lifecycle?: 'dispatch' | 'completion';
27
25
  }
28
26
 
@@ -156,7 +154,7 @@ Same-repository workflow policy:
156
154
  - Run \`git remote get-url origin\` to determine whether a hosted origin exists.
157
155
  - When that command succeeds, publish the branch with \`git push -u origin <branch>\`; the branch is REVIEW-READY only after that push and exact remote-head verification.
158
156
  - When that command fails because no origin exists, omit the push; the work is REVIEW-READY when its exact commit SHA is available through the project review mechanism.
159
- - After every merge to the protected or main branch, broadcast the merge SHA.`;
157
+ - After every merge to the protected or main branch, post the merge SHA with \`to: "broadcast"\`.`;
160
158
 
161
159
  export const UNIVERSAL_SAFETY_DISCIPLINES = [WAKE_PATH_MONITOR_DISCIPLINE];
162
160
 
@@ -179,9 +177,10 @@ Drone addressing:
179
177
  const STRUCTURED_MESSAGE_ROUTING_DISCIPLINE = `
180
178
 
181
179
  Structured message routing:
182
- - Pass the intended recipient through borg_log's structured \`to:\` parameter for every directed message.
180
+ - Every borg_log call must set structured \`to:\` to either \`"broadcast"\` or a non-empty recipient selector array.
181
+ - Use \`to: "broadcast"\` only when every cube member is the intended audience; otherwise name every intended recipient explicitly.
183
182
  - Naming a recipient inside the message text does not route it.
184
- - The default is broadcast. Without \`to:\`, a matching directed class, or explicit direct visibility, the unrouted message broadcasts to every seat.`;
183
+ - Message classes and prefixes classify lifecycle signals only; they never choose recipients or provide a default audience.`;
185
184
 
186
185
  const DIRECTED_DISCUSSION_DISCIPLINE = `
187
186
  - Use QUESTION, ANSWER, or HEADS-UP with \`to:\` for directed discussion outside the role's terminal workflow signals.`;
@@ -215,82 +214,57 @@ const SOFTWARE_DEV_TAXONOMY: MessageTaxonomy = [
215
214
  {
216
215
  class: 'status-claim',
217
216
  prefixes: ['STARTING', 'PROGRESS', 'ACK', 'PONG', 'PUSHING'],
218
- routing: 'directed',
219
- default_to: ['coordinator', 'queen'],
220
217
  },
221
218
  {
222
219
  class: 'completion-status',
223
220
  prefixes: ['DONE'],
224
- routing: 'directed',
225
- default_to: ['coordinator', 'queen'],
226
221
  lifecycle: 'completion',
227
222
  },
228
223
  {
229
224
  class: 'review-request',
230
225
  prefixes: ['REVIEW-READY'],
231
- routing: 'directed',
232
- default_to: ['coordinator', 'queen'],
233
226
  },
234
227
  {
235
228
  class: 'review-feedback',
236
229
  prefixes: ['REVIEW-FEEDBACK', 'RQ-FEEDBACK', 'SECURITY-FEEDBACK', 'PD-FEEDBACK', 'PS-FEEDBACK'],
237
- routing: 'directed',
238
- default_to: ['coordinator', 'queen'],
239
230
  },
240
231
  {
241
232
  class: 'completion-gate',
242
233
  prefixes: ['REVIEW-APPROVED', 'RQ-APPROVED', 'SECURITY-APPROVED', 'PD-APPROVED', 'PS-APPROVED'],
243
- routing: 'directed',
244
- default_to: ['coordinator', 'queen'],
245
234
  lifecycle: 'completion',
246
235
  },
247
236
  {
248
237
  class: 'blocked-signal',
249
238
  prefixes: ['BLOCKED'],
250
- routing: 'directed',
251
- default_to: ['coordinator', 'queen'],
252
239
  },
253
240
  {
254
241
  class: 'dispatch-routing',
255
242
  prefixes: ['START NOW', 'RESUME NOW', 'REVIEW NOW', 'HOLD'],
256
- routing: 'directed',
257
- default_to: ['coordinator', 'queen'],
258
243
  lifecycle: 'dispatch',
259
244
  },
260
245
  {
261
246
  class: 'ping',
262
247
  prefixes: ['PING'],
263
- routing: 'directed',
264
- default_to: ['coordinator', 'queen'],
265
248
  },
266
249
  {
267
250
  class: 'peer-question',
268
251
  prefixes: ['QUESTION', 'ASK'],
269
- routing: 'directed',
270
- default_to: ['coordinator', 'queen'],
271
252
  },
272
253
  {
273
254
  class: 'peer-answer',
274
255
  prefixes: ['ANSWER'],
275
- routing: 'directed',
276
- default_to: ['coordinator', 'queen'],
277
256
  },
278
257
  {
279
258
  class: 'peer-heads-up',
280
259
  prefixes: ['HEADS-UP'],
281
- routing: 'directed',
282
- default_to: ['coordinator', 'queen'],
283
260
  },
284
261
  {
285
262
  class: 'finding',
286
263
  prefixes: ['PROPOSAL'],
287
- routing: 'directed',
288
- default_to: ['coordinator', 'queen'],
289
264
  },
290
265
  {
291
266
  class: 'cube-wide',
292
267
  prefixes: ['DECISION', 'HALT', 'MERGED'],
293
- routing: 'broadcast',
294
268
  },
295
269
  ];
296
270
 
@@ -327,7 +301,7 @@ Communication:
327
301
  - Surface decisions, blockers, asks, and material evidence in the human conversation, not only the cube log.
328
302
  - Distinguish read-only findings, proposals, completed actions, and actions awaiting authority.
329
303
  - Send START NOW, RESUME NOW, REVIEW NOW, and HOLD with \`to:\` to the named implementer or reviewer. Use \`to:\` for every later directed transition.
330
- - Send PING with \`to:\` only for a directed liveness check. Use DECISION or HALT only for an intentional cube-wide human-seat message. After an authorized merge, broadcast MERGED with the exact merge SHA.
304
+ - Send PING with \`to:\` only for a directed liveness check. Use DECISION or HALT with \`to: "broadcast"\` only for an intentional cube-wide human-seat message. After an authorized merge, post MERGED with the exact merge SHA and \`to: "broadcast"\`.
331
305
  - Keep the primary playbook operational and concise. Delete obsolete, redundant, historical, cautionary, and example-heavy prose; do not relocate it into new runbooks, decisions, contracts, rationale, or case-study archives unless it has a current operational consumer.
332
306
 
333
307
  Builders implement; reviewers review; you coordinate. Integrate only when authorized.${COORDINATOR_FINDING_DISPATCH_DISCIPLINE}${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${GIT_OPERATIONAL_DISCIPLINE_COORDINATOR}${PUSH_DISCIPLINE_COORDINATOR}${DRONE_ADDRESSING_CONVENTION}${STRUCTURED_MESSAGE_ROUTING_DISCIPLINE}${DIRECTED_DISCUSSION_DISCIPLINE}${RECEIPT_AND_LIVENESS_DISCIPLINE}${OPERATOR_CONTROLLED_OWNERSHIP_DISCIPLINE}`;
@@ -508,76 +482,53 @@ const STARTER_TAXONOMY: MessageTaxonomy = [
508
482
  {
509
483
  class: 'status-claim',
510
484
  prefixes: ['STARTING', 'PROGRESS', 'ACK', 'PONG'],
511
- routing: 'directed',
512
- default_to: ['coordinator', 'queen'],
513
485
  },
514
486
  {
515
487
  class: 'completion-status',
516
488
  prefixes: ['DONE'],
517
- routing: 'directed',
518
- default_to: ['coordinator', 'queen'],
519
489
  lifecycle: 'completion',
520
490
  },
521
491
  {
522
492
  class: 'review-request',
523
493
  prefixes: ['REVIEW-READY'],
524
- routing: 'directed',
525
- default_to: ['coordinator', 'queen'],
526
494
  },
527
495
  {
528
496
  class: 'review-feedback',
529
497
  prefixes: ['FEEDBACK'],
530
- routing: 'directed',
531
- default_to: ['coordinator', 'queen'],
532
498
  },
533
499
  {
534
500
  class: 'completion-gate',
535
501
  prefixes: ['APPROVED'],
536
- routing: 'directed',
537
- default_to: ['coordinator', 'queen'],
538
502
  lifecycle: 'completion',
539
503
  },
540
504
  {
541
505
  class: 'blocked-signal',
542
506
  prefixes: ['BLOCKED'],
543
- routing: 'directed',
544
- default_to: ['coordinator', 'queen'],
545
507
  },
546
508
  {
547
509
  class: 'dispatch-routing',
548
510
  prefixes: ['START NOW', 'RESUME NOW', 'REVIEW NOW', 'HOLD'],
549
- routing: 'directed',
550
- default_to: ['coordinator', 'queen'],
551
511
  lifecycle: 'dispatch',
552
512
  },
553
513
  {
554
514
  class: 'ping',
555
515
  prefixes: ['PING'],
556
- routing: 'directed',
557
- default_to: ['coordinator', 'queen'],
558
516
  },
559
517
  {
560
518
  class: 'peer-question',
561
519
  prefixes: ['QUESTION', 'ASK'],
562
- routing: 'directed',
563
- default_to: ['coordinator', 'queen'],
564
520
  },
565
521
  {
566
522
  class: 'peer-answer',
567
523
  prefixes: ['ANSWER'],
568
- routing: 'directed',
569
- default_to: ['coordinator', 'queen'],
570
524
  },
571
525
  {
572
526
  class: 'peer-heads-up',
573
527
  prefixes: ['HEADS-UP'],
574
- routing: 'directed',
575
- default_to: ['coordinator', 'queen'],
576
528
  },
577
529
  {
578
530
  class: 'cube-wide',
579
531
  prefixes: ['DECISION', 'HALT'],
580
- routing: 'broadcast',
581
532
  },
582
533
  ];
583
534
 
@@ -606,7 +557,7 @@ const STARTER: Template = {
606
557
  - Questions, findings, proposals, open queues, and spare capacity do not authorize new work.
607
558
  - Route completed work to the Reviewer only when review is required.
608
559
  - Send START NOW, RESUME NOW, REVIEW NOW, and HOLD with \`to:\` to the named Worker or Reviewer.
609
- - Send PING with \`to:\` only for a directed liveness check. Use DECISION or HALT only for an intentional cube-wide human-seat message.
560
+ - Send PING with \`to:\` only for a directed liveness check. Use DECISION or HALT with \`to: "broadcast"\` only for an intentional cube-wide human-seat message.
610
561
  - Ask the human before rescoping, abandoning, waiving, merging, shipping, publishing, or taking an irreversible action unless already delegated.
611
562
  - Waiting is valid when work is complete, blocked, under review, or awaiting authority.${COORDINATOR_FINDING_DISPATCH_DISCIPLINE}${ANTI_PASSIVE_STANDING_DISCIPLINE}${DRONE_ADDRESSING_CONVENTION}${STRUCTURED_MESSAGE_ROUTING_DISCIPLINE}${DIRECTED_DISCUSSION_DISCIPLINE}${RECEIPT_AND_LIVENESS_DISCIPLINE}${OPERATOR_CONTROLLED_OWNERSHIP_DISCIPLINE}`,
612
563
  },
@@ -642,83 +593,58 @@ const LOCAL_MODEL_TAXONOMY: MessageTaxonomy = [
642
593
  {
643
594
  class: 'executor-echo',
644
595
  prefixes: ['PACKET-ECHO'],
645
- routing: 'directed',
646
- default_to: ['shaper'],
647
596
  },
648
597
  {
649
598
  class: 'executor-refusal',
650
599
  prefixes: ['SPEC-GAP'],
651
- routing: 'directed',
652
- default_to: ['shaper'],
653
600
  },
654
601
  {
655
602
  class: 'executor-completion',
656
603
  prefixes: ['PACKET-DONE'],
657
- routing: 'directed',
658
- default_to: ['shaper'],
659
604
  lifecycle: 'completion',
660
605
  },
661
606
  {
662
607
  class: 'packet-dispatch',
663
608
  prefixes: ['EXECUTE PACKET'],
664
- routing: 'directed',
665
- default_to: ['executor'],
666
609
  lifecycle: 'dispatch',
667
610
  },
668
611
  {
669
612
  class: 'packet-verdict',
670
613
  prefixes: ['ACCEPT', 'REJECT'],
671
- routing: 'directed',
672
- default_to: ['executor'],
673
614
  },
674
615
  {
675
616
  class: 'blocked-signal',
676
617
  prefixes: ['BLOCKED'],
677
- routing: 'directed',
678
- default_to: ['director', 'queen'],
679
618
  },
680
619
  {
681
620
  class: 'review-request',
682
621
  prefixes: ['REVIEW-READY'],
683
- routing: 'directed',
684
- default_to: ['director', 'queen'],
685
622
  },
686
623
  {
687
624
  class: 'director-dispatch',
688
625
  prefixes: ['DISPATCH', 'HOLD'],
689
- routing: 'directed',
690
- default_to: ['shaper'],
691
626
  lifecycle: 'dispatch',
692
627
  },
693
628
  {
694
629
  class: 'director-approval',
695
630
  prefixes: ['APPROVED'],
696
- routing: 'directed',
697
- default_to: ['shaper'],
698
631
  lifecycle: 'completion',
699
632
  },
700
633
  {
701
634
  class: 'peer-question',
702
635
  prefixes: ['QUESTION'],
703
- routing: 'directed',
704
- default_to: ['director', 'queen'],
705
636
  },
706
637
  {
707
638
  class: 'peer-answer',
708
639
  prefixes: ['ANSWER'],
709
- routing: 'directed',
710
- default_to: ['director', 'queen'],
711
640
  },
712
641
  {
713
642
  class: 'peer-heads-up',
714
643
  prefixes: ['HEADS-UP'],
715
- routing: 'directed',
716
- default_to: ['director', 'queen'],
717
644
  },
718
645
  {
719
646
  class: 'cube-wide',
720
647
  prefixes: ['DECISION'],
721
- routing: 'broadcast',
722
648
  },
723
649
  ];
724
650
 
@@ -754,7 +680,7 @@ Direction and verification:
754
680
  - Never approve work you authored. If the Shaper implemented an unconvertible item, you are its independent verifier.
755
681
 
756
682
  Continuity:
757
- - Send DISPATCH, HOLD, APPROVED, QUESTION, ANSWER, and HEADS-UP with \`to:\` to the Shaper. Use DECISION only when the message is intentionally cube-wide.
683
+ - Send DISPATCH, HOLD, APPROVED, QUESTION, ANSWER, and HEADS-UP with \`to:\` to the Shaper. Use DECISION with \`to: "broadcast"\` only when the message is intentionally cube-wide.
758
684
  - DISPATCH, HOLD, and DECISION are not completion when they leave an authorized follow-on action.
759
685
  - After answering an interruption, resume any Director action you can advance in the same turn.
760
686
  - Waiting is valid only when no routed Director action or active outcome remains, or while a named Shaper/reviewer/human decision is outstanding and you have no independent action.