borgmcp-shared 0.4.0 → 0.4.3
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 +29 -16
- package/SECURITY.md +6 -5
- package/dist/conformance/adapter.d.ts +58 -8
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +260 -14
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/protocol/contract.d.ts +17 -1
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +17 -1
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/coordination.d.ts +25 -0
- package/dist/protocol/coordination.d.ts.map +1 -1
- package/dist/protocol/coordination.js +44 -0
- package/dist/protocol/coordination.js.map +1 -1
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js.map +1 -1
- package/dist/templates.d.ts +3 -2
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +45 -40
- package/dist/templates.js.map +1 -1
- package/docs/enrollment.md +19 -15
- package/docs/releasing.md +119 -284
- package/package.json +2 -2
- package/src/conformance/adapter.ts +589 -26
- package/src/protocol/contract.ts +17 -1
- package/src/protocol/coordination.ts +82 -0
- package/src/protocol/errors.ts +3 -2
- package/src/templates.ts +51 -45
|
@@ -7,11 +7,15 @@ import {
|
|
|
7
7
|
decodeDecisionResultEnvelope,
|
|
8
8
|
decodeDecisionsResultEnvelope,
|
|
9
9
|
decodeEnrollmentExchangeResponseEnvelope,
|
|
10
|
+
decodeEvictDroneResultEnvelope,
|
|
11
|
+
decodeProtocolEnvelope,
|
|
10
12
|
decodeProtocolErrorEnvelope,
|
|
11
13
|
decodeProtocolTagPreflight,
|
|
12
14
|
decodeReadLogResultEnvelope,
|
|
15
|
+
decodeReassignDroneResultEnvelope,
|
|
13
16
|
decodeSseFrames,
|
|
14
17
|
PROTOCOL_LIMIT_CEILINGS,
|
|
18
|
+
PROTOCOL_HTTP_CONTRACT,
|
|
15
19
|
PROTOCOL_VERSION,
|
|
16
20
|
utf8ByteLength,
|
|
17
21
|
type CreateCubeResponse,
|
|
@@ -33,6 +37,29 @@ export interface ConformanceCube {
|
|
|
33
37
|
readonly id: string;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
export interface ConformanceRole {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ConformanceDrone {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type ConformanceCubeAccess = 'read' | 'write' | 'manage';
|
|
49
|
+
|
|
50
|
+
export interface ConformanceCubeManagementState {
|
|
51
|
+
readonly directive: string;
|
|
52
|
+
readonly taxonomy_marker: string | null;
|
|
53
|
+
readonly role_ids: readonly string[];
|
|
54
|
+
readonly active_decision_ids: readonly string[];
|
|
55
|
+
readonly drones: ReadonlyArray<{
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly role_id: string;
|
|
58
|
+
readonly evicted: boolean;
|
|
59
|
+
readonly session_revoked: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
export interface ConformanceAuthorityState {
|
|
37
64
|
enrolled_clients: number;
|
|
38
65
|
enrollment_claims: number;
|
|
@@ -83,7 +110,31 @@ export interface ConformanceAdmin {
|
|
|
83
110
|
reset(): Promise<void>;
|
|
84
111
|
createPrincipal(name: string): Promise<ConformancePrincipal>;
|
|
85
112
|
createCube(name: string): Promise<ConformanceCube>;
|
|
86
|
-
|
|
113
|
+
/** Grants the requested cube authority; omitted access defaults to manage. */
|
|
114
|
+
grantCube(
|
|
115
|
+
principal: ConformancePrincipal,
|
|
116
|
+
cube: ConformanceCube,
|
|
117
|
+
access?: ConformanceCubeAccess,
|
|
118
|
+
): Promise<void>;
|
|
119
|
+
createRole(cube: ConformanceCube, input: {
|
|
120
|
+
readonly roleClass: 'queen' | 'worker';
|
|
121
|
+
readonly isHumanSeat: boolean;
|
|
122
|
+
}): Promise<ConformanceRole>;
|
|
123
|
+
createDrone(
|
|
124
|
+
principal: ConformancePrincipal,
|
|
125
|
+
cube: ConformanceCube,
|
|
126
|
+
role: ConformanceRole,
|
|
127
|
+
): Promise<ConformanceDrone>;
|
|
128
|
+
issueManagedDroneSession(drone: ConformanceDrone): Promise<string>;
|
|
129
|
+
revokeManagedDroneSession(drone: ConformanceDrone): Promise<void>;
|
|
130
|
+
expireManagedDroneSession(drone: ConformanceDrone): Promise<void>;
|
|
131
|
+
inspectManagedDrone(drone: ConformanceDrone): Promise<{
|
|
132
|
+
readonly role_id: string;
|
|
133
|
+
readonly evicted: boolean;
|
|
134
|
+
readonly session_revoked: boolean;
|
|
135
|
+
}>;
|
|
136
|
+
/** Observes every cube field mutated by a represented manage-scoped operation. */
|
|
137
|
+
inspectCubeManagementState(cube: ConformanceCube): Promise<ConformanceCubeManagementState>;
|
|
87
138
|
grantCreateCubeCapability(principal: ConformancePrincipal): Promise<void>;
|
|
88
139
|
issueDroneSession(principal: ConformancePrincipal): Promise<string>;
|
|
89
140
|
issueSingleUseInvitation(principal: ConformancePrincipal, purpose: 'owner' | 'client'): Promise<string>;
|
|
@@ -128,6 +179,21 @@ export interface ConformanceOperations {
|
|
|
128
179
|
cube: ConformanceCube,
|
|
129
180
|
request: unknown,
|
|
130
181
|
): Promise<ConformanceHttpResponse>;
|
|
182
|
+
updateCube(
|
|
183
|
+
credential: string,
|
|
184
|
+
cube: ConformanceCube,
|
|
185
|
+
request: unknown,
|
|
186
|
+
): Promise<ConformanceHttpResponse>;
|
|
187
|
+
createRole(
|
|
188
|
+
credential: string,
|
|
189
|
+
cube: ConformanceCube,
|
|
190
|
+
request: unknown,
|
|
191
|
+
): Promise<ConformanceHttpResponse>;
|
|
192
|
+
patchTaxonomy(
|
|
193
|
+
credential: string,
|
|
194
|
+
cube: ConformanceCube,
|
|
195
|
+
request: unknown,
|
|
196
|
+
): Promise<ConformanceHttpResponse>;
|
|
131
197
|
recordDecision(
|
|
132
198
|
credential: string,
|
|
133
199
|
cube: ConformanceCube,
|
|
@@ -138,6 +204,22 @@ export interface ConformanceOperations {
|
|
|
138
204
|
cube: ConformanceCube,
|
|
139
205
|
request: unknown,
|
|
140
206
|
): Promise<ConformanceHttpResponse>;
|
|
207
|
+
listDrones(
|
|
208
|
+
credential: string,
|
|
209
|
+
cube: ConformanceCube,
|
|
210
|
+
): Promise<ConformanceHttpResponse>;
|
|
211
|
+
reassignDrone(
|
|
212
|
+
credential: string,
|
|
213
|
+
cube: ConformanceCube,
|
|
214
|
+
drone: ConformanceDrone,
|
|
215
|
+
request: unknown,
|
|
216
|
+
): Promise<ConformanceHttpResponse>;
|
|
217
|
+
evictDrone(
|
|
218
|
+
credential: string,
|
|
219
|
+
cube: ConformanceCube,
|
|
220
|
+
drone: ConformanceDrone,
|
|
221
|
+
request: unknown,
|
|
222
|
+
): Promise<ConformanceHttpResponse>;
|
|
141
223
|
openStream(
|
|
142
224
|
credential: string,
|
|
143
225
|
cube: ConformanceCube,
|
|
@@ -163,6 +245,12 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
163
245
|
{ id: 'acks.idempotent', area: 'acks' },
|
|
164
246
|
{ id: 'claims.durable-noncursor', area: 'claims' },
|
|
165
247
|
{ id: 'decisions.topic-supersession', area: 'decisions' },
|
|
248
|
+
{ id: 'security.drone-management-authorization', area: 'security' },
|
|
249
|
+
{ id: 'security.manage-access-matrix', area: 'security' },
|
|
250
|
+
{ id: 'drones.reassign-invariants', area: 'drones' },
|
|
251
|
+
{ id: 'security.cross-cube-drone-management', area: 'security' },
|
|
252
|
+
{ id: 'drones.evict-terminal-signal', area: 'drones' },
|
|
253
|
+
{ id: 'security.drone-session-rejection-causes', area: 'security' },
|
|
166
254
|
{ id: 'security.active-stream-revocation', area: 'security' },
|
|
167
255
|
] as const;
|
|
168
256
|
|
|
@@ -186,13 +274,6 @@ export interface AdapterConformanceReport {
|
|
|
186
274
|
}>;
|
|
187
275
|
}
|
|
188
276
|
|
|
189
|
-
export interface EquivalentAdapterConformanceReport {
|
|
190
|
-
ok: boolean;
|
|
191
|
-
cloud: AdapterConformanceReport;
|
|
192
|
-
local: AdapterConformanceReport;
|
|
193
|
-
equivalent: boolean;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
277
|
export interface AdapterConformanceOptions {
|
|
197
278
|
/** Maximum wait for a replay, live delivery, or revocation close. */
|
|
198
279
|
streamDeadlineMs?: number;
|
|
@@ -338,6 +419,20 @@ function same(left: unknown, right: unknown): boolean {
|
|
|
338
419
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
339
420
|
}
|
|
340
421
|
|
|
422
|
+
function listedDroneIds(response: ConformanceHttpResponse): string[] {
|
|
423
|
+
return decodeProtocolEnvelope(response.body, (payload) => {
|
|
424
|
+
invariant(typeof payload === 'object' && payload !== null, 'Roster payload is not an object.');
|
|
425
|
+
const drones = (payload as { drones?: unknown }).drones;
|
|
426
|
+
invariant(Array.isArray(drones), 'Roster payload omitted drones.');
|
|
427
|
+
return drones.map((drone) => {
|
|
428
|
+
invariant(typeof drone === 'object' && drone !== null, 'Roster contains an invalid drone.');
|
|
429
|
+
const id = (drone as { id?: unknown }).id;
|
|
430
|
+
invariant(typeof id === 'string', 'Roster drone omitted its id.');
|
|
431
|
+
return id;
|
|
432
|
+
});
|
|
433
|
+
}).payload;
|
|
434
|
+
}
|
|
435
|
+
|
|
341
436
|
function assertStateDelta(
|
|
342
437
|
before: ConformanceAuthorityState,
|
|
343
438
|
after: ConformanceAuthorityState,
|
|
@@ -985,6 +1080,492 @@ export async function runAdapterConformance(
|
|
|
985
1080
|
return { active_count: 1, active_decision: 'second', supersedes_first: true };
|
|
986
1081
|
});
|
|
987
1082
|
|
|
1083
|
+
let workerRoleA!: ConformanceRole;
|
|
1084
|
+
let workerRoleB!: ConformanceRole;
|
|
1085
|
+
let managedWorker!: ConformanceDrone;
|
|
1086
|
+
let readCredential = '';
|
|
1087
|
+
let writeCredential = '';
|
|
1088
|
+
let workerSession = '';
|
|
1089
|
+
await record('security.drone-management-authorization', async () => {
|
|
1090
|
+
workerRoleA = await environment.admin.createRole(cubeA, {
|
|
1091
|
+
roleClass: 'worker', isHumanSeat: false,
|
|
1092
|
+
});
|
|
1093
|
+
managedWorker = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1094
|
+
const deniedCredentials: Array<{ access: 'read' | 'write'; credential: string }> = [];
|
|
1095
|
+
for (const [index, access] of ['read', 'write'].entries()) {
|
|
1096
|
+
const principalName = access === 'read' ? 'Coordinator' : 'write-principal';
|
|
1097
|
+
const principal = await environment.admin.createPrincipal(principalName);
|
|
1098
|
+
await environment.admin.grantCube(principal, cubeA, access as 'read' | 'write');
|
|
1099
|
+
const invitation = await environment.admin.issueSingleUseInvitation(principal, 'client');
|
|
1100
|
+
const credential = `${(access === 'read' ? 'R' : 'W').repeat(42)}Q`;
|
|
1101
|
+
if (access === 'read') readCredential = credential;
|
|
1102
|
+
else writeCredential = credential;
|
|
1103
|
+
const enrollment = await environment.operations.enroll(createProtocolEnvelope(
|
|
1104
|
+
`${access}-principal-enroll`,
|
|
1105
|
+
{
|
|
1106
|
+
invitation,
|
|
1107
|
+
retry_key: `00000000-0000-4000-8000-${String(301 + index).padStart(12, '0')}`,
|
|
1108
|
+
client_credential: credential,
|
|
1109
|
+
client_name: principalName,
|
|
1110
|
+
},
|
|
1111
|
+
));
|
|
1112
|
+
expectStatus(enrollment, 201, `${access} principal enrollment`);
|
|
1113
|
+
deniedCredentials.push({ access: access as 'read' | 'write', credential });
|
|
1114
|
+
}
|
|
1115
|
+
for (const { access, credential } of deniedCredentials) {
|
|
1116
|
+
expectError(
|
|
1117
|
+
await environment.operations.reassignDrone(
|
|
1118
|
+
credential,
|
|
1119
|
+
cubeA,
|
|
1120
|
+
managedWorker,
|
|
1121
|
+
createProtocolEnvelope(`${access}-reassign-denied`, { role_id: workerRoleA.id }),
|
|
1122
|
+
),
|
|
1123
|
+
403,
|
|
1124
|
+
ErrorCode.ACCESS_DENIED,
|
|
1125
|
+
`${access} principal reassignment`,
|
|
1126
|
+
);
|
|
1127
|
+
expectError(
|
|
1128
|
+
await environment.operations.evictDrone(
|
|
1129
|
+
credential,
|
|
1130
|
+
cubeA,
|
|
1131
|
+
managedWorker,
|
|
1132
|
+
createProtocolEnvelope(`${access}-evict-denied`, {}),
|
|
1133
|
+
),
|
|
1134
|
+
403,
|
|
1135
|
+
ErrorCode.ACCESS_DENIED,
|
|
1136
|
+
`${access} principal eviction`,
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
return { read_status: 403, write_status: 403, manage_required: true };
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
await record('security.manage-access-matrix', async () => {
|
|
1143
|
+
const noGrantPrincipal = await environment.admin.createPrincipal('Coordinator role without cube grant');
|
|
1144
|
+
const noGrantInvitation = await environment.admin.issueSingleUseInvitation(noGrantPrincipal, 'client');
|
|
1145
|
+
const noGrantCredential = `${'N'.repeat(42)}Q`;
|
|
1146
|
+
expectStatus(await environment.operations.enroll(createProtocolEnvelope(
|
|
1147
|
+
'no-grant-principal-enroll',
|
|
1148
|
+
{
|
|
1149
|
+
invitation: noGrantInvitation,
|
|
1150
|
+
retry_key: '00000000-0000-4000-8000-000000000303',
|
|
1151
|
+
client_credential: noGrantCredential,
|
|
1152
|
+
client_name: 'Coordinator',
|
|
1153
|
+
},
|
|
1154
|
+
)), 201, 'No-grant principal enrollment');
|
|
1155
|
+
|
|
1156
|
+
const droneCredential = await environment.admin.issueManagedDroneSession(managedWorker);
|
|
1157
|
+
const matrixTargetRole = await environment.admin.createRole(cubeA, {
|
|
1158
|
+
roleClass: 'worker', isHumanSeat: false,
|
|
1159
|
+
});
|
|
1160
|
+
const evictionTarget = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1161
|
+
await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
1162
|
+
const unknownCube = { id: '00000000-0000-4000-8000-000000000399' };
|
|
1163
|
+
const snapshot = async (): Promise<unknown> => ({
|
|
1164
|
+
cubeA: await environment.admin.inspectCubeManagementState(cubeA),
|
|
1165
|
+
cubeB: await environment.admin.inspectCubeManagementState(cubeB),
|
|
1166
|
+
});
|
|
1167
|
+
const operations: ReadonlyArray<{
|
|
1168
|
+
name: string;
|
|
1169
|
+
successStatus: number;
|
|
1170
|
+
invoke: (credential: string, cube: ConformanceCube) => Promise<ConformanceHttpResponse>;
|
|
1171
|
+
}> = [
|
|
1172
|
+
{
|
|
1173
|
+
name: 'cube-update',
|
|
1174
|
+
successStatus: 200,
|
|
1175
|
+
invoke: (credential, cube) => environment.operations.updateCube(
|
|
1176
|
+
credential,
|
|
1177
|
+
cube,
|
|
1178
|
+
createProtocolEnvelope('matrix-cube-update', { cube_directive: 'matrix-directive' }),
|
|
1179
|
+
),
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
name: 'role-create',
|
|
1183
|
+
successStatus: 201,
|
|
1184
|
+
invoke: (credential, cube) => environment.operations.createRole(
|
|
1185
|
+
credential,
|
|
1186
|
+
cube,
|
|
1187
|
+
createProtocolEnvelope('matrix-role-create', { name: 'Matrix Role' }),
|
|
1188
|
+
),
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
name: 'taxonomy-patch',
|
|
1192
|
+
successStatus: 200,
|
|
1193
|
+
invoke: (credential, cube) => environment.operations.patchTaxonomy(
|
|
1194
|
+
credential,
|
|
1195
|
+
cube,
|
|
1196
|
+
createProtocolEnvelope('matrix-taxonomy-patch', { marker: 'matrix-taxonomy' }),
|
|
1197
|
+
),
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: 'decision-record',
|
|
1201
|
+
successStatus: 201,
|
|
1202
|
+
invoke: (credential, cube) => environment.operations.recordDecision(
|
|
1203
|
+
credential,
|
|
1204
|
+
cube,
|
|
1205
|
+
createProtocolEnvelope('matrix-decision-record', {
|
|
1206
|
+
topic: 'matrix-authority', decision: 'manage only',
|
|
1207
|
+
}),
|
|
1208
|
+
),
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
name: 'drone-reassign',
|
|
1212
|
+
successStatus: 200,
|
|
1213
|
+
invoke: (credential, cube) => environment.operations.reassignDrone(
|
|
1214
|
+
credential,
|
|
1215
|
+
cube,
|
|
1216
|
+
managedWorker,
|
|
1217
|
+
createProtocolEnvelope('matrix-drone-reassign', { role_id: matrixTargetRole.id }),
|
|
1218
|
+
),
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
name: 'drone-evict',
|
|
1222
|
+
successStatus: 200,
|
|
1223
|
+
invoke: (credential, cube) => environment.operations.evictDrone(
|
|
1224
|
+
credential,
|
|
1225
|
+
cube,
|
|
1226
|
+
evictionTarget,
|
|
1227
|
+
createProtocolEnvelope('matrix-drone-evict', {}),
|
|
1228
|
+
),
|
|
1229
|
+
},
|
|
1230
|
+
];
|
|
1231
|
+
|
|
1232
|
+
for (const operation of operations) {
|
|
1233
|
+
for (const [kind, credential] of [
|
|
1234
|
+
['read', readCredential],
|
|
1235
|
+
['write', writeCredential],
|
|
1236
|
+
['drone-session', droneCredential],
|
|
1237
|
+
] as const) {
|
|
1238
|
+
const before = await snapshot();
|
|
1239
|
+
expectError(
|
|
1240
|
+
await operation.invoke(credential, cubeA),
|
|
1241
|
+
403,
|
|
1242
|
+
ErrorCode.ACCESS_DENIED,
|
|
1243
|
+
`${operation.name} by ${kind} principal`,
|
|
1244
|
+
);
|
|
1245
|
+
invariant(same(await snapshot(), before), `${operation.name} mutated state after ${kind} denial.`);
|
|
1246
|
+
}
|
|
1247
|
+
for (const [kind, credential, cube] of [
|
|
1248
|
+
['no-grant', noGrantCredential, cubeA],
|
|
1249
|
+
['foreign-principal', credentialB, cubeA],
|
|
1250
|
+
['foreign-cube', credentialA, cubeB],
|
|
1251
|
+
['unknown-cube', credentialA, unknownCube],
|
|
1252
|
+
] as const) {
|
|
1253
|
+
const before = await snapshot();
|
|
1254
|
+
expectError(
|
|
1255
|
+
await operation.invoke(credential, cube),
|
|
1256
|
+
404,
|
|
1257
|
+
ErrorCode.NOT_FOUND,
|
|
1258
|
+
`${operation.name} against ${kind}`,
|
|
1259
|
+
);
|
|
1260
|
+
invariant(same(await snapshot(), before), `${operation.name} mutated state after ${kind} denial.`);
|
|
1261
|
+
}
|
|
1262
|
+
const beforeSuccess = await environment.admin.inspectCubeManagementState(cubeA);
|
|
1263
|
+
const success = await operation.invoke(credentialA, cubeA);
|
|
1264
|
+
expectStatus(success, operation.successStatus, `${operation.name} by managing principal`);
|
|
1265
|
+
invariant(
|
|
1266
|
+
!same(await environment.admin.inspectCubeManagementState(cubeA), beforeSuccess),
|
|
1267
|
+
`${operation.name} managing success did not mutate its declared state.`,
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
operation_count: operations.length,
|
|
1272
|
+
manage_success: true,
|
|
1273
|
+
read_write_status: 403,
|
|
1274
|
+
drone_session_status: 403,
|
|
1275
|
+
hidden_status: 404,
|
|
1276
|
+
denied_mutations: 0,
|
|
1277
|
+
role_labels_authoritative: false,
|
|
1278
|
+
};
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
await record('drones.reassign-invariants', async () => {
|
|
1282
|
+
workerRoleB = await environment.admin.createRole(cubeA, {
|
|
1283
|
+
roleClass: 'worker', isHumanSeat: false,
|
|
1284
|
+
});
|
|
1285
|
+
const humanSourceRole = await environment.admin.createRole(cubeA, {
|
|
1286
|
+
roleClass: 'worker', isHumanSeat: true,
|
|
1287
|
+
});
|
|
1288
|
+
const queenRole = await environment.admin.createRole(cubeA, {
|
|
1289
|
+
roleClass: 'queen', isHumanSeat: false,
|
|
1290
|
+
});
|
|
1291
|
+
const occupiedHumanRole = await environment.admin.createRole(cubeA, {
|
|
1292
|
+
roleClass: 'worker', isHumanSeat: true,
|
|
1293
|
+
});
|
|
1294
|
+
const humanSource = await environment.admin.createDrone(principalA, cubeA, humanSourceRole);
|
|
1295
|
+
await environment.admin.createDrone(principalA, cubeA, occupiedHumanRole);
|
|
1296
|
+
const contender = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1297
|
+
workerSession = await environment.admin.issueManagedDroneSession(managedWorker);
|
|
1298
|
+
|
|
1299
|
+
const reassigned = await environment.operations.reassignDrone(
|
|
1300
|
+
credentialA,
|
|
1301
|
+
cubeA,
|
|
1302
|
+
managedWorker,
|
|
1303
|
+
createProtocolEnvelope('reassign-worker', { role_id: workerRoleB.id }),
|
|
1304
|
+
);
|
|
1305
|
+
expectStatus(reassigned, 200, 'Worker reassignment');
|
|
1306
|
+
const reassignedDrone = decodeReassignDroneResultEnvelope(reassigned.body).payload.drone;
|
|
1307
|
+
invariant(
|
|
1308
|
+
reassignedDrone.id === managedWorker.id && reassignedDrone.role_id === workerRoleB.id,
|
|
1309
|
+
'Reassignment response did not identify the persisted target role.',
|
|
1310
|
+
);
|
|
1311
|
+
|
|
1312
|
+
expectError(
|
|
1313
|
+
await environment.operations.reassignDrone(
|
|
1314
|
+
workerSession,
|
|
1315
|
+
cubeA,
|
|
1316
|
+
managedWorker,
|
|
1317
|
+
createProtocolEnvelope('reassign-drone-session-denied', { role_id: workerRoleA.id }),
|
|
1318
|
+
),
|
|
1319
|
+
403,
|
|
1320
|
+
ErrorCode.ACCESS_DENIED,
|
|
1321
|
+
'Drone-session reassignment',
|
|
1322
|
+
);
|
|
1323
|
+
expectError(
|
|
1324
|
+
await environment.operations.reassignDrone(
|
|
1325
|
+
credentialA,
|
|
1326
|
+
cubeA,
|
|
1327
|
+
managedWorker,
|
|
1328
|
+
createProtocolEnvelope('reassign-queen-denied', { role_id: queenRole.id }),
|
|
1329
|
+
),
|
|
1330
|
+
403,
|
|
1331
|
+
ErrorCode.ACCESS_DENIED,
|
|
1332
|
+
'Worker-to-queen reassignment',
|
|
1333
|
+
);
|
|
1334
|
+
const promoted = await environment.operations.reassignDrone(
|
|
1335
|
+
credentialA,
|
|
1336
|
+
cubeA,
|
|
1337
|
+
humanSource,
|
|
1338
|
+
createProtocolEnvelope('reassign-queen-allowed', { role_id: queenRole.id }),
|
|
1339
|
+
);
|
|
1340
|
+
expectStatus(promoted, 200, 'Human-seat-to-queen reassignment');
|
|
1341
|
+
invariant(
|
|
1342
|
+
decodeReassignDroneResultEnvelope(promoted.body).payload.drone.role_id === queenRole.id,
|
|
1343
|
+
'Human-seat-to-queen reassignment did not persist.',
|
|
1344
|
+
);
|
|
1345
|
+
expectError(
|
|
1346
|
+
await environment.operations.reassignDrone(
|
|
1347
|
+
credentialA,
|
|
1348
|
+
cubeA,
|
|
1349
|
+
contender,
|
|
1350
|
+
createProtocolEnvelope('reassign-occupied-denied', { role_id: occupiedHumanRole.id }),
|
|
1351
|
+
),
|
|
1352
|
+
409,
|
|
1353
|
+
ErrorCode.ROLE_IN_USE,
|
|
1354
|
+
'Occupied human-seat reassignment',
|
|
1355
|
+
);
|
|
1356
|
+
return {
|
|
1357
|
+
worker_reassigned: true,
|
|
1358
|
+
drone_session_denied: true,
|
|
1359
|
+
queen_requires_human_seat: true,
|
|
1360
|
+
occupied_human_seat_denied: true,
|
|
1361
|
+
};
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
await record('security.cross-cube-drone-management', async () => {
|
|
1365
|
+
const foreignRole = await environment.admin.createRole(cubeB, {
|
|
1366
|
+
roleClass: 'worker', isHumanSeat: false,
|
|
1367
|
+
});
|
|
1368
|
+
const foreignDrone = await environment.admin.createDrone(principalB, cubeB, foreignRole);
|
|
1369
|
+
expectError(
|
|
1370
|
+
await environment.operations.reassignDrone(
|
|
1371
|
+
credentialA,
|
|
1372
|
+
cubeB,
|
|
1373
|
+
foreignDrone,
|
|
1374
|
+
createProtocolEnvelope('cross-cube-reassign', { role_id: foreignRole.id }),
|
|
1375
|
+
),
|
|
1376
|
+
404,
|
|
1377
|
+
ErrorCode.NOT_FOUND,
|
|
1378
|
+
'Cross-cube reassignment',
|
|
1379
|
+
);
|
|
1380
|
+
expectError(
|
|
1381
|
+
await environment.operations.evictDrone(
|
|
1382
|
+
credentialA,
|
|
1383
|
+
cubeB,
|
|
1384
|
+
foreignDrone,
|
|
1385
|
+
createProtocolEnvelope('cross-cube-evict', {}),
|
|
1386
|
+
),
|
|
1387
|
+
404,
|
|
1388
|
+
ErrorCode.NOT_FOUND,
|
|
1389
|
+
'Cross-cube eviction',
|
|
1390
|
+
);
|
|
1391
|
+
expectError(
|
|
1392
|
+
await environment.operations.reassignDrone(
|
|
1393
|
+
credentialA,
|
|
1394
|
+
cubeA,
|
|
1395
|
+
foreignDrone,
|
|
1396
|
+
createProtocolEnvelope('foreign-drone-local-route', { role_id: workerRoleA.id }),
|
|
1397
|
+
),
|
|
1398
|
+
404,
|
|
1399
|
+
ErrorCode.NOT_FOUND,
|
|
1400
|
+
'Foreign drone reassignment through authorized cube route',
|
|
1401
|
+
);
|
|
1402
|
+
expectError(
|
|
1403
|
+
await environment.operations.evictDrone(
|
|
1404
|
+
credentialA,
|
|
1405
|
+
cubeA,
|
|
1406
|
+
foreignDrone,
|
|
1407
|
+
createProtocolEnvelope('foreign-drone-local-evict', {}),
|
|
1408
|
+
),
|
|
1409
|
+
404,
|
|
1410
|
+
ErrorCode.NOT_FOUND,
|
|
1411
|
+
'Foreign drone eviction through authorized cube route',
|
|
1412
|
+
);
|
|
1413
|
+
expectError(
|
|
1414
|
+
await environment.operations.reassignDrone(
|
|
1415
|
+
credentialA,
|
|
1416
|
+
cubeA,
|
|
1417
|
+
managedWorker,
|
|
1418
|
+
createProtocolEnvelope('foreign-role-local-drone', { role_id: foreignRole.id }),
|
|
1419
|
+
),
|
|
1420
|
+
404,
|
|
1421
|
+
ErrorCode.NOT_FOUND,
|
|
1422
|
+
'Foreign role reassignment through authorized cube route',
|
|
1423
|
+
);
|
|
1424
|
+
await environment.admin.grantCube(principalA, cubeB, 'read');
|
|
1425
|
+
const foreignDroneBefore = await environment.admin.inspectManagedDrone(foreignDrone);
|
|
1426
|
+
expectError(
|
|
1427
|
+
await environment.operations.reassignDrone(
|
|
1428
|
+
workerSession,
|
|
1429
|
+
cubeB,
|
|
1430
|
+
foreignDrone,
|
|
1431
|
+
createProtocolEnvelope('bound-drone-cross-cube-reassign', { role_id: foreignRole.id }),
|
|
1432
|
+
),
|
|
1433
|
+
404,
|
|
1434
|
+
ErrorCode.NOT_FOUND,
|
|
1435
|
+
'Bound drone cross-cube reassignment',
|
|
1436
|
+
);
|
|
1437
|
+
invariant(
|
|
1438
|
+
same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore),
|
|
1439
|
+
'Bound drone cross-cube reassignment mutated the foreign target.',
|
|
1440
|
+
);
|
|
1441
|
+
expectError(
|
|
1442
|
+
await environment.operations.evictDrone(
|
|
1443
|
+
workerSession,
|
|
1444
|
+
cubeB,
|
|
1445
|
+
foreignDrone,
|
|
1446
|
+
createProtocolEnvelope('bound-drone-cross-cube-evict', {}),
|
|
1447
|
+
),
|
|
1448
|
+
404,
|
|
1449
|
+
ErrorCode.NOT_FOUND,
|
|
1450
|
+
'Bound drone cross-cube eviction',
|
|
1451
|
+
);
|
|
1452
|
+
invariant(
|
|
1453
|
+
same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore),
|
|
1454
|
+
'Bound drone cross-cube eviction mutated the foreign target.',
|
|
1455
|
+
);
|
|
1456
|
+
return {
|
|
1457
|
+
unauthorized_cube_status: 404,
|
|
1458
|
+
foreign_drone_status: 404,
|
|
1459
|
+
foreign_role_status: 404,
|
|
1460
|
+
bound_drone_cross_cube_status: 404,
|
|
1461
|
+
code: ErrorCode.NOT_FOUND,
|
|
1462
|
+
};
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
await record('drones.evict-terminal-signal', async () => {
|
|
1466
|
+
const evictedDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1467
|
+
const evictedCredential = await environment.admin.issueManagedDroneSession(evictedDrone);
|
|
1468
|
+
expectStatus(
|
|
1469
|
+
await environment.operations.read(
|
|
1470
|
+
evictedCredential,
|
|
1471
|
+
cubeA,
|
|
1472
|
+
createProtocolEnvelope('evict-probe-before', { cursor: null, limit: 1 }),
|
|
1473
|
+
),
|
|
1474
|
+
200,
|
|
1475
|
+
'Pre-eviction seat probe',
|
|
1476
|
+
);
|
|
1477
|
+
const before = await environment.operations.listDrones(credentialA, cubeA);
|
|
1478
|
+
expectStatus(before, 200, 'Pre-eviction roster');
|
|
1479
|
+
invariant(listedDroneIds(before).includes(evictedDrone.id), 'Active drone was absent from roster.');
|
|
1480
|
+
|
|
1481
|
+
const evicted = await environment.operations.evictDrone(
|
|
1482
|
+
credentialA,
|
|
1483
|
+
cubeA,
|
|
1484
|
+
evictedDrone,
|
|
1485
|
+
createProtocolEnvelope('evict-managed-drone', {}),
|
|
1486
|
+
);
|
|
1487
|
+
expectStatus(evicted, 200, 'Drone eviction');
|
|
1488
|
+
invariant(
|
|
1489
|
+
same(decodeEvictDroneResultEnvelope(evicted.body).payload, {
|
|
1490
|
+
drone_id: evictedDrone.id,
|
|
1491
|
+
evicted: true,
|
|
1492
|
+
}),
|
|
1493
|
+
'Eviction response did not identify the terminal seat.',
|
|
1494
|
+
);
|
|
1495
|
+
invariant(
|
|
1496
|
+
same(await environment.admin.inspectManagedDrone(evictedDrone), {
|
|
1497
|
+
role_id: workerRoleA.id,
|
|
1498
|
+
evicted: true,
|
|
1499
|
+
session_revoked: true,
|
|
1500
|
+
}),
|
|
1501
|
+
'Eviction did not atomically mark the drone evicted and revoke its session.',
|
|
1502
|
+
);
|
|
1503
|
+
const after = await environment.operations.listDrones(credentialA, cubeA);
|
|
1504
|
+
expectStatus(after, 200, 'Post-eviction roster');
|
|
1505
|
+
invariant(!listedDroneIds(after).includes(evictedDrone.id), 'Evicted drone remained in roster.');
|
|
1506
|
+
expectError(
|
|
1507
|
+
await environment.operations.append(
|
|
1508
|
+
credentialA,
|
|
1509
|
+
cubeA,
|
|
1510
|
+
createProtocolEnvelope('evict-direct-target', {
|
|
1511
|
+
message: 'must-not-fan-out',
|
|
1512
|
+
visibility: 'direct',
|
|
1513
|
+
recipientDroneIds: [evictedDrone.id],
|
|
1514
|
+
}),
|
|
1515
|
+
),
|
|
1516
|
+
404,
|
|
1517
|
+
ErrorCode.NOT_FOUND,
|
|
1518
|
+
'Evicted direct recipient',
|
|
1519
|
+
);
|
|
1520
|
+
expectError(
|
|
1521
|
+
await environment.operations.read(
|
|
1522
|
+
evictedCredential,
|
|
1523
|
+
cubeA,
|
|
1524
|
+
createProtocolEnvelope('evict-probe-after', { cursor: null, limit: 1 }),
|
|
1525
|
+
),
|
|
1526
|
+
PROTOCOL_HTTP_CONTRACT.drone_evicted_status,
|
|
1527
|
+
ErrorCode.DRONE_EVICTED,
|
|
1528
|
+
'Evicted seat probe',
|
|
1529
|
+
);
|
|
1530
|
+
return {
|
|
1531
|
+
eviction_status: 200,
|
|
1532
|
+
roster_visible: false,
|
|
1533
|
+
fanout_reachable: false,
|
|
1534
|
+
old_bearer_status: 410,
|
|
1535
|
+
old_bearer_code: ErrorCode.DRONE_EVICTED,
|
|
1536
|
+
};
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
await record('security.drone-session-rejection-causes', async () => {
|
|
1540
|
+
const revokedDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1541
|
+
const expiredDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1542
|
+
const revokedCredential = await environment.admin.issueManagedDroneSession(revokedDrone);
|
|
1543
|
+
const expiredCredential = await environment.admin.issueManagedDroneSession(expiredDrone);
|
|
1544
|
+
await environment.admin.revokeManagedDroneSession(revokedDrone);
|
|
1545
|
+
await environment.admin.expireManagedDroneSession(expiredDrone);
|
|
1546
|
+
for (const [label, credential, code] of [
|
|
1547
|
+
['revoked', revokedCredential, ErrorCode.SESSION_REVOKED],
|
|
1548
|
+
['expired', expiredCredential, ErrorCode.AUTH_EXPIRED],
|
|
1549
|
+
] as const) {
|
|
1550
|
+
expectError(
|
|
1551
|
+
await environment.operations.read(
|
|
1552
|
+
credential,
|
|
1553
|
+
cubeA,
|
|
1554
|
+
createProtocolEnvelope(`${label}-seat-probe`, { cursor: null, limit: 1 }),
|
|
1555
|
+
),
|
|
1556
|
+
401,
|
|
1557
|
+
code,
|
|
1558
|
+
`${label} seat probe`,
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
return {
|
|
1562
|
+
revoked_status: 401,
|
|
1563
|
+
expired_status: 401,
|
|
1564
|
+
revoked_code: ErrorCode.SESSION_REVOKED,
|
|
1565
|
+
expired_code: ErrorCode.AUTH_EXPIRED,
|
|
1566
|
+
};
|
|
1567
|
+
});
|
|
1568
|
+
|
|
988
1569
|
await record('security.active-stream-revocation', async () => {
|
|
989
1570
|
invariant(liveCursor, 'Stream fixture did not produce a live cursor.');
|
|
990
1571
|
const opened = await environment.operations.openStream(credentialA, cubeA, liveCursor);
|
|
@@ -1020,21 +1601,3 @@ export async function runAdapterConformance(
|
|
|
1020
1601
|
const normalizedTranscript = results.map(({ id, observations }) => ({ id, observations }));
|
|
1021
1602
|
return { ok: results.every((result) => result.ok), results, normalizedTranscript };
|
|
1022
1603
|
}
|
|
1023
|
-
|
|
1024
|
-
export async function runEquivalentAdapterConformance(
|
|
1025
|
-
cloud: ConformanceEnvironment,
|
|
1026
|
-
local: ConformanceEnvironment,
|
|
1027
|
-
options: AdapterConformanceOptions = {},
|
|
1028
|
-
): Promise<EquivalentAdapterConformanceReport> {
|
|
1029
|
-
const [cloudReport, localReport] = await Promise.all([
|
|
1030
|
-
runAdapterConformance(cloud, options),
|
|
1031
|
-
runAdapterConformance(local, options),
|
|
1032
|
-
]);
|
|
1033
|
-
const equivalent = same(cloudReport.normalizedTranscript, localReport.normalizedTranscript);
|
|
1034
|
-
return {
|
|
1035
|
-
ok: cloudReport.ok && localReport.ok && equivalent,
|
|
1036
|
-
cloud: cloudReport,
|
|
1037
|
-
local: localReport,
|
|
1038
|
-
equivalent,
|
|
1039
|
-
};
|
|
1040
|
-
}
|