borgmcp-shared 0.6.3 → 0.7.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.
- package/README.md +24 -9
- package/dist/conformance/adapter.d.ts +18 -0
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +119 -1
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +118 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +236 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +48 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +114 -11
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/coordination.d.ts +2 -2
- package/dist/protocol/coordination.d.ts.map +1 -1
- package/dist/protocol/coordination.js +36 -2
- package/dist/protocol/coordination.js.map +1 -1
- package/dist/protocol/errors.d.ts +2 -0
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +2 -0
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +4 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +182 -1
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +22 -7
- package/docs/enrollment.md +57 -9
- package/docs/releasing.md +14 -8
- package/package.json +1 -1
- package/src/conformance/adapter.ts +261 -0
- package/src/conformance/index.ts +296 -0
- package/src/protocol/contract.ts +168 -11
- package/src/protocol/coordination.ts +53 -4
- package/src/protocol/errors.ts +2 -0
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +188 -1
package/src/protocol/contract.ts
CHANGED
|
@@ -13,12 +13,14 @@ 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.7.0' as const;
|
|
17
17
|
|
|
18
18
|
export const HEALTH_PATH = '/healthz' as const;
|
|
19
19
|
export const PROTOCOL_INFO_PATH = '/api/protocol' as const;
|
|
20
20
|
export const ENROLLMENT_EXCHANGE_PATH = '/api/enrollment/exchange' as const;
|
|
21
21
|
export const CUBES_PATH = '/api/cubes' as const;
|
|
22
|
+
export const REPOSITORY_CUBE_RESOLVE_PATH = '/api/repository-cubes/resolve' as const;
|
|
23
|
+
export const REPOSITORY_CUBE_ASSOCIATION_PATH = '/api/repository-cubes/association' as const;
|
|
22
24
|
export const ATTACH_PATH = '/api/client/attach' as const;
|
|
23
25
|
export const SELF_RUNTIME_METADATA_PATH = '/api/cubes/:cubeId/drones/self/metadata' as const;
|
|
24
26
|
|
|
@@ -27,6 +29,20 @@ export const PROTOCOL_HTTP_CONTRACT = {
|
|
|
27
29
|
protocol: { method: 'GET', path: PROTOCOL_INFO_PATH, authenticated: false, success_status: 200 },
|
|
28
30
|
enrollment: { method: 'POST', path: ENROLLMENT_EXCHANGE_PATH, authenticated: 'invitation', success_status: 201 },
|
|
29
31
|
cubes: { method: 'POST', path: CUBES_PATH, authenticated: true, success_status: 201 },
|
|
32
|
+
repository_cube_resolve: {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
path: REPOSITORY_CUBE_RESOLVE_PATH,
|
|
35
|
+
authenticated: true,
|
|
36
|
+
success_status: 200,
|
|
37
|
+
mutation: false,
|
|
38
|
+
},
|
|
39
|
+
repository_cube_association: {
|
|
40
|
+
method: 'PUT',
|
|
41
|
+
path: REPOSITORY_CUBE_ASSOCIATION_PATH,
|
|
42
|
+
authenticated: true,
|
|
43
|
+
success_status: 200,
|
|
44
|
+
mutation: true,
|
|
45
|
+
},
|
|
30
46
|
attach: { method: 'POST', path: ATTACH_PATH, authenticated: true, success_status: 200 },
|
|
31
47
|
drone_reassign: {
|
|
32
48
|
method: 'PATCH',
|
|
@@ -121,7 +137,7 @@ export type EnrollmentExchangeResponse =
|
|
|
121
137
|
| ClientEnrollmentExchangeResponse
|
|
122
138
|
| OwnerEnrollmentExchangeResponse;
|
|
123
139
|
|
|
124
|
-
export const CUBE_TEMPLATES = ['default', 'software-dev', 'starter'] as const;
|
|
140
|
+
export const CUBE_TEMPLATES = ['default', 'software-dev', 'starter', 'local-model'] as const;
|
|
125
141
|
export type CubeTemplate = (typeof CUBE_TEMPLATES)[number];
|
|
126
142
|
|
|
127
143
|
export type CreateCubeRepository =
|
|
@@ -148,6 +164,33 @@ export interface CreateCubeResponse {
|
|
|
148
164
|
access: 'manage';
|
|
149
165
|
}
|
|
150
166
|
|
|
167
|
+
export interface ResolveRepositoryCubeRequest {
|
|
168
|
+
working_repo_name: string;
|
|
169
|
+
repository: CreateCubeRepository;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface AssociateRepositoryCubeRequest extends ResolveRepositoryCubeRequest {
|
|
173
|
+
cube_id: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ResolvedRepositoryCube {
|
|
177
|
+
result: 'resolved';
|
|
178
|
+
cube_id: string;
|
|
179
|
+
name: string;
|
|
180
|
+
working_repo_name: string;
|
|
181
|
+
repository: CreateCubeRepository;
|
|
182
|
+
template: CubeTemplate;
|
|
183
|
+
human_seat_role_id: string;
|
|
184
|
+
default_worker_role_id: string;
|
|
185
|
+
access: 'manage';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type ResolveRepositoryCubeResponse =
|
|
189
|
+
| { result: 'none' }
|
|
190
|
+
| ResolvedRepositoryCube;
|
|
191
|
+
|
|
192
|
+
export type AssociateRepositoryCubeResponse = ResolvedRepositoryCube;
|
|
193
|
+
|
|
151
194
|
export interface AckLogRequest {
|
|
152
195
|
entry_id: string;
|
|
153
196
|
kind: 'ack' | 'claim';
|
|
@@ -284,7 +327,7 @@ export function decodeProtocolTagPreflight(value: unknown): ProtocolTagPreflight
|
|
|
284
327
|
exactKeys(input, ['protocol_version'], ['protocol_version']);
|
|
285
328
|
if (input.protocol_version !== PROTOCOL_VERSION) {
|
|
286
329
|
throw new ProtocolContractError(
|
|
287
|
-
'
|
|
330
|
+
'This client requires protocol v6. The peer presents a different version. Update `borgmcp-server` and `borgmcp` to matching releases — server first, then client.',
|
|
288
331
|
ErrorCode.UNSUPPORTED_PROTOCOL_VERSION,
|
|
289
332
|
['protocol_version'],
|
|
290
333
|
);
|
|
@@ -455,10 +498,7 @@ export function decodeCreateCubeRequest(value: unknown): CreateCubeRequest {
|
|
|
455
498
|
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
456
499
|
fail('Cube name contains unsupported characters.', ['name']);
|
|
457
500
|
}
|
|
458
|
-
const workingRepoName =
|
|
459
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
460
|
-
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
461
|
-
}
|
|
501
|
+
const workingRepoName = decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']);
|
|
462
502
|
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
463
503
|
fail('Unsupported cube template.', ['template']);
|
|
464
504
|
}
|
|
@@ -509,10 +549,7 @@ export function decodeCreateCubeResponse(value: unknown): CreateCubeResponse {
|
|
|
509
549
|
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
510
550
|
fail('Cube name contains unsupported characters.', ['name']);
|
|
511
551
|
}
|
|
512
|
-
const workingRepoName =
|
|
513
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(workingRepoName)) {
|
|
514
|
-
fail('Cube name contains unsupported characters.', ['working_repo_name']);
|
|
515
|
-
}
|
|
552
|
+
const workingRepoName = decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']);
|
|
516
553
|
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
517
554
|
fail('Unsupported cube template.', ['template']);
|
|
518
555
|
}
|
|
@@ -559,6 +596,126 @@ export function decodeCreateCubeResponseEnvelope(value: unknown): ProtocolEnvelo
|
|
|
559
596
|
return decodeProtocolEnvelope(value, decodeCreateCubeResponse);
|
|
560
597
|
}
|
|
561
598
|
|
|
599
|
+
export function decodeResolveRepositoryCubeRequest(value: unknown): ResolveRepositoryCubeRequest {
|
|
600
|
+
const input = record(value);
|
|
601
|
+
exactKeys(input, ['working_repo_name', 'repository'], ['working_repo_name', 'repository']);
|
|
602
|
+
return {
|
|
603
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
604
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function decodeResolveRepositoryCubeRequestEnvelope(
|
|
609
|
+
value: unknown,
|
|
610
|
+
): ProtocolEnvelope<ResolveRepositoryCubeRequest> {
|
|
611
|
+
return decodeProtocolEnvelope(value, decodeResolveRepositoryCubeRequest);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function decodeAssociateRepositoryCubeRequest(value: unknown): AssociateRepositoryCubeRequest {
|
|
615
|
+
const input = record(value);
|
|
616
|
+
exactKeys(
|
|
617
|
+
input,
|
|
618
|
+
['cube_id', 'working_repo_name', 'repository'],
|
|
619
|
+
['cube_id', 'working_repo_name', 'repository'],
|
|
620
|
+
);
|
|
621
|
+
return {
|
|
622
|
+
cube_id: decodeUuid(input.cube_id, ['cube_id']),
|
|
623
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
624
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function decodeAssociateRepositoryCubeRequestEnvelope(
|
|
629
|
+
value: unknown,
|
|
630
|
+
): ProtocolEnvelope<AssociateRepositoryCubeRequest> {
|
|
631
|
+
return decodeProtocolEnvelope(value, decodeAssociateRepositoryCubeRequest);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function decodeResolvedRepositoryCube(value: unknown): ResolvedRepositoryCube {
|
|
635
|
+
const input = record(value);
|
|
636
|
+
exactKeys(
|
|
637
|
+
input,
|
|
638
|
+
[
|
|
639
|
+
'result',
|
|
640
|
+
'cube_id',
|
|
641
|
+
'name',
|
|
642
|
+
'working_repo_name',
|
|
643
|
+
'repository',
|
|
644
|
+
'template',
|
|
645
|
+
'human_seat_role_id',
|
|
646
|
+
'default_worker_role_id',
|
|
647
|
+
'access',
|
|
648
|
+
],
|
|
649
|
+
[
|
|
650
|
+
'result',
|
|
651
|
+
'cube_id',
|
|
652
|
+
'name',
|
|
653
|
+
'working_repo_name',
|
|
654
|
+
'repository',
|
|
655
|
+
'template',
|
|
656
|
+
'human_seat_role_id',
|
|
657
|
+
'default_worker_role_id',
|
|
658
|
+
'access',
|
|
659
|
+
],
|
|
660
|
+
);
|
|
661
|
+
if (input.result !== 'resolved') fail('Invalid repository cube result.', ['result']);
|
|
662
|
+
const name = boundedString(input.name, 1, 120, ['name']);
|
|
663
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
664
|
+
fail('Cube name contains unsupported characters.', ['name']);
|
|
665
|
+
}
|
|
666
|
+
if (!CUBE_TEMPLATES.includes(input.template as CubeTemplate)) {
|
|
667
|
+
fail('Unsupported cube template.', ['template']);
|
|
668
|
+
}
|
|
669
|
+
if (input.access !== 'manage') fail('Repository cube access must be manage.', ['access']);
|
|
670
|
+
return {
|
|
671
|
+
result: 'resolved',
|
|
672
|
+
cube_id: decodeUuid(input.cube_id, ['cube_id']),
|
|
673
|
+
name,
|
|
674
|
+
working_repo_name: decodeWorkingRepositoryName(input.working_repo_name, ['working_repo_name']),
|
|
675
|
+
repository: decodeCreateCubeRepository(input.repository, ['repository']),
|
|
676
|
+
template: input.template as CubeTemplate,
|
|
677
|
+
human_seat_role_id: decodeUuid(input.human_seat_role_id, ['human_seat_role_id']),
|
|
678
|
+
default_worker_role_id: decodeUuid(input.default_worker_role_id, ['default_worker_role_id']),
|
|
679
|
+
access: 'manage',
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
export function decodeResolveRepositoryCubeResponse(value: unknown): ResolveRepositoryCubeResponse {
|
|
684
|
+
const input = record(value);
|
|
685
|
+
if (input.result === 'none') {
|
|
686
|
+
exactKeys(input, ['result'], ['result']);
|
|
687
|
+
return { result: 'none' };
|
|
688
|
+
}
|
|
689
|
+
return decodeResolvedRepositoryCube(input);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export function decodeResolveRepositoryCubeResponseEnvelope(
|
|
693
|
+
value: unknown,
|
|
694
|
+
): ProtocolEnvelope<ResolveRepositoryCubeResponse> {
|
|
695
|
+
return decodeProtocolEnvelope(value, decodeResolveRepositoryCubeResponse);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function decodeAssociateRepositoryCubeResponse(value: unknown): AssociateRepositoryCubeResponse {
|
|
699
|
+
return decodeResolvedRepositoryCube(value);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function decodeAssociateRepositoryCubeResponseEnvelope(
|
|
703
|
+
value: unknown,
|
|
704
|
+
): ProtocolEnvelope<AssociateRepositoryCubeResponse> {
|
|
705
|
+
return decodeProtocolEnvelope(value, decodeAssociateRepositoryCubeResponse);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function decodeWorkingRepositoryName(
|
|
709
|
+
value: unknown,
|
|
710
|
+
path: readonly (string | number)[],
|
|
711
|
+
): string {
|
|
712
|
+
const name = boundedString(value, 1, 120, path);
|
|
713
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(name)) {
|
|
714
|
+
fail('Repository name contains unsupported characters.', path);
|
|
715
|
+
}
|
|
716
|
+
return name;
|
|
717
|
+
}
|
|
718
|
+
|
|
562
719
|
export function decodeAppendLogRequest(value: unknown): import('./types.js').AppendLogRequest {
|
|
563
720
|
const input = record(value);
|
|
564
721
|
exactKeys(
|
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
type ProtocolEnvelope,
|
|
11
11
|
} from './contract.js';
|
|
12
12
|
import { decodeEnrichedStreamEntry } from './sse.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
AppendLogResponse,
|
|
15
|
+
Decision,
|
|
16
|
+
EnrichedStreamEntry,
|
|
17
|
+
RoutingEcho,
|
|
18
|
+
} from './types.js';
|
|
14
19
|
|
|
15
20
|
export interface ReadLogRequest {
|
|
16
21
|
cursor: LogCursor | null;
|
|
@@ -26,7 +31,7 @@ export interface ClaimRecord {
|
|
|
26
31
|
stale: boolean;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
export interface AppendLogResult {
|
|
34
|
+
export interface AppendLogResult extends Omit<AppendLogResponse, 'entry'> {
|
|
30
35
|
entry: EnrichedStreamEntry;
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -214,10 +219,54 @@ function decodeClaimRecord(value: unknown): ClaimRecord {
|
|
|
214
219
|
};
|
|
215
220
|
}
|
|
216
221
|
|
|
222
|
+
function decodeRoutingEcho(value: unknown): RoutingEcho {
|
|
223
|
+
const input = object(value);
|
|
224
|
+
exact(
|
|
225
|
+
input,
|
|
226
|
+
['class', 'recipients', 'fellOpen', 'message'],
|
|
227
|
+
['class', 'recipients', 'fellOpen', 'message'],
|
|
228
|
+
);
|
|
229
|
+
if (!Array.isArray(input.recipients) || input.recipients.length > 100) {
|
|
230
|
+
throw new ProtocolContractError('Invalid routing recipient list.');
|
|
231
|
+
}
|
|
232
|
+
if (typeof input.fellOpen !== 'boolean') {
|
|
233
|
+
throw new ProtocolContractError('Invalid routing fell-open flag.');
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
class: nullableString(input.class, 'routing.class', 64),
|
|
237
|
+
recipients: input.recipients.map((recipient) =>
|
|
238
|
+
boundedString(recipient, 'routing.recipients', 120)
|
|
239
|
+
),
|
|
240
|
+
fellOpen: input.fellOpen,
|
|
241
|
+
message: nullableString(input.message, 'routing.message', 512),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function decodeUnreachableRecipient(
|
|
246
|
+
value: unknown,
|
|
247
|
+
): NonNullable<AppendLogResponse['unreachableRecipients']>[number] {
|
|
248
|
+
const input = object(value);
|
|
249
|
+
exact(input, ['id', 'label'], ['id', 'label']);
|
|
250
|
+
return {
|
|
251
|
+
id: boundedString(input.id, 'unreachableRecipients.id', 120),
|
|
252
|
+
label: boundedString(input.label, 'unreachableRecipients.label', 120),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
217
256
|
export function decodeAppendLogResult(value: unknown): AppendLogResult {
|
|
218
257
|
const input = object(value);
|
|
219
|
-
exact(input, ['entry'], ['entry']);
|
|
220
|
-
|
|
258
|
+
exact(input, ['entry', 'routing', 'unreachableRecipients'], ['entry']);
|
|
259
|
+
const output: AppendLogResult = { entry: decodeEnrichedStreamEntry(input.entry) };
|
|
260
|
+
if (input.routing !== undefined) {
|
|
261
|
+
output.routing = input.routing === null ? null : decodeRoutingEcho(input.routing);
|
|
262
|
+
}
|
|
263
|
+
if (input.unreachableRecipients !== undefined) {
|
|
264
|
+
if (!Array.isArray(input.unreachableRecipients) || input.unreachableRecipients.length > 100) {
|
|
265
|
+
throw new ProtocolContractError('Invalid unreachable-recipient list.');
|
|
266
|
+
}
|
|
267
|
+
output.unreachableRecipients = input.unreachableRecipients.map(decodeUnreachableRecipient);
|
|
268
|
+
}
|
|
269
|
+
return output;
|
|
221
270
|
}
|
|
222
271
|
|
|
223
272
|
export function decodeAppendLogResultEnvelope(
|
package/src/protocol/errors.ts
CHANGED
|
@@ -13,6 +13,8 @@ export enum ErrorCode {
|
|
|
13
13
|
DATABASE_ERROR = 'DATABASE_ERROR',
|
|
14
14
|
EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR',
|
|
15
15
|
NOT_FOUND = 'NOT_FOUND',
|
|
16
|
+
REPOSITORY_ALREADY_ASSOCIATED = 'REPOSITORY_ALREADY_ASSOCIATED',
|
|
17
|
+
CUBE_ALREADY_ASSOCIATED = 'CUBE_ALREADY_ASSOCIATED',
|
|
16
18
|
ROLE_IN_USE = 'ROLE_IN_USE',
|
|
17
19
|
ROLE_HAS_FROZEN_DRONES = 'ROLE_HAS_FROZEN_DRONES',
|
|
18
20
|
DRONE_EVICTED = 'DRONE_EVICTED',
|
package/src/protocol/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Current Borg coordination protocol generation. Clean-slate
|
|
2
|
-
export const PROTOCOL_VERSION = '
|
|
1
|
+
/** Current Borg coordination protocol generation. Clean-slate v6. */
|
|
2
|
+
export const PROTOCOL_VERSION = '6' as const;
|
|
3
3
|
|
|
4
4
|
export type ProtocolVersion = typeof PROTOCOL_VERSION;
|
package/src/templates.ts
CHANGED
|
@@ -51,6 +51,11 @@ export const NEW_CUBE_TEMPLATE_PRESENTATIONS = [
|
|
|
51
51
|
label: 'Starter',
|
|
52
52
|
short_description: 'Minimal roles for general projects.',
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
name: 'local-model',
|
|
56
|
+
label: 'Local Model',
|
|
57
|
+
short_description: 'Maximizes local-model execution through complete, machine-checkable work packets.',
|
|
58
|
+
},
|
|
54
59
|
] as const;
|
|
55
60
|
|
|
56
61
|
export const ESCALATION_DISCIPLINE = `
|
|
@@ -266,9 +271,16 @@ Before changing code:
|
|
|
266
271
|
- Inspect existing code and tests. Preserve unrelated and pre-existing changes.
|
|
267
272
|
- If the request is ambiguous in a way that changes scope, post BLOCKED with the smallest decision needed.
|
|
268
273
|
|
|
274
|
+
Implementation discipline:
|
|
275
|
+
- Read and trace the real affected flow before choosing an implementation.
|
|
276
|
+
- Prefer, in order: no change when the requirement is already satisfied; an existing repository helper or pattern; the standard library or native platform; an already-installed dependency; only then the minimum new code.
|
|
277
|
+
- Make the smallest change that satisfies the complete authorized acceptance criteria. Prefer the least complex implementation that fully works, not the least work.
|
|
278
|
+
- For defects, inspect sibling callers and fix the root cause at the narrowest shared point when that is safer and smaller than per-caller patches.
|
|
279
|
+
- Never simplify away trust-boundary validation, security controls, data-loss prevention, accessibility requirements, explicit acceptance criteria, or proportionate regression tests.
|
|
280
|
+
|
|
269
281
|
While working:
|
|
270
282
|
- Post STARTING with the branch and first concrete action, then substantive PROGRESS during active work.
|
|
271
|
-
-
|
|
283
|
+
- Do not add cleanup, broad refactors, speculative hardening, documentation programs, or follow-up issues unless assigned.
|
|
272
284
|
- A discovered issue outside the slice is a finding, not permission to fix it.
|
|
273
285
|
- Add proportionate tests for behavior you change. Run the repository checks required by the touched surface.
|
|
274
286
|
|
|
@@ -503,9 +515,184 @@ const STARTER: Template = {
|
|
|
503
515
|
],
|
|
504
516
|
};
|
|
505
517
|
|
|
518
|
+
const LOCAL_MODEL_TAXONOMY: MessageTaxonomy = [
|
|
519
|
+
{
|
|
520
|
+
class: 'executor-echo',
|
|
521
|
+
prefixes: ['PACKET-ECHO'],
|
|
522
|
+
routing: 'directed',
|
|
523
|
+
default_to: ['shaper'],
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
class: 'executor-refusal',
|
|
527
|
+
prefixes: ['SPEC-GAP'],
|
|
528
|
+
routing: 'directed',
|
|
529
|
+
default_to: ['shaper'],
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
class: 'executor-completion',
|
|
533
|
+
prefixes: ['PACKET-DONE'],
|
|
534
|
+
routing: 'directed',
|
|
535
|
+
default_to: ['shaper'],
|
|
536
|
+
lifecycle: 'completion',
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
class: 'packet-dispatch',
|
|
540
|
+
prefixes: ['EXECUTE PACKET'],
|
|
541
|
+
routing: 'directed',
|
|
542
|
+
default_to: ['executor'],
|
|
543
|
+
lifecycle: 'dispatch',
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
class: 'packet-verdict',
|
|
547
|
+
prefixes: ['ACCEPT', 'REJECT'],
|
|
548
|
+
routing: 'directed',
|
|
549
|
+
default_to: ['executor'],
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
class: 'blocked-signal',
|
|
553
|
+
prefixes: ['BLOCKED'],
|
|
554
|
+
routing: 'directed',
|
|
555
|
+
default_to: ['director', 'queen'],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
class: 'review-request',
|
|
559
|
+
prefixes: ['REVIEW-READY'],
|
|
560
|
+
routing: 'directed',
|
|
561
|
+
default_to: ['director', 'queen'],
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
class: 'director-dispatch',
|
|
565
|
+
prefixes: ['DISPATCH', 'HOLD'],
|
|
566
|
+
routing: 'directed',
|
|
567
|
+
default_to: ['shaper'],
|
|
568
|
+
lifecycle: 'dispatch',
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
class: 'director-approval',
|
|
572
|
+
prefixes: ['APPROVED'],
|
|
573
|
+
routing: 'directed',
|
|
574
|
+
default_to: ['shaper'],
|
|
575
|
+
lifecycle: 'completion',
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
class: 'cube-wide',
|
|
579
|
+
prefixes: ['DECISION'],
|
|
580
|
+
routing: 'broadcast',
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
const LOCAL_MODEL_DIRECTIVE = `## Verification-cost workflow
|
|
585
|
+
|
|
586
|
+
- Work only on the human-authorized outcome. Questions, findings, spare capacity, and open work do not authorize another task.
|
|
587
|
+
- Use three seats: Director for intent and independent careful-reading verification, Shaper for conversion and acceptance, and Executor for one complete packet at a time.
|
|
588
|
+
- The author of a change never solely verifies it. The Director never implements; work implemented by the Shaper returns to the Director for verification.
|
|
589
|
+
- Convert work before sending it to the Executor. Every packet must contain literal Surface, Shape, Check, Forbidden to infer, and Echo schema fields.
|
|
590
|
+
- The Shaper withholds a holdout test, keeps test files outside the Executor's write allowlist, rejects deleted or weakened assertions, and never lets an Executor regenerate goldens.
|
|
591
|
+
- 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
|
+
- 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
|
+
- 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.`;
|
|
595
|
+
|
|
596
|
+
const LOCAL_MODEL_DIRECTOR = `You own authorized intent, priorities, decisions, and verification that requires careful reading. Never implement a change.
|
|
597
|
+
|
|
598
|
+
Scope and authority:
|
|
599
|
+
- Preserve the human-authorized outcome, boundaries, priorities, permitted mutations, and required evidence.
|
|
600
|
+
- Decide what must be verified by a capable reader and what the Shaper may convert into a machine-checkable packet.
|
|
601
|
+
- Dispatch exact outcomes to the Shaper. Never dispatch implementation directly to the Executor.
|
|
602
|
+
- A finding, proposal, idle seat, or open issue does not authorize new scope.
|
|
603
|
+
|
|
604
|
+
Direction and verification:
|
|
605
|
+
- Use DISPATCH for an authorized Shaper item and HOLD when work must not proceed.
|
|
606
|
+
- Require the Shaper to return the exact artifact, its own check output, the holdout result, and any judgment residue.
|
|
607
|
+
- Verify the residue by careful reading. Do not treat an automated check as proof of intent, design, security, data-loss safety, or irreversible-action safety.
|
|
608
|
+
- Post APPROVED only after the authorized outcome and independent verification are complete. Use DECISION for a human-facing choice that changes the controlling direction.
|
|
609
|
+
- Never approve work you authored. If the Shaper implemented an unconvertible item, you are its independent verifier.
|
|
610
|
+
|
|
611
|
+
Continuity:
|
|
612
|
+
- DISPATCH, HOLD, and DECISION are not completion when they leave an authorized follow-on action.
|
|
613
|
+
- After answering an interruption, resume any Director action you can advance in the same turn.
|
|
614
|
+
- 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.
|
|
615
|
+
- An active Director outcome ends with APPROVED, or with BLOCKED naming the missing decision or the reason it cannot proceed. After DECISION, continue with any dispatch or verification that decision enables.`;
|
|
616
|
+
|
|
617
|
+
const LOCAL_MODEL_SHAPER = `You convert authorized intent into machine-checkable packets, accept returned packets by running their checks, and implement only work that cannot be converted.
|
|
618
|
+
|
|
619
|
+
Conversion:
|
|
620
|
+
- A task is converted only when every field below has a literal value:
|
|
621
|
+
Surface: exact file allowlist. Never write "do not touch unrelated files."
|
|
622
|
+
Shape: failing tests, target signature, schema, enumerated case table, golden, or other exact target.
|
|
623
|
+
Check: exact commands and expected results, runnable without the author.
|
|
624
|
+
Forbidden to infer: enumerated open points the Executor must refuse rather than decide.
|
|
625
|
+
Echo schema: exactly "PACKET-ECHO | Surface: <verbatim> | Shape: <verbatim> | Check: <verbatim> | Forbidden to infer: <verbatim>".
|
|
626
|
+
- If any field cannot be filled, the task is not converted. Continue shaping it, implement it yourself when explicitly authorized, or post BLOCKED with the missing decision.
|
|
627
|
+
- Surface is the packet's write boundary; do not authorize paths outside the routed scope.
|
|
628
|
+
- Test files must stay outside Surface. Never give the Executor permission to edit them.
|
|
629
|
+
- Before dispatch, withhold at least one holdout test that is not visible in the packet.
|
|
630
|
+
|
|
631
|
+
Dispatch and acceptance:
|
|
632
|
+
- Send one complete packet with EXECUTE PACKET. Do not bundle another function, choice, or optional improvement into it.
|
|
633
|
+
- While the Executor owns that packet, waiting is valid only if you have no independent part of the active Shaper assignment to advance.
|
|
634
|
+
- On SPEC-GAP, supply the missing literal or reshape the packet; never tell the Executor to use judgment.
|
|
635
|
+
- On PACKET-DONE, inspect the diff for the Surface allowlist and test-path changes. Deleting or weakening an assertion is automatic rejection.
|
|
636
|
+
- Run every packet check yourself in a clean state, then run the withheld holdout test. Do not accept copied output as proof.
|
|
637
|
+
- Post ACCEPT or REJECT with your own verbatim check output. Never regenerate a golden file to make a result pass.
|
|
638
|
+
- Route accepted work to the Director with REVIEW-READY. When you implement an unconvertible item, you still return it to the Director for independent verification.
|
|
639
|
+
|
|
640
|
+
Continuity:
|
|
641
|
+
- EXECUTE PACKET, ACCEPT, REJECT, and an answer are not completion of the active Shaper assignment.
|
|
642
|
+
- After handling an interruption, resume the assignment in the same turn when an authorized action remains.
|
|
643
|
+
- A Shaper assignment ends only with BLOCKED or REVIEW-READY.`;
|
|
644
|
+
|
|
645
|
+
const LOCAL_MODEL_EXECUTOR = `You execute one complete authorized packet exactly. You do not shape, review, decide, or claim correctness.
|
|
646
|
+
|
|
647
|
+
A packet has five literal fields: Surface, Shape, Check, Forbidden to infer, and Echo schema.
|
|
648
|
+
Surface is your complete scope boundary.
|
|
649
|
+
A REJECT is not a packet. Take no action on it; wait for a new EXECUTE PACKET.
|
|
650
|
+
If asked anything you cannot answer with SPEC-GAP or PACKET-DONE, post SPEC-GAP naming what was asked.
|
|
651
|
+
|
|
652
|
+
1. If any field is missing, or any needed value is not written literally, post SPEC-GAP naming the missing value. Do not guess.
|
|
653
|
+
2. Before changing anything, post PACKET-ECHO using the packet's exact Echo schema. Fill it only from packet text.
|
|
654
|
+
3. PACKET-ECHO is not completion. Continue the packet in the same turn.
|
|
655
|
+
4. Touch only files listed in Surface. No other file, for any reason. Test files must stay outside Surface.
|
|
656
|
+
5. Produce exactly the Shape. Do not fix, improve, clean, or infer anything else.
|
|
657
|
+
6. Run every Check command. Copy its complete output verbatim.
|
|
658
|
+
7. Post PACKET-DONE with the diff and verbatim check output. Add no prose claim about correctness.
|
|
659
|
+
|
|
660
|
+
Waiting is valid only when no packet is active. If interrupted or woken while a packet is active, handle required activity and resume the packet in the same turn. An active packet ends only with SPEC-GAP or PACKET-DONE.
|
|
661
|
+
|
|
662
|
+
Never merge, push, install packages, change configuration, edit a test, delete or weaken an assertion, or regenerate a golden file.`;
|
|
663
|
+
|
|
664
|
+
const LOCAL_MODEL: Template = {
|
|
665
|
+
...NEW_CUBE_TEMPLATE_PRESENTATIONS[2],
|
|
666
|
+
description: 'Three-seat software workflow that converts intent into machine-checkable packets for local-model execution.',
|
|
667
|
+
cube_directive: LOCAL_MODEL_DIRECTIVE,
|
|
668
|
+
message_taxonomy: LOCAL_MODEL_TAXONOMY,
|
|
669
|
+
roles: [
|
|
670
|
+
{
|
|
671
|
+
name: 'Director',
|
|
672
|
+
is_mandatory: true,
|
|
673
|
+
is_human_seat: true,
|
|
674
|
+
can_broadcast: true,
|
|
675
|
+
short_description: 'Owns intent, authorization, and careful-reading verification; never implements changes.',
|
|
676
|
+
detailed_description: LOCAL_MODEL_DIRECTOR,
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: 'Shaper',
|
|
680
|
+
short_description: 'Converts intent into complete machine-checkable packets, runs acceptance checks, and implements only unconvertible work.',
|
|
681
|
+
detailed_description: LOCAL_MODEL_SHAPER,
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: 'Executor',
|
|
685
|
+
is_default: true,
|
|
686
|
+
short_description: 'Executes one complete packet exactly, refuses missing literals, and returns only a diff plus verbatim check output.',
|
|
687
|
+
detailed_description: LOCAL_MODEL_EXECUTOR,
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
};
|
|
691
|
+
|
|
506
692
|
export const TEMPLATES: Record<string, Template> = {
|
|
507
693
|
'software-dev': SOFTWARE_DEV,
|
|
508
694
|
starter: STARTER,
|
|
695
|
+
'local-model': LOCAL_MODEL,
|
|
509
696
|
};
|
|
510
697
|
|
|
511
698
|
export function getTemplate(name: string): Template | null {
|