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
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
ErrorCode,
|
|
3
3
|
compareLogCursor,
|
|
4
4
|
createProtocolEnvelope,
|
|
5
|
+
decodeAssociateRepositoryCubeResponseEnvelope,
|
|
5
6
|
decodeCreateCubeResponseEnvelope,
|
|
6
7
|
decodeAppendLogResultEnvelope,
|
|
7
8
|
decodeDecisionResultEnvelope,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
decodeProtocolErrorEnvelope,
|
|
15
16
|
decodeProtocolTagPreflight,
|
|
16
17
|
decodeReadLogResultEnvelope,
|
|
18
|
+
decodeResolveRepositoryCubeResponseEnvelope,
|
|
17
19
|
decodeReassignDroneResultEnvelope,
|
|
18
20
|
decodeUpdateDroneRuntimeMetadataResponseEnvelope,
|
|
19
21
|
decodeSseFrames,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
type CreateCubeResponse,
|
|
25
27
|
type CreateCubeRepository,
|
|
26
28
|
type CubeTemplate,
|
|
29
|
+
type ResolvedRepositoryCube,
|
|
27
30
|
type LogCursor,
|
|
28
31
|
type StreamEvent,
|
|
29
32
|
type DroneRuntimeMetadata,
|
|
@@ -127,6 +130,15 @@ export interface ConformanceCreatedCubeState {
|
|
|
127
130
|
default_worker_role_matches: boolean;
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
export interface ConformanceRepositoryCubeFixture {
|
|
134
|
+
cube_id: string;
|
|
135
|
+
name: string;
|
|
136
|
+
template: CubeTemplate;
|
|
137
|
+
human_seat_role_id: string;
|
|
138
|
+
default_worker_role_id: string;
|
|
139
|
+
access: 'manage';
|
|
140
|
+
}
|
|
141
|
+
|
|
130
142
|
export interface ConformanceEnrollmentPrincipalState {
|
|
131
143
|
response_client_matches: boolean;
|
|
132
144
|
active_credential_bindings: number;
|
|
@@ -164,6 +176,7 @@ export interface ConformanceAdmin {
|
|
|
164
176
|
cube: ConformanceCube,
|
|
165
177
|
access?: ConformanceCubeAccess,
|
|
166
178
|
): Promise<void>;
|
|
179
|
+
revokeCubeGrant(principal: ConformancePrincipal, cube: ConformanceCube): Promise<void>;
|
|
167
180
|
createRole(cube: ConformanceCube, input: {
|
|
168
181
|
readonly roleClass: 'queen' | 'worker';
|
|
169
182
|
readonly isHumanSeat: boolean;
|
|
@@ -192,6 +205,13 @@ export interface ConformanceAdmin {
|
|
|
192
205
|
creator: ConformancePrincipal,
|
|
193
206
|
response: CreateCubeResponse,
|
|
194
207
|
): Promise<ConformanceCreatedCubeState>;
|
|
208
|
+
prepareRepositoryCube(
|
|
209
|
+
cube: ConformanceCube,
|
|
210
|
+
input: {
|
|
211
|
+
name: string;
|
|
212
|
+
template: CubeTemplate;
|
|
213
|
+
},
|
|
214
|
+
): Promise<ConformanceRepositoryCubeFixture>;
|
|
195
215
|
inspectEnrollmentPrincipal(
|
|
196
216
|
principal: ConformancePrincipal,
|
|
197
217
|
responseClientId: string,
|
|
@@ -207,6 +227,8 @@ export interface ConformanceOperations {
|
|
|
207
227
|
protocol(credential: string | null): Promise<ConformanceHttpResponse>;
|
|
208
228
|
enroll(request: unknown): Promise<ConformanceHttpResponse>;
|
|
209
229
|
createCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
230
|
+
resolveRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
231
|
+
associateRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
210
232
|
attach(credential: string, request: unknown): Promise<ConformanceHttpResponse>;
|
|
211
233
|
selfMetadataUpdate(
|
|
212
234
|
credential: string,
|
|
@@ -291,6 +313,7 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
291
313
|
{ id: 'http.unauthenticated-liveness', area: 'http' },
|
|
292
314
|
{ id: 'protocol.credential-free-preflight', area: 'protocol' },
|
|
293
315
|
{ id: 'enrollment.retry-authority', area: 'enrollment' },
|
|
316
|
+
{ id: 'repository.explicit-association', area: 'repository' },
|
|
294
317
|
{ id: 'security.adapter-boundary-injection', area: 'security' },
|
|
295
318
|
{ id: 'security.oversize-request', area: 'security' },
|
|
296
319
|
{ id: 'security.cross-cube-isolation', area: 'security' },
|
|
@@ -981,6 +1004,244 @@ export async function runAdapterConformance(
|
|
|
981
1004
|
};
|
|
982
1005
|
});
|
|
983
1006
|
|
|
1007
|
+
await record('repository.explicit-association', async () => {
|
|
1008
|
+
const repository = {
|
|
1009
|
+
kind: 'origin' as const,
|
|
1010
|
+
value: 'https://github.com/Byte-Ventures/legacy-repository',
|
|
1011
|
+
};
|
|
1012
|
+
const prepared = await environment.admin.prepareRepositoryCube(cubeA, {
|
|
1013
|
+
name: 'Legacy Repository Cube',
|
|
1014
|
+
template: 'software-dev',
|
|
1015
|
+
});
|
|
1016
|
+
const resolveRequest = {
|
|
1017
|
+
working_repo_name: 'legacy-repository',
|
|
1018
|
+
repository,
|
|
1019
|
+
};
|
|
1020
|
+
const beforeNone = await environment.admin.observeAuthorityState();
|
|
1021
|
+
const noneResponse = await environment.operations.resolveRepositoryCube(
|
|
1022
|
+
credentialA,
|
|
1023
|
+
createProtocolEnvelope('repository-resolve-none', resolveRequest),
|
|
1024
|
+
);
|
|
1025
|
+
expectStatus(noneResponse, 200, 'Unassociated repository resolution');
|
|
1026
|
+
invariant(
|
|
1027
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }),
|
|
1028
|
+
'Unassociated repository did not return explicit none.',
|
|
1029
|
+
);
|
|
1030
|
+
assertStateDelta(beforeNone, await environment.admin.observeAuthorityState(), {}, 'Repository resolution');
|
|
1031
|
+
|
|
1032
|
+
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
1033
|
+
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
1034
|
+
expectSecretFreeError(
|
|
1035
|
+
await environment.operations.associateRepositoryCube(
|
|
1036
|
+
credentialB,
|
|
1037
|
+
createProtocolEnvelope('repository-associate-denied', associationRequest),
|
|
1038
|
+
),
|
|
1039
|
+
403,
|
|
1040
|
+
ErrorCode.ACCESS_DENIED,
|
|
1041
|
+
'Inaccessible repository cube association',
|
|
1042
|
+
[associationRequest.cube_id, repository.value],
|
|
1043
|
+
);
|
|
1044
|
+
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
1045
|
+
|
|
1046
|
+
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
1047
|
+
const associatedResponse = await environment.operations.associateRepositoryCube(
|
|
1048
|
+
credentialA,
|
|
1049
|
+
createProtocolEnvelope('repository-associate', associationRequest),
|
|
1050
|
+
);
|
|
1051
|
+
expectStatus(associatedResponse, 200, 'Explicit repository cube association');
|
|
1052
|
+
const associated = decodeAssociateRepositoryCubeResponseEnvelope(associatedResponse.body).payload;
|
|
1053
|
+
const authoritative = {
|
|
1054
|
+
result: 'resolved' as const,
|
|
1055
|
+
cube_id: prepared.cube_id,
|
|
1056
|
+
name: prepared.name,
|
|
1057
|
+
working_repo_name: resolveRequest.working_repo_name,
|
|
1058
|
+
repository,
|
|
1059
|
+
template: prepared.template,
|
|
1060
|
+
human_seat_role_id: prepared.human_seat_role_id,
|
|
1061
|
+
default_worker_role_id: prepared.default_worker_role_id,
|
|
1062
|
+
access: prepared.access,
|
|
1063
|
+
};
|
|
1064
|
+
invariant(same(associated, authoritative), 'Repository association did not return authoritative stored fields.');
|
|
1065
|
+
assertStateDelta(
|
|
1066
|
+
beforeAssociation,
|
|
1067
|
+
await environment.admin.observeAuthorityState(),
|
|
1068
|
+
{ repository_associations: 1 },
|
|
1069
|
+
'Explicit repository association',
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
const beforeResolve = await environment.admin.observeAuthorityState();
|
|
1073
|
+
const resolvedResponse = await environment.operations.resolveRepositoryCube(
|
|
1074
|
+
credentialA,
|
|
1075
|
+
createProtocolEnvelope('repository-resolve', {
|
|
1076
|
+
...resolveRequest,
|
|
1077
|
+
working_repo_name: 'ignored-new-display',
|
|
1078
|
+
}),
|
|
1079
|
+
);
|
|
1080
|
+
expectStatus(resolvedResponse, 200, 'Associated repository resolution');
|
|
1081
|
+
invariant(
|
|
1082
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative),
|
|
1083
|
+
'Associated repository did not return stored authoritative display.',
|
|
1084
|
+
);
|
|
1085
|
+
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
1086
|
+
|
|
1087
|
+
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
1088
|
+
const retryResponse = await environment.operations.associateRepositoryCube(
|
|
1089
|
+
credentialA,
|
|
1090
|
+
createProtocolEnvelope('repository-associate-retry', associationRequest),
|
|
1091
|
+
);
|
|
1092
|
+
expectStatus(retryResponse, 200, 'Exact repository association retry');
|
|
1093
|
+
invariant(
|
|
1094
|
+
same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative),
|
|
1095
|
+
'Exact repository association retry changed authoritative fields.',
|
|
1096
|
+
);
|
|
1097
|
+
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
1098
|
+
|
|
1099
|
+
const cubeC = await environment.admin.createCube('cube-c');
|
|
1100
|
+
await environment.admin.grantCube(principalA, cubeC);
|
|
1101
|
+
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
1102
|
+
name: 'Other Legacy Cube',
|
|
1103
|
+
template: 'starter',
|
|
1104
|
+
});
|
|
1105
|
+
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
1106
|
+
expectSecretFreeError(
|
|
1107
|
+
await environment.operations.associateRepositoryCube(
|
|
1108
|
+
credentialA,
|
|
1109
|
+
createProtocolEnvelope('repository-associate-repository-conflict', {
|
|
1110
|
+
...associationRequest,
|
|
1111
|
+
cube_id: cubeC.id,
|
|
1112
|
+
}),
|
|
1113
|
+
),
|
|
1114
|
+
409,
|
|
1115
|
+
ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
1116
|
+
'Repository-to-other-cube conflict',
|
|
1117
|
+
[cubeA.id, cubeC.id, repository.value],
|
|
1118
|
+
);
|
|
1119
|
+
assertStateDelta(
|
|
1120
|
+
beforeRepositoryConflict,
|
|
1121
|
+
await environment.admin.observeAuthorityState(),
|
|
1122
|
+
{},
|
|
1123
|
+
'Repository-to-other-cube conflict',
|
|
1124
|
+
);
|
|
1125
|
+
|
|
1126
|
+
const beforeCubeConflict = await environment.admin.observeAuthorityState();
|
|
1127
|
+
expectSecretFreeError(
|
|
1128
|
+
await environment.operations.associateRepositoryCube(
|
|
1129
|
+
credentialA,
|
|
1130
|
+
createProtocolEnvelope('repository-associate-cube-conflict', {
|
|
1131
|
+
cube_id: cubeA.id,
|
|
1132
|
+
working_repo_name: 'other-repository',
|
|
1133
|
+
repository: {
|
|
1134
|
+
kind: 'origin',
|
|
1135
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1136
|
+
},
|
|
1137
|
+
}),
|
|
1138
|
+
),
|
|
1139
|
+
409,
|
|
1140
|
+
ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
1141
|
+
'Cube-to-other-repository conflict',
|
|
1142
|
+
[cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository'],
|
|
1143
|
+
);
|
|
1144
|
+
assertStateDelta(
|
|
1145
|
+
beforeCubeConflict,
|
|
1146
|
+
await environment.admin.observeAuthorityState(),
|
|
1147
|
+
{},
|
|
1148
|
+
'Cube-to-other-repository conflict',
|
|
1149
|
+
);
|
|
1150
|
+
|
|
1151
|
+
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
1152
|
+
name: 'Other Client Legacy Cube',
|
|
1153
|
+
template: 'starter',
|
|
1154
|
+
});
|
|
1155
|
+
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
1156
|
+
const crossClientResolve = await environment.operations.resolveRepositoryCube(
|
|
1157
|
+
credentialB,
|
|
1158
|
+
createProtocolEnvelope('repository-resolve-cross-client', resolveRequest),
|
|
1159
|
+
);
|
|
1160
|
+
expectStatus(crossClientResolve, 200, 'Cross-client repository resolution');
|
|
1161
|
+
invariant(
|
|
1162
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }),
|
|
1163
|
+
'Another client repository binding was exposed by resolution.',
|
|
1164
|
+
);
|
|
1165
|
+
assertStateDelta(
|
|
1166
|
+
beforeCrossClientResolve,
|
|
1167
|
+
await environment.admin.observeAuthorityState(),
|
|
1168
|
+
{},
|
|
1169
|
+
'Cross-client repository resolution',
|
|
1170
|
+
);
|
|
1171
|
+
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
1172
|
+
const crossClientAssociation = await environment.operations.associateRepositoryCube(
|
|
1173
|
+
credentialB,
|
|
1174
|
+
createProtocolEnvelope('repository-associate-cross-client', {
|
|
1175
|
+
...associationRequest,
|
|
1176
|
+
cube_id: cubeB.id,
|
|
1177
|
+
}),
|
|
1178
|
+
);
|
|
1179
|
+
expectStatus(crossClientAssociation, 200, 'Cross-client repository association');
|
|
1180
|
+
const crossClientAssociated = decodeAssociateRepositoryCubeResponseEnvelope(
|
|
1181
|
+
crossClientAssociation.body,
|
|
1182
|
+
).payload;
|
|
1183
|
+
invariant(
|
|
1184
|
+
crossClientAssociated.cube_id === preparedB.cube_id &&
|
|
1185
|
+
crossClientAssociated.repository.value === repository.value,
|
|
1186
|
+
'Another client binding changed cross-client association identity.',
|
|
1187
|
+
);
|
|
1188
|
+
assertStateDelta(
|
|
1189
|
+
beforeCrossClientAssociation,
|
|
1190
|
+
await environment.admin.observeAuthorityState(),
|
|
1191
|
+
{ repository_associations: 1 },
|
|
1192
|
+
'Cross-client repository association',
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
1196
|
+
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
1197
|
+
const hiddenResolve = await environment.operations.resolveRepositoryCube(
|
|
1198
|
+
credentialA,
|
|
1199
|
+
createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest),
|
|
1200
|
+
);
|
|
1201
|
+
expectStatus(hiddenResolve, 200, 'Inaccessible repository binding resolution');
|
|
1202
|
+
invariant(
|
|
1203
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }),
|
|
1204
|
+
'Inaccessible repository binding was exposed by read-only resolution.',
|
|
1205
|
+
);
|
|
1206
|
+
assertStateDelta(
|
|
1207
|
+
beforeHiddenResolve,
|
|
1208
|
+
await environment.admin.observeAuthorityState(),
|
|
1209
|
+
{},
|
|
1210
|
+
'Inaccessible repository binding resolution',
|
|
1211
|
+
);
|
|
1212
|
+
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
1213
|
+
expectSecretFreeError(
|
|
1214
|
+
await environment.operations.associateRepositoryCube(
|
|
1215
|
+
credentialA,
|
|
1216
|
+
createProtocolEnvelope('repository-associate-inaccessible-binding', {
|
|
1217
|
+
...associationRequest,
|
|
1218
|
+
cube_id: cubeC.id,
|
|
1219
|
+
}),
|
|
1220
|
+
),
|
|
1221
|
+
403,
|
|
1222
|
+
ErrorCode.ACCESS_DENIED,
|
|
1223
|
+
'Inaccessible existing repository binding',
|
|
1224
|
+
[cubeA.id, cubeC.id, repository.value],
|
|
1225
|
+
);
|
|
1226
|
+
assertStateDelta(
|
|
1227
|
+
beforeHiddenAssociation,
|
|
1228
|
+
await environment.admin.observeAuthorityState(),
|
|
1229
|
+
{},
|
|
1230
|
+
'Inaccessible existing repository binding',
|
|
1231
|
+
);
|
|
1232
|
+
await environment.admin.grantCube(principalA, cubeA);
|
|
1233
|
+
return {
|
|
1234
|
+
none: true,
|
|
1235
|
+
resolved: true,
|
|
1236
|
+
idempotent: true,
|
|
1237
|
+
repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
1238
|
+
cube_conflict: ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
1239
|
+
inaccessible: ErrorCode.ACCESS_DENIED,
|
|
1240
|
+
inaccessible_binding_hidden: true,
|
|
1241
|
+
cross_client_binding_hidden: true,
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
|
|
984
1245
|
await record('security.adapter-boundary-injection', async () => {
|
|
985
1246
|
const injectedMessage = "'); DROP TABLE log_entries; --\r\ndata: forged-sse-frame";
|
|
986
1247
|
const injectedBody = JSON.stringify(
|
package/src/conformance/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { BroadcastHwm } from '../log-stream-hwm.js';
|
|
2
2
|
import type {
|
|
3
3
|
AttachResponse,
|
|
4
|
+
AssociateRepositoryCubeRequest,
|
|
4
5
|
CreateCubeRequest,
|
|
5
6
|
EnrollmentExchangeRequest,
|
|
7
|
+
ResolveRepositoryCubeRequest,
|
|
6
8
|
} from '../protocol/contract.js';
|
|
9
|
+
import type { EnrichedStreamEntry } from '../protocol/types.js';
|
|
7
10
|
|
|
8
11
|
export * from './adapter.js';
|
|
9
12
|
|
|
@@ -65,6 +68,138 @@ export const DRONE_ADDRESS_CONFORMANCE: readonly ConformanceVector<string, strin
|
|
|
65
68
|
},
|
|
66
69
|
];
|
|
67
70
|
|
|
71
|
+
const APPEND_LOG_ENTRY: EnrichedStreamEntry = {
|
|
72
|
+
id: '00000000-0000-4000-8000-000000000201',
|
|
73
|
+
cube_id: '00000000-0000-4000-8000-000000000202',
|
|
74
|
+
drone_id: '00000000-0000-4000-8000-000000000203',
|
|
75
|
+
message: 'REVIEW-READY: example',
|
|
76
|
+
visibility: 'direct',
|
|
77
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
78
|
+
drone_label: 'one-of-one-builder',
|
|
79
|
+
role_name: 'Builder',
|
|
80
|
+
recipient_drone_ids: ['00000000-0000-4000-8000-000000000204'],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const APPEND_LOG_ROUTING = {
|
|
84
|
+
class: 'review',
|
|
85
|
+
recipients: ['one-of-one-reviewer'],
|
|
86
|
+
fellOpen: false,
|
|
87
|
+
message: 'Routing applied.',
|
|
88
|
+
} as const;
|
|
89
|
+
|
|
90
|
+
export interface AppendLogResultConformanceVector {
|
|
91
|
+
name: string;
|
|
92
|
+
response: unknown;
|
|
93
|
+
accepts: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Runtime response vectors keep the append-log decoder aligned with its public type. */
|
|
97
|
+
export const APPEND_LOG_RESULT_CONFORMANCE: readonly AppendLogResultConformanceVector[] = [
|
|
98
|
+
{
|
|
99
|
+
name: 'accepts a response without optional routing metadata',
|
|
100
|
+
response: { entry: APPEND_LOG_ENTRY },
|
|
101
|
+
accepts: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'accepts exact routing metadata and unreachable recipients',
|
|
105
|
+
response: {
|
|
106
|
+
entry: APPEND_LOG_ENTRY,
|
|
107
|
+
routing: APPEND_LOG_ROUTING,
|
|
108
|
+
unreachableRecipients: [{ id: 'missing-reviewer', label: 'Missing Reviewer' }],
|
|
109
|
+
},
|
|
110
|
+
accepts: true,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'accepts a null routing echo and an empty unreachable-recipient list',
|
|
114
|
+
response: { entry: APPEND_LOG_ENTRY, routing: null, unreachableRecipients: [] },
|
|
115
|
+
accepts: true,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'rejects unknown top-level response fields',
|
|
119
|
+
response: { entry: APPEND_LOG_ENTRY, routing: APPEND_LOG_ROUTING, extra: true },
|
|
120
|
+
accepts: false,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'rejects a non-object routing echo',
|
|
124
|
+
response: { entry: APPEND_LOG_ENTRY, routing: 'review' },
|
|
125
|
+
accepts: false,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'rejects missing routing fields',
|
|
129
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { class: 'review' } },
|
|
130
|
+
accepts: false,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'rejects unknown routing fields',
|
|
134
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, extra: true } },
|
|
135
|
+
accepts: false,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'rejects an invalid routing class',
|
|
139
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, class: 7 } },
|
|
140
|
+
accepts: false,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'rejects an invalid routing recipient collection',
|
|
144
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, recipients: 'reviewer' } },
|
|
145
|
+
accepts: false,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'rejects invalid routing recipient members',
|
|
149
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, recipients: [7] } },
|
|
150
|
+
accepts: false,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'rejects an invalid routing fell-open flag',
|
|
154
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, fellOpen: 'false' } },
|
|
155
|
+
accepts: false,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'rejects an invalid routing message',
|
|
159
|
+
response: { entry: APPEND_LOG_ENTRY, routing: { ...APPEND_LOG_ROUTING, message: 7 } },
|
|
160
|
+
accepts: false,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'rejects a non-array unreachable-recipient list',
|
|
164
|
+
response: { entry: APPEND_LOG_ENTRY, unreachableRecipients: {} },
|
|
165
|
+
accepts: false,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'rejects a non-object unreachable recipient',
|
|
169
|
+
response: { entry: APPEND_LOG_ENTRY, unreachableRecipients: ['missing-reviewer'] },
|
|
170
|
+
accepts: false,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'rejects missing unreachable-recipient fields',
|
|
174
|
+
response: { entry: APPEND_LOG_ENTRY, unreachableRecipients: [{ id: 'missing-reviewer' }] },
|
|
175
|
+
accepts: false,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'rejects unknown unreachable-recipient fields',
|
|
179
|
+
response: {
|
|
180
|
+
entry: APPEND_LOG_ENTRY,
|
|
181
|
+
unreachableRecipients: [{ id: 'missing-reviewer', label: 'Missing Reviewer', extra: true }],
|
|
182
|
+
},
|
|
183
|
+
accepts: false,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'rejects an invalid unreachable-recipient id',
|
|
187
|
+
response: {
|
|
188
|
+
entry: APPEND_LOG_ENTRY,
|
|
189
|
+
unreachableRecipients: [{ id: 7, label: 'Missing Reviewer' }],
|
|
190
|
+
},
|
|
191
|
+
accepts: false,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'rejects an invalid unreachable-recipient label',
|
|
195
|
+
response: {
|
|
196
|
+
entry: APPEND_LOG_ENTRY,
|
|
197
|
+
unreachableRecipients: [{ id: 'missing-reviewer', label: null }],
|
|
198
|
+
},
|
|
199
|
+
accepts: false,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
|
|
68
203
|
export const ROLE_SECTION_ROUND_TRIP_CONFORMANCE: readonly string[] = [
|
|
69
204
|
'',
|
|
70
205
|
'Preamble only.',
|
|
@@ -189,6 +324,23 @@ const CREATE_CUBE_INITIAL: CreateCubeRequest = {
|
|
|
189
324
|
template: 'default',
|
|
190
325
|
};
|
|
191
326
|
|
|
327
|
+
export interface CubeTemplateAcceptanceConformanceVector {
|
|
328
|
+
name: string;
|
|
329
|
+
template: unknown;
|
|
330
|
+
accepts: boolean;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Protocol v6's complete closed acceptance set for cube-creation templates. */
|
|
334
|
+
export const CUBE_TEMPLATE_ACCEPTANCE_CONFORMANCE:
|
|
335
|
+
readonly CubeTemplateAcceptanceConformanceVector[] = [
|
|
336
|
+
{ name: 'accepts the legacy default template', template: 'default', accepts: true },
|
|
337
|
+
{ name: 'accepts the software-development template', template: 'software-dev', accepts: true },
|
|
338
|
+
{ name: 'accepts the starter template', template: 'starter', accepts: true },
|
|
339
|
+
{ name: 'accepts the local-model template', template: 'local-model', accepts: true },
|
|
340
|
+
{ name: 'rejects an unknown template name', template: 'custom', accepts: false },
|
|
341
|
+
{ name: 'rejects a non-string template', template: null, accepts: false },
|
|
342
|
+
];
|
|
343
|
+
|
|
192
344
|
/** Stateful vectors: name, repository identity, and template bind the retry key. */
|
|
193
345
|
export const CREATE_CUBE_RETRY_CONFORMANCE: readonly CreateCubeRetryConformanceVector[] = [
|
|
194
346
|
{
|
|
@@ -291,6 +443,150 @@ readonly CreateCubeAssociationConformanceVector[] = [
|
|
|
291
443
|
},
|
|
292
444
|
];
|
|
293
445
|
|
|
446
|
+
export interface ResolveRepositoryCubeConformanceVector {
|
|
447
|
+
name: string;
|
|
448
|
+
request: ResolveRepositoryCubeRequest;
|
|
449
|
+
associated: boolean;
|
|
450
|
+
expected:
|
|
451
|
+
| { outcome: 'none'; status: 200; authority_state_delta: Record<string, never> }
|
|
452
|
+
| { outcome: 'resolved'; status: 200; authority_state_delta: Record<string, never> };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const REPOSITORY_CUBE_ONE = '00000000-0000-4000-8000-000000000131';
|
|
456
|
+
const REPOSITORY_ONE: ResolveRepositoryCubeRequest = {
|
|
457
|
+
working_repo_name: 'repository-one',
|
|
458
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-one' },
|
|
459
|
+
};
|
|
460
|
+
const REPOSITORY_TWO: ResolveRepositoryCubeRequest = {
|
|
461
|
+
working_repo_name: 'repository-two',
|
|
462
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-two' },
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
/** Resolution is read-only and returns only explicit none or authoritative stored fields. */
|
|
466
|
+
export const RESOLVE_REPOSITORY_CUBE_CONFORMANCE:
|
|
467
|
+
readonly ResolveRepositoryCubeConformanceVector[] = [
|
|
468
|
+
{
|
|
469
|
+
name: 'unassociated repository resolves explicit none without mutation',
|
|
470
|
+
request: REPOSITORY_ONE,
|
|
471
|
+
associated: false,
|
|
472
|
+
expected: { outcome: 'none', status: 200, authority_state_delta: {} },
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'associated repository resolves authoritative stored fields without mutation',
|
|
476
|
+
request: { ...REPOSITORY_ONE, working_repo_name: 'ignored-new-display' },
|
|
477
|
+
associated: true,
|
|
478
|
+
expected: { outcome: 'resolved', status: 200, authority_state_delta: {} },
|
|
479
|
+
},
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
export interface AssociateRepositoryCubeConformanceVector {
|
|
483
|
+
name: string;
|
|
484
|
+
initial: AssociateRepositoryCubeRequest;
|
|
485
|
+
retry: AssociateRepositoryCubeRequest;
|
|
486
|
+
expected:
|
|
487
|
+
| {
|
|
488
|
+
outcome: 'resolved';
|
|
489
|
+
status: 200;
|
|
490
|
+
initial_authority_state_delta: { repository_associations: 1 };
|
|
491
|
+
retry_authority_state_delta: Record<string, never>;
|
|
492
|
+
}
|
|
493
|
+
| {
|
|
494
|
+
outcome: 'repository_conflict' | 'cube_conflict';
|
|
495
|
+
status: 409;
|
|
496
|
+
error: 'REPOSITORY_ALREADY_ASSOCIATED' | 'CUBE_ALREADY_ASSOCIATED';
|
|
497
|
+
diagnostic_disclosure: 'none';
|
|
498
|
+
retry_authority_state_delta: Record<string, never>;
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Explicit cube IDs bind atomically; names never select or identify a cube. */
|
|
503
|
+
export const ASSOCIATE_REPOSITORY_CUBE_CONFORMANCE:
|
|
504
|
+
readonly AssociateRepositoryCubeConformanceVector[] = [
|
|
505
|
+
{
|
|
506
|
+
name: 'same explicit cube and repository binding is idempotent',
|
|
507
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
508
|
+
retry: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
509
|
+
expected: {
|
|
510
|
+
outcome: 'resolved',
|
|
511
|
+
status: 200,
|
|
512
|
+
initial_authority_state_delta: { repository_associations: 1 },
|
|
513
|
+
retry_authority_state_delta: {},
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: 'repository already bound to another cube conflicts without mutation',
|
|
518
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
519
|
+
retry: { cube_id: '00000000-0000-4000-8000-000000000132', ...REPOSITORY_ONE },
|
|
520
|
+
expected: {
|
|
521
|
+
outcome: 'repository_conflict',
|
|
522
|
+
status: 409,
|
|
523
|
+
error: 'REPOSITORY_ALREADY_ASSOCIATED',
|
|
524
|
+
diagnostic_disclosure: 'none',
|
|
525
|
+
retry_authority_state_delta: {},
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'cube already bound to another repository conflicts without mutation',
|
|
530
|
+
initial: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
531
|
+
retry: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_TWO },
|
|
532
|
+
expected: {
|
|
533
|
+
outcome: 'cube_conflict',
|
|
534
|
+
status: 409,
|
|
535
|
+
error: 'CUBE_ALREADY_ASSOCIATED',
|
|
536
|
+
diagnostic_disclosure: 'none',
|
|
537
|
+
retry_authority_state_delta: {},
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
];
|
|
541
|
+
|
|
542
|
+
export const REPOSITORY_CUBE_PERMISSION_CONFORMANCE = [
|
|
543
|
+
{
|
|
544
|
+
name: 'association denies an inaccessible explicit cube without mutation',
|
|
545
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
546
|
+
expected: { status: 403, error: 'ACCESS_DENIED', authority_state_delta: {} },
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: 'same-client binding to an inaccessible cube is non-enumerating',
|
|
550
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
551
|
+
precondition: 'repository_bound_to_inaccessible_cube',
|
|
552
|
+
expected: {
|
|
553
|
+
resolve: { status: 200, outcome: 'none', authority_state_delta: {} },
|
|
554
|
+
associate: {
|
|
555
|
+
status: 403,
|
|
556
|
+
error: 'ACCESS_DENIED',
|
|
557
|
+
diagnostic_disclosure: 'none',
|
|
558
|
+
authority_state_delta: {},
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: 'another client binding is neither resolved nor treated as a conflict',
|
|
564
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
565
|
+
precondition: 'repository_bound_by_another_client',
|
|
566
|
+
expected: {
|
|
567
|
+
resolve: { status: 200, outcome: 'none', authority_state_delta: {} },
|
|
568
|
+
associate: {
|
|
569
|
+
status: 200,
|
|
570
|
+
outcome: 'resolved',
|
|
571
|
+
authority_state_delta: { repository_associations: 1 },
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
] as const;
|
|
576
|
+
|
|
577
|
+
export const REPOSITORY_CUBE_AUTHORITATIVE_STATE_CONFORMANCE = [
|
|
578
|
+
{
|
|
579
|
+
name: 'legacy cube with invalid authoritative roles is rejected without mutation',
|
|
580
|
+
request: { cube_id: REPOSITORY_CUBE_ONE, ...REPOSITORY_ONE },
|
|
581
|
+
expected: {
|
|
582
|
+
status: 409,
|
|
583
|
+
error: 'INVALID_INPUT',
|
|
584
|
+
diagnostic_disclosure: 'none',
|
|
585
|
+
authority_state_delta: {},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
] as const;
|
|
589
|
+
|
|
294
590
|
export const ENROLLMENT_AUTHORITY_CONFORMANCE = [
|
|
295
591
|
{
|
|
296
592
|
name: 'ordinary enrollment creates no authority or cube state',
|