borgmcp-shared 0.7.1 → 0.8.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.
- package/README.md +8 -33
- package/RELEASES.md +4 -0
- package/dist/conformance/adapter.d.ts +3 -0
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +29 -2
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +41 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +14 -1
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +181 -1
- package/dist/protocol/contract.js.map +1 -1
- package/dist/templates.d.ts +3 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +31 -11
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +19 -5
- package/docs/enrollment.md +5 -5
- package/docs/release-records.json +16 -0
- package/docs/releasing.md +43 -3
- package/package.json +4 -2
- package/src/conformance/adapter.ts +27 -0
- package/src/conformance/index.ts +49 -1
- package/src/protocol/contract.ts +219 -1
- package/src/templates.ts +34 -11
package/src/protocol/contract.ts
CHANGED
|
@@ -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.
|
|
16
|
+
export const SHARED_PACKAGE_VERSION = '0.8.1' as const;
|
|
17
17
|
|
|
18
18
|
export const HEALTH_PATH = '/healthz' as const;
|
|
19
19
|
export const PROTOCOL_INFO_PATH = '/api/protocol' as const;
|
|
@@ -125,6 +125,23 @@ export interface EnrollmentExchangeRequest {
|
|
|
125
125
|
client_name?: string;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
export const INVITATION_ARTIFACT_VERSION = 2 as const;
|
|
129
|
+
export type InvitationAuthority = 'client' | 'owner';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The single opaque value transported between machines for enrollment. The
|
|
133
|
+
* integrity field is produced and verified by the issuing implementation; the
|
|
134
|
+
* shared codec preserves it as a bounded canonical field.
|
|
135
|
+
*/
|
|
136
|
+
export interface InvitationArtifact {
|
|
137
|
+
version: typeof INVITATION_ARTIFACT_VERSION;
|
|
138
|
+
endpoint: string;
|
|
139
|
+
ca_spki_sha256: string;
|
|
140
|
+
authority: InvitationAuthority;
|
|
141
|
+
secret: string;
|
|
142
|
+
integrity: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
128
145
|
export const SERVER_CAPABILITIES = ['create_cube'] as const;
|
|
129
146
|
export type ServerCapability = (typeof SERVER_CAPABILITIES)[number];
|
|
130
147
|
|
|
@@ -315,6 +332,207 @@ function opaqueToken(value: unknown, path: readonly (string | number)[]): string
|
|
|
315
332
|
return token;
|
|
316
333
|
}
|
|
317
334
|
|
|
335
|
+
const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
336
|
+
const INVITATION_MAGIC = 'B2';
|
|
337
|
+
const INVITATION_LENGTH_HEX_DIGITS = 3;
|
|
338
|
+
|
|
339
|
+
function encodeBase64UrlAscii(value: string): string {
|
|
340
|
+
let output = '';
|
|
341
|
+
for (let index = 0; index < value.length; index += 3) {
|
|
342
|
+
const first = value.charCodeAt(index);
|
|
343
|
+
const hasSecond = index + 1 < value.length;
|
|
344
|
+
const hasThird = index + 2 < value.length;
|
|
345
|
+
const second = hasSecond ? value.charCodeAt(index + 1) : 0;
|
|
346
|
+
const third = hasThird ? value.charCodeAt(index + 2) : 0;
|
|
347
|
+
output += BASE64URL_ALPHABET[first >> 2];
|
|
348
|
+
output += BASE64URL_ALPHABET[((first & 0x03) << 4) | (second >> 4)];
|
|
349
|
+
if (hasSecond) output += BASE64URL_ALPHABET[((second & 0x0f) << 2) | (third >> 6)];
|
|
350
|
+
if (hasThird) output += BASE64URL_ALPHABET[third & 0x3f];
|
|
351
|
+
}
|
|
352
|
+
return output;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function decodeBase64UrlAscii(value: string, path: readonly (string | number)[]): string {
|
|
356
|
+
if (!/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
|
|
357
|
+
fail('Expected an unpadded base64url value.', path);
|
|
358
|
+
}
|
|
359
|
+
let output = '';
|
|
360
|
+
for (let index = 0; index < value.length; index += 4) {
|
|
361
|
+
const first = BASE64URL_ALPHABET.indexOf(value[index]);
|
|
362
|
+
const second = BASE64URL_ALPHABET.indexOf(value[index + 1]);
|
|
363
|
+
const third = index + 2 < value.length ? BASE64URL_ALPHABET.indexOf(value[index + 2]) : 0;
|
|
364
|
+
const fourth = index + 3 < value.length ? BASE64URL_ALPHABET.indexOf(value[index + 3]) : 0;
|
|
365
|
+
if (first < 0 || second < 0 || (index + 2 < value.length && third < 0) ||
|
|
366
|
+
(index + 3 < value.length && fourth < 0)) {
|
|
367
|
+
fail('Expected an unpadded base64url value.', path);
|
|
368
|
+
}
|
|
369
|
+
const bytes = [
|
|
370
|
+
(first << 2) | (second >> 4),
|
|
371
|
+
((second & 0x0f) << 4) | (third >> 2),
|
|
372
|
+
((third & 0x03) << 6) | fourth,
|
|
373
|
+
];
|
|
374
|
+
const byteCount = Math.min(3, value.length - index - 1);
|
|
375
|
+
for (let byteIndex = 0; byteIndex < byteCount; byteIndex++) {
|
|
376
|
+
if (bytes[byteIndex] > 0x7f) fail('Invitation fields must contain ASCII bytes.', path);
|
|
377
|
+
output += String.fromCharCode(bytes[byteIndex]);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (encodeBase64UrlAscii(output) !== value) {
|
|
381
|
+
fail('Expected canonical unpadded base64url encoding.', path);
|
|
382
|
+
}
|
|
383
|
+
return output;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function encodedBase64UrlLength(byteLength: number): number {
|
|
387
|
+
const remainder = byteLength % 3;
|
|
388
|
+
return Math.floor(byteLength / 3) * 4 + (remainder === 0 ? 0 : remainder + 1);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function invitationFieldLength(value: string, path: readonly (string | number)[]): string {
|
|
392
|
+
const length = value.length;
|
|
393
|
+
if (length > 0xfff) fail('Invitation field is too long.', path);
|
|
394
|
+
return length.toString(16).padStart(INVITATION_LENGTH_HEX_DIGITS, '0');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function decodeInvitationField(
|
|
398
|
+
payload: string,
|
|
399
|
+
cursor: { value: number },
|
|
400
|
+
path: readonly (string | number)[],
|
|
401
|
+
): string {
|
|
402
|
+
const lengthText = payload.slice(cursor.value, cursor.value + INVITATION_LENGTH_HEX_DIGITS);
|
|
403
|
+
if (!/^[0-9a-f]{3}$/.test(lengthText)) fail('Invitation field length is invalid.', path);
|
|
404
|
+
cursor.value += INVITATION_LENGTH_HEX_DIGITS;
|
|
405
|
+
const byteLength = Number.parseInt(lengthText, 16);
|
|
406
|
+
const encodedLength = encodedBase64UrlLength(byteLength);
|
|
407
|
+
const encoded = payload.slice(cursor.value, cursor.value + encodedLength);
|
|
408
|
+
if (encoded.length !== encodedLength) fail('Invitation field is truncated.', path);
|
|
409
|
+
cursor.value += encodedLength;
|
|
410
|
+
const decoded = decodeBase64UrlAscii(encoded, path);
|
|
411
|
+
if (decoded.length !== byteLength) fail('Invitation field length does not match its value.', path);
|
|
412
|
+
return decoded;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function canonicalInvitationEndpoint(value: unknown, path: readonly (string | number)[]): string {
|
|
416
|
+
const endpoint = boundedString(value, 1, 512, path);
|
|
417
|
+
type ParsedUrl = {
|
|
418
|
+
protocol: string;
|
|
419
|
+
origin: string;
|
|
420
|
+
hostname: string;
|
|
421
|
+
port: string;
|
|
422
|
+
pathname: string;
|
|
423
|
+
search: string;
|
|
424
|
+
hash: string;
|
|
425
|
+
username: string;
|
|
426
|
+
password: string;
|
|
427
|
+
};
|
|
428
|
+
const UrlParser = (globalThis as unknown as { URL?: new (value: string) => ParsedUrl }).URL;
|
|
429
|
+
if (UrlParser === undefined) fail('Invitation endpoint URL parsing is unavailable.', path);
|
|
430
|
+
let parsed: ParsedUrl;
|
|
431
|
+
try {
|
|
432
|
+
parsed = new UrlParser(endpoint);
|
|
433
|
+
} catch {
|
|
434
|
+
fail('Invitation endpoint must be a valid URL.', path);
|
|
435
|
+
}
|
|
436
|
+
if (parsed.protocol !== 'https:' || !parsed.hostname || parsed.username || parsed.password ||
|
|
437
|
+
parsed.pathname !== '/' || parsed.search || parsed.hash || endpoint !== parsed.origin ||
|
|
438
|
+
(parsed.port !== '' && (Number.parseInt(parsed.port, 10) < 1 || Number.parseInt(parsed.port, 10) > 65_535))) {
|
|
439
|
+
fail('Invitation endpoint must be a canonical HTTPS origin.', path);
|
|
440
|
+
}
|
|
441
|
+
return endpoint;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function validateInvitationArtifact(value: unknown): InvitationArtifact {
|
|
445
|
+
const input = record(value);
|
|
446
|
+
exactKeys(input, ['version', 'endpoint', 'ca_spki_sha256', 'authority', 'secret', 'integrity'], [
|
|
447
|
+
'version',
|
|
448
|
+
'endpoint',
|
|
449
|
+
'ca_spki_sha256',
|
|
450
|
+
'authority',
|
|
451
|
+
'secret',
|
|
452
|
+
'integrity',
|
|
453
|
+
]);
|
|
454
|
+
if (input.version !== INVITATION_ARTIFACT_VERSION) {
|
|
455
|
+
fail('Unsupported invitation artifact version.', ['version']);
|
|
456
|
+
}
|
|
457
|
+
const endpoint = canonicalInvitationEndpoint(input.endpoint, ['endpoint']);
|
|
458
|
+
const caSpkiSha256 = boundedString(input.ca_spki_sha256, 64, 64, ['ca_spki_sha256']);
|
|
459
|
+
if (!/^[0-9a-f]{64}$/.test(caSpkiSha256)) {
|
|
460
|
+
fail('CA SPKI SHA-256 must be lowercase hexadecimal.', ['ca_spki_sha256']);
|
|
461
|
+
}
|
|
462
|
+
if (input.authority !== 'client' && input.authority !== 'owner') {
|
|
463
|
+
fail('Invitation authority is invalid.', ['authority']);
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
version: INVITATION_ARTIFACT_VERSION,
|
|
467
|
+
endpoint,
|
|
468
|
+
ca_spki_sha256: caSpkiSha256,
|
|
469
|
+
authority: input.authority,
|
|
470
|
+
secret: opaqueToken(input.secret, ['secret']),
|
|
471
|
+
integrity: opaqueToken(input.integrity, ['integrity']),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Return the canonical ASCII preimage for the artifact integrity binding.
|
|
477
|
+
* Implementations hash these exact bytes with their agreed secret algorithm;
|
|
478
|
+
* the shared package intentionally does not own a crypto runtime.
|
|
479
|
+
*/
|
|
480
|
+
export function getInvitationArtifactIntegrityInput(value: InvitationArtifact): string {
|
|
481
|
+
const artifact = validateInvitationArtifact(value);
|
|
482
|
+
const endpoint = encodeBase64UrlAscii(artifact.endpoint);
|
|
483
|
+
const secret = encodeBase64UrlAscii(artifact.secret);
|
|
484
|
+
return [
|
|
485
|
+
INVITATION_MAGIC,
|
|
486
|
+
invitationFieldLength(artifact.endpoint, ['endpoint']),
|
|
487
|
+
endpoint,
|
|
488
|
+
artifact.ca_spki_sha256,
|
|
489
|
+
artifact.authority === 'client' ? 'c' : 'o',
|
|
490
|
+
invitationFieldLength(artifact.secret, ['secret']),
|
|
491
|
+
secret,
|
|
492
|
+
].join('');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Encode an invitation artifact as one canonical, unpadded base64url token. */
|
|
496
|
+
export function encodeInvitationArtifact(value: InvitationArtifact): string {
|
|
497
|
+
const artifact = validateInvitationArtifact(value);
|
|
498
|
+
const payload = [
|
|
499
|
+
getInvitationArtifactIntegrityInput(artifact),
|
|
500
|
+
invitationFieldLength(artifact.integrity, ['integrity']),
|
|
501
|
+
encodeBase64UrlAscii(artifact.integrity),
|
|
502
|
+
].join('');
|
|
503
|
+
const token = encodeBase64UrlAscii(payload);
|
|
504
|
+
if (token.length < 43 || token.length > 1024) {
|
|
505
|
+
fail('Encoded invitation artifact exceeds the supported token bound.');
|
|
506
|
+
}
|
|
507
|
+
return token;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** Decode and strictly validate a canonical invitation artifact token. */
|
|
511
|
+
export function decodeInvitationArtifact(value: unknown): InvitationArtifact {
|
|
512
|
+
const token = opaqueToken(value, ['invitation']);
|
|
513
|
+
const payload = decodeBase64UrlAscii(token, ['invitation']);
|
|
514
|
+
if (!payload.startsWith(INVITATION_MAGIC)) {
|
|
515
|
+
fail('Invitation uses an unsupported or legacy format.', ['invitation']);
|
|
516
|
+
}
|
|
517
|
+
const cursor = { value: INVITATION_MAGIC.length };
|
|
518
|
+
const endpoint = decodeInvitationField(payload, cursor, ['endpoint']);
|
|
519
|
+
const caSpkiSha256 = payload.slice(cursor.value, cursor.value + 64);
|
|
520
|
+
if (caSpkiSha256.length !== 64) fail('Invitation pin is truncated.', ['ca_spki_sha256']);
|
|
521
|
+
cursor.value += 64;
|
|
522
|
+
const authority = payload[cursor.value++];
|
|
523
|
+
const secret = decodeInvitationField(payload, cursor, ['secret']);
|
|
524
|
+
const integrity = decodeInvitationField(payload, cursor, ['integrity']);
|
|
525
|
+
if (cursor.value !== payload.length) fail('Invitation contains trailing fields.', ['invitation']);
|
|
526
|
+
return validateInvitationArtifact({
|
|
527
|
+
version: INVITATION_ARTIFACT_VERSION,
|
|
528
|
+
endpoint,
|
|
529
|
+
ca_spki_sha256: caSpkiSha256,
|
|
530
|
+
authority: authority === 'c' ? 'client' : authority === 'o' ? 'owner' : authority,
|
|
531
|
+
secret,
|
|
532
|
+
integrity,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
318
536
|
function decodeRequestId(value: unknown, path: readonly (string | number)[]): string {
|
|
319
537
|
const decoded = boundedString(value, 8, 128, path);
|
|
320
538
|
if (!/^[A-Za-z0-9._-]+$/.test(decoded)) {
|
package/src/templates.ts
CHANGED
|
@@ -87,6 +87,16 @@ Review rounds:
|
|
|
87
87
|
- After two blocked rounds, stop and ask the human before opening an exceptional round.
|
|
88
88
|
- Findings outside the authorized outcome are reported separately and do not expand or gate the current work.`;
|
|
89
89
|
|
|
90
|
+
export const COORDINATOR_FINDING_DISPATCH_DISCIPLINE = `
|
|
91
|
+
|
|
92
|
+
Review dispatch:
|
|
93
|
+
- A reviewer finding that carries an open ASK or an unverified condition is NOT dispatchable. Hold the rework until the ANSWER lands or the finding is withdrawn. Dispatch latency is seconds and verification is minutes, so routing a conditional finding guarantees that work starts before its premise is checked.`;
|
|
94
|
+
|
|
95
|
+
export const REVIEWER_FINDING_DISCIPLINE = `
|
|
96
|
+
|
|
97
|
+
Review findings:
|
|
98
|
+
- Never post an unanswered ASK and the consequences that depend on it in the same entry. The ASK goes alone; the finding follows the answer. Labeling an entry "not a verdict" does not help: a post naming a path and a consequence is actionable on its face.`;
|
|
99
|
+
|
|
90
100
|
export const RELEASE_CYCLE_SHAPES = `
|
|
91
101
|
|
|
92
102
|
Integration and release:
|
|
@@ -136,6 +146,19 @@ Push discipline:
|
|
|
136
146
|
- Push only the assigned branch after verifying the staged paths and final diff.
|
|
137
147
|
- Do not force-push, rebase a shared branch, or publish from a local substitute artifact.`;
|
|
138
148
|
|
|
149
|
+
export const SAME_REPOSITORY_WORKFLOW_DISCIPLINE = `
|
|
150
|
+
|
|
151
|
+
Same-repository worktrees and handover:
|
|
152
|
+
- One seat uses one stable worktree, created once at assimilation under the standard worktree root and approved once by the operator. All seats for a repository are worktrees of the same clone family, sharing its object database and refs.
|
|
153
|
+
- Start each new work item by switching branches in that seat's worktree with \`git checkout -b <branch>\`; never create a new worktree or folder per work item.
|
|
154
|
+
- Create a branch only for a routed work item and announce its name in STARTING. One branch equals one work item and one owning seat; hand a branch to another seat only through an explicit log event.
|
|
155
|
+
- Use merge-only history: no rebases and no force-pushes, because another seat may have the branch checked out or fetched.
|
|
156
|
+
- Hand over a ref and exact commit SHA, never a filesystem path. Reviewers check out the SHA in their own worktree with \`git checkout --detach <SHA>\`; never read another seat's folder. Each review round binds to one exact SHA, and a new SHA restarts the gate sequence.
|
|
157
|
+
- With a hosted origin, push the branch at creation with \`git push -u origin <branch>\`; a branch is cube-visible and REVIEW-READY only after that push.
|
|
158
|
+
- With no hosted remote, the commit itself is the durable handover artifact because clone-family worktrees share refs; omit the push step. If push/fetch semantics are needed locally, use a local bare repository as the origin path.
|
|
159
|
+
- Put all scratch work — detached review checkouts, clean-environment verification rigs, fake HOMEs, unpacked artifacts, and throwaway worktrees — under \`~/.borg/scratch/<your-seat-label>/\`; never use \`/tmp\` or an ad-hoc path. Scratch contents are disposable and must be cleaned up with the work.
|
|
160
|
+
- After every merge to the protected or main branch, broadcast the merge SHA. When an origin exists, include \`git fetch origin && git merge origin/main\` as the merge-only sync instruction.`;
|
|
161
|
+
|
|
139
162
|
export const UNIVERSAL_SAFETY_DISCIPLINES = [WAKE_PATH_MONITOR_DISCIPLINE];
|
|
140
163
|
|
|
141
164
|
export const ROLE_SCOPED_SAFETY_DISCIPLINES = [
|
|
@@ -162,7 +185,7 @@ const SOFTWARE_DEV_DIRECTIVE = `## Scope and coordination
|
|
|
162
185
|
- Reviewers assess the routed exact revision and do not create or expand work.
|
|
163
186
|
- Waiting is valid when no authorized action is available.
|
|
164
187
|
- Merge, deploy, publish, tag, release, credential, and live-operator actions require explicit authority.
|
|
165
|
-
- Keep cube-log signals concise. Put durable reasoning in the relevant issue, change, or existing maintained documentation only when it has an operational consumer
|
|
188
|
+
- Keep cube-log signals concise. Put durable reasoning in the relevant issue, change, or existing maintained documentation only when it has an operational consumer.${SAME_REPOSITORY_WORKFLOW_DISCIPLINE}`;
|
|
166
189
|
|
|
167
190
|
const SOFTWARE_DEV_TAXONOMY: MessageTaxonomy = [
|
|
168
191
|
{
|
|
@@ -262,7 +285,7 @@ Communication:
|
|
|
262
285
|
- Distinguish read-only findings, proposals, completed actions, and actions awaiting authority.
|
|
263
286
|
- 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.
|
|
264
287
|
|
|
265
|
-
Builders implement; reviewers review; you coordinate. Integrate only when authorized.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${GIT_OPERATIONAL_DISCIPLINE_COORDINATOR}${PUSH_DISCIPLINE_COORDINATOR}${DRONE_ADDRESSING_CONVENTION}`;
|
|
288
|
+
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}`;
|
|
266
289
|
|
|
267
290
|
const BUILDER = `You implement only explicitly assigned software changes within the stated repository and slice.
|
|
268
291
|
|
|
@@ -306,7 +329,7 @@ Verdict:
|
|
|
306
329
|
- Post one consolidated REVIEW-APPROVED or REVIEW-FEEDBACK bound to the exact revision.
|
|
307
330
|
- Give file/line evidence and a bounded acceptance condition for blockers.
|
|
308
331
|
- A new revision requires fresh review; never imply approval from a prior revision.
|
|
309
|
-
- Do not merge, deploy, publish, tag, or release.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
332
|
+
- Do not merge, deploy, publish, tag, or release.${REVIEWER_FINDING_DISCIPLINE}${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
310
333
|
|
|
311
334
|
const RELEASE_QUALITY = `Perform only the routed release-quality checks for the exact software revision and changed surface.
|
|
312
335
|
|
|
@@ -316,7 +339,7 @@ const RELEASE_QUALITY = `Perform only the routed release-quality checks for the
|
|
|
316
339
|
- Report reproducible failures with steps and evidence. Report passes with the exact scenarios exercised.
|
|
317
340
|
- Label the verdict testing, docs, or both, and bind it to the exact revision.
|
|
318
341
|
- Keep polish, unrelated drift, and optional improvements non-blocking and outside the current work unless explicitly assigned.
|
|
319
|
-
- Do not merge, publish, deploy, tag, release, or create follow-up issues on your own.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
342
|
+
- Do not merge, publish, deploy, tag, release, or create follow-up issues on your own.${REVIEWER_FINDING_DISCIPLINE}${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
320
343
|
|
|
321
344
|
const PRODUCT_DESIGN = `Review only routed user-facing software changes or an explicit design request.
|
|
322
345
|
|
|
@@ -326,7 +349,7 @@ const PRODUCT_DESIGN = `Review only routed user-facing software changes or an ex
|
|
|
326
349
|
- Create a mockup only when it materially resolves the authorized question; use repository-tracked, reviewable artifacts.
|
|
327
350
|
- Give one consolidated approval or bounded blocker with observable evidence.
|
|
328
351
|
- Do not redesign adjacent surfaces, set product strategy, implement code, create speculative artifacts, or open follow-up work without authorization.
|
|
329
|
-
- Waiting is valid when no design review is routed.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
352
|
+
- Waiting is valid when no design review is routed.${REVIEWER_FINDING_DISCIPLINE}${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
330
353
|
|
|
331
354
|
const PRODUCT_STRATEGY = `Provide source-verified product analysis only when requested.
|
|
332
355
|
|
|
@@ -335,7 +358,7 @@ const PRODUCT_STRATEGY = `Provide source-verified product analysis only when req
|
|
|
335
358
|
- Preserve uncertainty. A proposal is advisory and never authorizes implementation, reprioritization, or mutation.
|
|
336
359
|
- Do not dispatch Builders, write implementation code, merge, release, or manufacture roadmap work from idle capacity.
|
|
337
360
|
- Surface contradictions that materially affect the requested outcome; leave unrelated opportunities outside the active work.
|
|
338
|
-
- Waiting is valid when no strategy question is assigned.${ESCALATION_DISCIPLINE}`;
|
|
361
|
+
- Waiting is valid when no strategy question is assigned.${REVIEWER_FINDING_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
339
362
|
|
|
340
363
|
const SECURITY_AUDITOR = `Perform only the routed security review of an exact software revision or an explicitly authorized security sweep.
|
|
341
364
|
|
|
@@ -344,7 +367,7 @@ const SECURITY_AUDITOR = `Perform only the routed security review of an exact so
|
|
|
344
367
|
- Reproduce or source-prove findings. State preconditions, impact, severity, and the smallest acceptance condition.
|
|
345
368
|
- One consolidated verdict per revision. Block only concrete in-scope or touched-surface security defects.
|
|
346
369
|
- Report unrelated risks separately; do not expand the implementation, start a general hardening program, or create follow-up issues without authorization.
|
|
347
|
-
- Do not implement fixes, merge, deploy, publish, tag, or release.${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
370
|
+
- Do not implement fixes, merge, deploy, publish, tag, or release.${REVIEWER_FINDING_DISCIPLINE}${SERIALIZED_REVIEW_ROUNDS_DISCIPLINE}${ESCALATION_DISCIPLINE}`;
|
|
348
371
|
|
|
349
372
|
const SOFTWARE_DEV: Template = {
|
|
350
373
|
...NEW_CUBE_TEMPLATE_PRESENTATIONS[0],
|
|
@@ -469,7 +492,7 @@ const STARTER: Template = {
|
|
|
469
492
|
- Assignment, review, and completion do not authorize unrelated work or integration.
|
|
470
493
|
- ACK is receipt only; STARTING or substantive PROGRESS confirms activation.
|
|
471
494
|
- Findings outside scope are reported, not automatically fixed.
|
|
472
|
-
- Waiting is valid when no authorized action is available
|
|
495
|
+
- Waiting is valid when no authorized action is available.${SAME_REPOSITORY_WORKFLOW_DISCIPLINE}`,
|
|
473
496
|
message_taxonomy: STARTER_TAXONOMY,
|
|
474
497
|
roles: [
|
|
475
498
|
{
|
|
@@ -485,7 +508,7 @@ const STARTER: Template = {
|
|
|
485
508
|
- Questions, findings, proposals, open queues, and spare capacity do not authorize new work.
|
|
486
509
|
- Route completed work to the Reviewer only when review is required.
|
|
487
510
|
- Ask the human before rescoping, abandoning, waiving, merging, shipping, publishing, or taking an irreversible action unless already delegated.
|
|
488
|
-
- Waiting is valid when work is complete, blocked, under review, or awaiting authority.${ANTI_PASSIVE_STANDING_DISCIPLINE}${DRONE_ADDRESSING_CONVENTION}`,
|
|
511
|
+
- Waiting is valid when work is complete, blocked, under review, or awaiting authority.${COORDINATOR_FINDING_DISPATCH_DISCIPLINE}${ANTI_PASSIVE_STANDING_DISCIPLINE}${DRONE_ADDRESSING_CONVENTION}`,
|
|
489
512
|
},
|
|
490
513
|
{
|
|
491
514
|
name: 'Worker',
|
|
@@ -510,7 +533,7 @@ const STARTER: Template = {
|
|
|
510
533
|
- Check correctness, completeness, regressions, and scope containment proportionate to the task.
|
|
511
534
|
- Post one APPROVED or FEEDBACK verdict. Give concrete evidence and a bounded acceptance condition for blockers.
|
|
512
535
|
- Keep unrelated observations outside the current work. Do not implement fixes, expand scope, integrate, publish, or take irreversible actions.
|
|
513
|
-
- Waiting is valid when no review is routed.${ESCALATION_DISCIPLINE}`,
|
|
536
|
+
- Waiting is valid when no review is routed.${REVIEWER_FINDING_DISCIPLINE}${ESCALATION_DISCIPLINE}`,
|
|
514
537
|
},
|
|
515
538
|
],
|
|
516
539
|
};
|
|
@@ -591,7 +614,7 @@ const LOCAL_MODEL_DIRECTIVE = `## Verification-cost workflow
|
|
|
591
614
|
- A fourth seat is optional: add a second Executor when throughput-bound, or a second capable Director as an independent review lens when correctness-bound. Never use a cheap model as a review lens.
|
|
592
615
|
- Waiting is valid only when no authorized action or active assigned work remains, or while a role is awaiting a named predecessor and has no independent action it can advance.
|
|
593
616
|
- Dispatch, packet echo, status, and answers are not completion. Each role continues its active item in the same turn until it posts a terminal signal from its own vocabulary.
|
|
594
|
-
- Merge, publish, deploy, tag, release, credential, and irreversible actions require explicit authority
|
|
617
|
+
- Merge, publish, deploy, tag, release, credential, and irreversible actions require explicit authority.${SAME_REPOSITORY_WORKFLOW_DISCIPLINE}`;
|
|
595
618
|
|
|
596
619
|
const LOCAL_MODEL_DIRECTOR = `You own authorized intent, priorities, decisions, and verification that requires careful reading. Never implement a change.
|
|
597
620
|
|