borgmcp-shared 1.0.1 → 1.2.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/CONTRIBUTING.md +4 -1
- package/README.md +37 -8
- package/RELEASES.md +2 -3
- package/dist/conformance/adapter.d.ts +6 -101
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +546 -281
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +5 -42
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +16 -52
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +2 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +2 -2
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/errors.d.ts +0 -14
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +0 -8
- 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 +0 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +1 -2
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +11 -0
- package/docs/enrollment.md +6 -9
- package/docs/releases/1.1.0.md +19 -0
- package/docs/releases/1.2.0.md +20 -0
- package/docs/releasing.md +13 -23
- package/package.json +1 -2
- package/src/conformance/adapter.ts +822 -640
- package/src/conformance/index.ts +24 -75
- package/src/protocol/contract.ts +2 -2
- package/src/protocol/errors.ts +0 -17
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +1 -3
- package/docs/release-records.json +0 -261
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeAssociateRepositoryCubeResponseEnvelope, decodeCreateCubeResponseEnvelope, decodeDeleteCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeAckStatusResultEnvelope, decodeEntryQueryResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeInvitationArtifact, decodeAttachResponseEnvelope,
|
|
1
|
+
import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeAssociateRepositoryCubeResponseEnvelope, decodeCreateCubeResponseEnvelope, decodeDeleteCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeAckStatusResultEnvelope, decodeEntryQueryResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeInvitationArtifact, decodeAttachResponseEnvelope, decodeEvictDroneResultEnvelope, decodeDeleteRoleResultEnvelope, decodeRoleRationaleResultEnvelope, decodeProtocolEnvelope, decodeProtocolErrorEnvelope, decodeProtocolTagPreflight, decodeReadLogResultEnvelope, decodeResolveRepositoryCubeResponseEnvelope, decodeReassignDroneResultEnvelope, decodeUpdateDroneRuntimeMetadataResponseEnvelope, decodeGetDocumentResultEnvelope, decodeListDocumentsResultEnvelope, decodePutDocumentResultEnvelope, decodeRemoveDocumentResultEnvelope, decodeSseFrames, PROTOCOL_LIMIT_CEILINGS, PROTOCOL_HTTP_CONTRACT, PROTOCOL_VERSION, ROLE_IN_USE_DELETE_MESSAGE, utf8ByteLength, } from '../protocol/index.js';
|
|
2
2
|
import { APPEND_LOG_IDEMPOTENCY_CONFORMANCE, CREATE_CUBE_ASSOCIATION_CONFORMANCE, CREATE_CUBE_RETRY_CONFORMANCE, INVITATION_ARTIFACT_CONFORMANCE, ENROLLMENT_RETRY_CONFORMANCE, DOCUMENT_CONFORMANCE, } from './index.js';
|
|
3
3
|
export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
4
4
|
{ id: 'http.unauthenticated-liveness', area: 'http' },
|
|
@@ -37,6 +37,33 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
37
37
|
{ id: 'security.active-stream-revocation', area: 'security' },
|
|
38
38
|
{ id: 'cubes.delete-terminal-cascade', area: 'cubes' },
|
|
39
39
|
];
|
|
40
|
+
const FIXTURE_PREREQUISITES = {
|
|
41
|
+
'repository.explicit-association': ['shared.enrolled-principals'],
|
|
42
|
+
'documents.lifecycle': ['shared.enrolled-principals'],
|
|
43
|
+
'security.adapter-boundary-injection': ['shared.enrolled-principals'],
|
|
44
|
+
'security.oversize-request': ['shared.enrolled-principals'],
|
|
45
|
+
'security.cross-cube-isolation': ['shared.enrolled-principals'],
|
|
46
|
+
'log.mandatory-addressing': ['shared.enrolled-principals'],
|
|
47
|
+
'log.entry-query': ['shared.enrolled-principals'],
|
|
48
|
+
'log.read-cursor-tuple': ['shared.enrolled-principals'],
|
|
49
|
+
'sse.replay-live-transition': ['shared.enrolled-principals'],
|
|
50
|
+
'cursor.explicit-expiry': ['shared.enrolled-principals'],
|
|
51
|
+
'acks.idempotent': ['shared.enrolled-principals'],
|
|
52
|
+
'acks.status-query': ['shared.enrolled-principals'],
|
|
53
|
+
'claims.durable-noncursor': ['shared.enrolled-principals'],
|
|
54
|
+
'decisions.topic-supersession': ['shared.enrolled-principals'],
|
|
55
|
+
'security.manage-access-matrix': ['shared.enrolled-principals'],
|
|
56
|
+
'drones.reassign-invariants': ['shared.enrolled-principals'],
|
|
57
|
+
'security.cross-cube-drone-management': ['shared.enrolled-principals'],
|
|
58
|
+
'drones.evict-terminal-signal': ['shared.enrolled-principals'],
|
|
59
|
+
'metadata.attach-report': ['shared.enrolled-principals'],
|
|
60
|
+
'metadata.self-heal-patch': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
61
|
+
'security.metadata-invalid-atomic': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
62
|
+
'security.metadata-own-seat': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
63
|
+
'security.metadata-cross-cube-isolation': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
64
|
+
'security.metadata-noninterference': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
65
|
+
'security.metadata-secret-non-echo': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
66
|
+
};
|
|
40
67
|
const DEFAULT_STREAM_DEADLINE_MS = 5_000;
|
|
41
68
|
const DEFAULT_PENDING_PROBE_MS = 25;
|
|
42
69
|
function invariant(condition, message) {
|
|
@@ -160,10 +187,19 @@ function listedDroneIds(response) {
|
|
|
160
187
|
});
|
|
161
188
|
}).payload;
|
|
162
189
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
invariant(
|
|
166
|
-
|
|
190
|
+
function listedDrone(response, droneId) {
|
|
191
|
+
return decodeProtocolEnvelope(response.body, (payload) => {
|
|
192
|
+
invariant(typeof payload === 'object' && payload !== null, 'Roster payload is not an object.');
|
|
193
|
+
const drones = payload.drones;
|
|
194
|
+
invariant(Array.isArray(drones), 'Roster payload omitted drones.');
|
|
195
|
+
const drone = drones.find((candidate) => typeof candidate === 'object' && candidate !== null &&
|
|
196
|
+
candidate.id === droneId);
|
|
197
|
+
return drone ? { ...drone } : null;
|
|
198
|
+
}).payload;
|
|
199
|
+
}
|
|
200
|
+
function withoutRuntimeMetadata(drone) {
|
|
201
|
+
const { agent_kind: _agentKind, reported_model: _reportedModel, working_repo_name: _workingRepoName, working_repo_origin: _workingRepoOrigin, runtime_metadata_reported: _runtimeMetadataReported, ...rest } = drone;
|
|
202
|
+
return rest;
|
|
167
203
|
}
|
|
168
204
|
function assertEnrollmentErrorIsSecretFree(response, requests, description) {
|
|
169
205
|
const diagnostic = JSON.stringify(response.body);
|
|
@@ -189,16 +225,30 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
189
225
|
invariant(streamDeadlineMs > 0, 'streamDeadlineMs must be positive.');
|
|
190
226
|
invariant(pendingProbeMs > 0, 'pendingProbeMs must be positive.');
|
|
191
227
|
const results = [];
|
|
192
|
-
const
|
|
228
|
+
const prerequisiteFailures = new Map();
|
|
229
|
+
const record = async (id, execute, prerequisites = []) => {
|
|
230
|
+
const required = [...(FIXTURE_PREREQUISITES[id] ?? []), ...prerequisites];
|
|
231
|
+
const failedPrerequisite = required.find((name) => prerequisiteFailures.has(name));
|
|
232
|
+
if (failedPrerequisite) {
|
|
233
|
+
results.push({
|
|
234
|
+
id,
|
|
235
|
+
ok: false,
|
|
236
|
+
skipped: true,
|
|
237
|
+
observations: {},
|
|
238
|
+
error: `Skipped because prerequisite ${failedPrerequisite} failed: ${prerequisiteFailures.get(failedPrerequisite)}`,
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
193
242
|
try {
|
|
194
243
|
results.push({ id, ok: true, observations: await execute() });
|
|
195
244
|
}
|
|
196
245
|
catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
197
247
|
results.push({
|
|
198
248
|
id,
|
|
199
249
|
ok: false,
|
|
200
250
|
observations: {},
|
|
201
|
-
error:
|
|
251
|
+
error: message,
|
|
202
252
|
});
|
|
203
253
|
}
|
|
204
254
|
};
|
|
@@ -216,13 +266,11 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
216
266
|
return { status: 204, bodyless: true };
|
|
217
267
|
});
|
|
218
268
|
await record('protocol.credential-free-preflight', async () => {
|
|
219
|
-
const before = await environment.admin.observeAuthorityState();
|
|
220
269
|
const response = await environment.operations.protocol(null);
|
|
221
270
|
expectStatus(response, 200, 'Credential-free protocol-tag preflight');
|
|
222
271
|
const preflight = decodeProtocolTagPreflight(response.body);
|
|
223
272
|
invariant(Object.keys(preflight).length === 1 && preflight.protocol_version === PROTOCOL_VERSION, 'Protocol-tag preflight exposed more than the exact tag.');
|
|
224
|
-
|
|
225
|
-
return { authenticated: false, mutation_free: true, protocol_version: preflight.protocol_version };
|
|
273
|
+
return { authenticated: false, protocol_version: preflight.protocol_version };
|
|
226
274
|
});
|
|
227
275
|
await record('enrollment.invitation-artifact', async () => {
|
|
228
276
|
const errors = [];
|
|
@@ -260,19 +308,11 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
260
308
|
const invitation = await environment.admin.issueSingleUseInvitation(principal, purpose);
|
|
261
309
|
const initialPayload = { ...vector.initial, invitation };
|
|
262
310
|
const retryPayload = { ...vector.retry, invitation };
|
|
263
|
-
const beforeInitial = await environment.admin.observeAuthorityState();
|
|
264
311
|
const initialResponse = await environment.operations.enroll(createProtocolEnvelope(`retry-${index}-initial`, initialPayload));
|
|
265
312
|
expectStatus(initialResponse, 201, `${vector.name} initial request`);
|
|
266
313
|
const initial = decodeEnrollmentExchangeResponseEnvelope(initialResponse.body).payload;
|
|
267
314
|
invariant(initial.purpose === purpose, `${purpose} ${vector.name} returned the wrong purpose.`);
|
|
268
315
|
invariant(same(initial.server_capabilities, purpose === 'owner' ? ['create_cube'] : []), `${purpose} ${vector.name} returned incorrect server authority.`);
|
|
269
|
-
const afterInitial = await environment.admin.observeAuthorityState();
|
|
270
|
-
assertStateDelta(beforeInitial, afterInitial, {
|
|
271
|
-
enrolled_clients: 1,
|
|
272
|
-
enrollment_claims: 1,
|
|
273
|
-
server_capabilities: purpose === 'owner' ? 1 : 0,
|
|
274
|
-
}, `${purpose} ${vector.name} initial request`);
|
|
275
|
-
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
276
316
|
const retryResponse = await environment.operations.enroll(createProtocolEnvelope(`retry-${index}-retry`, retryPayload));
|
|
277
317
|
if (vector.expected.outcome === 'stable_non_secret_identity') {
|
|
278
318
|
expectStatus(retryResponse, 201, `${purpose} ${vector.name}`);
|
|
@@ -286,12 +326,25 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
286
326
|
expectError(retryResponse, vector.expected.status, ErrorCode.AUTH_INVALID, `${purpose} ${vector.name}`);
|
|
287
327
|
assertEnrollmentErrorIsSecretFree(retryResponse, [initialPayload, retryPayload], `${purpose} ${vector.name}`);
|
|
288
328
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
329
|
+
const credentialProbeCube = await environment.admin.createCube(`${purpose}-credential-probe-${index}`);
|
|
330
|
+
await environment.admin.grantCube(principal, credentialProbeCube, 'read');
|
|
331
|
+
expectStatus(await environment.operations.read(initialPayload.client_credential, credentialProbeCube, createProtocolEnvelope(`retry-${index}-credential-probe`, { cursor: null, limit: 1 })), 200, `${purpose} ${vector.name} original credential probe`);
|
|
332
|
+
const capabilityProbe = await environment.operations.createCube(initialPayload.client_credential, createProtocolEnvelope(`retry-${index}-${purpose}-capability-probe`, {
|
|
333
|
+
retry_key: `10000000-0000-4000-8000-${String(index + (purpose === 'owner' ? 700 : 600)).padStart(12, '0')}`,
|
|
334
|
+
name: `${purpose} capability probe ${index}`,
|
|
335
|
+
working_repo_name: `${purpose}-capability-probe-${index}`,
|
|
336
|
+
repository: {
|
|
337
|
+
kind: 'local',
|
|
338
|
+
value: `20000000-0000-4000-8000-${String(index + (purpose === 'owner' ? 700 : 600)).padStart(12, '0')}`,
|
|
339
|
+
},
|
|
340
|
+
template: 'software-dev',
|
|
341
|
+
}));
|
|
342
|
+
if (purpose === 'owner') {
|
|
343
|
+
expectStatus(capabilityProbe, 201, `${purpose} ${vector.name} capability probe`);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
expectError(capabilityProbe, 403, ErrorCode.ACCESS_DENIED, `${purpose} ${vector.name} capability probe`);
|
|
347
|
+
}
|
|
295
348
|
}
|
|
296
349
|
catch (error) {
|
|
297
350
|
retryVectorErrors.push(`${purpose} ${vector.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -306,7 +359,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
306
359
|
const ordinaryInvitation = await environment.admin.issueSingleUseInvitation(ordinaryPrincipal, 'client');
|
|
307
360
|
const ownerCredential = `${'Q'.repeat(42)}U`;
|
|
308
361
|
const ordinaryCredential = `${'Y'.repeat(42)}U`;
|
|
309
|
-
const beforeAuthorityEnrollment = await environment.admin.observeAuthorityState();
|
|
310
362
|
const ownerResponse = await environment.operations.enroll(createProtocolEnvelope('owner-enroll', {
|
|
311
363
|
invitation: ownerInvitation,
|
|
312
364
|
retry_key: '00000000-0000-4000-8000-000000000211',
|
|
@@ -325,7 +377,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
325
377
|
const ordinary = decodeEnrollmentExchangeResponseEnvelope(ordinaryResponse.body).payload;
|
|
326
378
|
invariant(owner.purpose === 'owner' && same(owner.server_capabilities, ['create_cube']), 'Owner enrollment lacked exact create-cube authority.');
|
|
327
379
|
invariant(ordinary.purpose === 'client' && ordinary.server_capabilities.length === 0, 'Ordinary enrollment gained authority.');
|
|
328
|
-
assertStateDelta(beforeAuthorityEnrollment, await environment.admin.observeAuthorityState(), { enrolled_clients: 2, enrollment_claims: 2, server_capabilities: 1 }, 'Owner and ordinary enrollment');
|
|
329
380
|
const cubeRequest = {
|
|
330
381
|
retry_key: '00000000-0000-4000-8000-000000000213',
|
|
331
382
|
name: 'Repository One',
|
|
@@ -334,64 +385,85 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
334
385
|
kind: 'origin',
|
|
335
386
|
value: 'https://github.com/Byte-Ventures/repository-one',
|
|
336
387
|
},
|
|
337
|
-
template: '
|
|
388
|
+
template: 'software-dev',
|
|
338
389
|
};
|
|
339
390
|
const droneCredential = await environment.admin.issueDroneSession(ownerPrincipal);
|
|
340
|
-
const beforeDeniedCreate = await environment.admin.observeAuthorityState();
|
|
341
391
|
expectSecretFreeError(await environment.operations.createCube(null, createProtocolEnvelope('cube-missing-auth', cubeRequest)), 401, ErrorCode.AUTH_MISSING, 'Missing-auth cube create', [cubeRequest.retry_key]);
|
|
342
392
|
expectSecretFreeError(await environment.operations.createCube('invalid-credential', createProtocolEnvelope('cube-invalid-auth', cubeRequest)), 401, ErrorCode.AUTH_INVALID, 'Invalid-auth cube create', [cubeRequest.retry_key]);
|
|
343
393
|
expectSecretFreeError(await environment.operations.createCube(ordinaryCredential, createProtocolEnvelope('cube-denied', cubeRequest)), 403, ErrorCode.ACCESS_DENIED, 'Ordinary cube create', [cubeRequest.retry_key]);
|
|
344
394
|
expectSecretFreeError(await environment.operations.createCube(droneCredential, createProtocolEnvelope('cube-drone-denied', cubeRequest)), 403, ErrorCode.ACCESS_DENIED, 'Drone-session cube create', [cubeRequest.retry_key]);
|
|
345
|
-
assertStateDelta(beforeDeniedCreate, await environment.admin.observeAuthorityState(), {}, 'Denied ordinary cube create');
|
|
346
|
-
const beforeCreate = await environment.admin.observeAuthorityState();
|
|
347
395
|
const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
|
|
348
396
|
expectStatus(createdResponse, 201, 'Owner cube create');
|
|
349
397
|
const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
398
|
+
expectError(await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-case-variant-duplicate', {
|
|
399
|
+
...cubeRequest,
|
|
400
|
+
retry_key: '00000000-0000-4000-8000-000000000214',
|
|
401
|
+
name: 'repository one',
|
|
402
|
+
repository: {
|
|
403
|
+
kind: 'origin',
|
|
404
|
+
value: 'https://github.com/Byte-Ventures/repository-one-case-variant',
|
|
405
|
+
},
|
|
406
|
+
})), 409, ErrorCode.INVALID_INPUT, 'Same-owner case-variant cube name');
|
|
407
|
+
const createdRoleProbes = new Map();
|
|
408
|
+
for (const [kind, roleId, roleClass, isHumanSeat, marker] of [
|
|
409
|
+
['human-seat', created.human_seat_role_id, 'queen', true, 'H'],
|
|
410
|
+
['default-worker', created.default_worker_role_id, 'worker', false, 'D'],
|
|
411
|
+
]) {
|
|
412
|
+
const sessionCredential = marker.repeat(43);
|
|
413
|
+
const attached = await environment.operations.attach(ownerCredential, createProtocolEnvelope(`cube-create-${kind}-probe`, {
|
|
414
|
+
cube_id: created.cube_id,
|
|
415
|
+
role_id: roleId,
|
|
416
|
+
session_credential: sessionCredential,
|
|
417
|
+
}));
|
|
418
|
+
expectStatus(attached, 200, `Created ${kind} role probe`);
|
|
419
|
+
const attachedPayload = decodeAttachResponseEnvelope(attached.body).payload;
|
|
420
|
+
const attachedRole = attachedPayload.role;
|
|
421
|
+
invariant(attachedRole.id === roleId &&
|
|
422
|
+
attachedRole.role_class === roleClass &&
|
|
423
|
+
attachedRole.is_human_seat === isHumanSeat, `Created ${kind} role identity did not match its protocol role.`);
|
|
424
|
+
createdRoleProbes.set(kind, {
|
|
425
|
+
role: attachedRole,
|
|
426
|
+
droneId: attachedPayload.drone.id,
|
|
427
|
+
sessionCredential,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
367
430
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
368
431
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
369
432
|
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
370
433
|
invariant(retriedCreate.result === 'resolved', 'Exact cube-create retry did not report resolved readback.');
|
|
371
434
|
invariant(same({ ...retriedCreate, result: 'created' }, created), 'Exact cube-create retry returned different authoritative fields.');
|
|
372
|
-
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
373
435
|
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find((vector) => vector.expected.outcome === 'resolved_response' &&
|
|
374
436
|
vector.retry.working_repo_name !== vector.initial.working_repo_name);
|
|
375
437
|
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
376
|
-
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
377
438
|
const displayRetryResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-display-retry', {
|
|
378
439
|
...displayRetryVector.retry,
|
|
379
440
|
retry_key: cubeRequest.retry_key,
|
|
380
441
|
}));
|
|
381
442
|
expectStatus(displayRetryResponse, 201, 'Changed repository display retry');
|
|
382
443
|
invariant(same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate), 'Changed repository display retry did not return stored authoritative display.');
|
|
383
|
-
assertStateDelta(beforeDisplayRetry, await environment.admin.observeAuthorityState(), {}, 'Changed repository display retry');
|
|
384
444
|
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
385
445
|
if (vector.expected.outcome !== 'retry_tuple_mismatch')
|
|
386
446
|
continue;
|
|
387
447
|
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
388
|
-
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
389
448
|
expectSecretFreeError(await environment.operations.createCube(ownerCredential, createProtocolEnvelope(`cube-mismatch-${index}`, mismatch)), 409, ErrorCode.INVALID_INPUT, `Cube-create ${vector.name}`, [cubeRequest.retry_key]);
|
|
390
|
-
|
|
449
|
+
}
|
|
450
|
+
for (const [kind, roleId] of [
|
|
451
|
+
['human-seat', created.human_seat_role_id],
|
|
452
|
+
['default-worker', created.default_worker_role_id],
|
|
453
|
+
]) {
|
|
454
|
+
const initialProbe = createdRoleProbes.get(kind);
|
|
455
|
+
invariant(initialProbe !== undefined, `Missing initial ${kind} role probe.`);
|
|
456
|
+
const attached = await environment.operations.attach(ownerCredential, createProtocolEnvelope(`cube-create-${kind}-post-refusal-probe`, {
|
|
457
|
+
cube_id: created.cube_id,
|
|
458
|
+
role_id: roleId,
|
|
459
|
+
session_credential: initialProbe.sessionCredential,
|
|
460
|
+
prior_drone_id: initialProbe.droneId,
|
|
461
|
+
}));
|
|
462
|
+
expectStatus(attached, 200, `Post-refusal ${kind} role probe`);
|
|
463
|
+
invariant(same(decodeAttachResponseEnvelope(attached.body).payload.role, initialProbe.role), `Cube-create retry refusal changed the full ${kind} role shape.`);
|
|
391
464
|
}
|
|
392
465
|
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find((vector) => vector.expected.outcome === 'resolved');
|
|
393
466
|
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
394
|
-
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
395
467
|
const associationResolveResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-association-resolve', {
|
|
396
468
|
...sameAssociationVector.request,
|
|
397
469
|
repository: cubeRequest.repository,
|
|
@@ -400,7 +472,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
400
472
|
const associationResolved = decodeCreateCubeResponseEnvelope(associationResolveResponse.body).payload;
|
|
401
473
|
invariant(associationResolved.result === 'resolved', 'Associated repository did not resolve.');
|
|
402
474
|
invariant(same({ ...associationResolved, result: 'created' }, created), 'Associated repository did not return stored authoritative fields.');
|
|
403
|
-
assertStateDelta(beforeAssociationResolve, await environment.admin.observeAuthorityState(), sameAssociationVector.expected.authority_state_delta, 'Fresh retry for associated repository');
|
|
404
475
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
405
476
|
const crossClientRequest = {
|
|
406
477
|
...cubeRequest,
|
|
@@ -410,35 +481,49 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
410
481
|
value: '00000000-0000-4000-8000-000000000216',
|
|
411
482
|
},
|
|
412
483
|
};
|
|
413
|
-
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
414
484
|
const crossClientResponse = await environment.operations.createCube(ordinaryCredential, createProtocolEnvelope('cube-cross-client', crossClientRequest));
|
|
415
485
|
expectStatus(crossClientResponse, 201, 'Cross-client cube create with reused retry key');
|
|
416
486
|
const crossClientCreated = decodeCreateCubeResponseEnvelope(crossClientResponse.body).payload;
|
|
417
487
|
invariant(crossClientCreated.cube_id !== created.cube_id, 'Cross-client retry key reused another client\'s cube.');
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
488
|
+
for (const [kind, roleId, originalRoleId, marker] of [
|
|
489
|
+
['human-seat', crossClientCreated.human_seat_role_id, created.human_seat_role_id, 'V'],
|
|
490
|
+
['default-worker', crossClientCreated.default_worker_role_id, created.default_worker_role_id, 'W'],
|
|
491
|
+
]) {
|
|
492
|
+
const originalProbe = createdRoleProbes.get(kind);
|
|
493
|
+
invariant(originalProbe !== undefined, `Missing original ${kind} role probe.`);
|
|
494
|
+
const attached = await environment.operations.attach(ordinaryCredential, createProtocolEnvelope(`cube-cross-client-${kind}-probe`, {
|
|
495
|
+
cube_id: crossClientCreated.cube_id,
|
|
496
|
+
role_id: roleId,
|
|
497
|
+
session_credential: marker.repeat(43),
|
|
498
|
+
}));
|
|
499
|
+
expectStatus(attached, 200, `Cross-client ${kind} role probe`);
|
|
500
|
+
const attachedRole = decodeAttachResponseEnvelope(attached.body).payload.role;
|
|
501
|
+
invariant(same({ ...attachedRole, id: originalRoleId }, originalProbe.role), `Cross-client ${kind} role did not match the full template role shape.`);
|
|
502
|
+
}
|
|
421
503
|
const crossClientRetry = await environment.operations.createCube(ordinaryCredential, createProtocolEnvelope('cube-cross-client-retry', crossClientRequest));
|
|
422
504
|
expectStatus(crossClientRetry, 201, 'Exact cross-client cube-create retry');
|
|
423
505
|
const crossClientResolved = decodeCreateCubeResponseEnvelope(crossClientRetry.body).payload;
|
|
424
506
|
invariant(crossClientResolved.result === 'resolved', 'Exact cross-client cube-create retry did not report resolved readback.');
|
|
425
507
|
invariant(same({ ...crossClientResolved, result: 'created' }, crossClientCreated), 'Exact cross-client cube-create retry returned different authoritative fields.');
|
|
426
|
-
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
427
508
|
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find((vector) => vector.expected.outcome === 'created');
|
|
428
509
|
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
429
|
-
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
430
510
|
const secondCreatedResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create-second', differentAssociationVector.request));
|
|
431
511
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
432
512
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
433
513
|
invariant(secondCreated.cube_id !== created.cube_id, 'Fresh cube-create retry key reused an existing cube.');
|
|
434
|
-
assertStateDelta(beforeSecondCreate, await environment.admin.observeAuthorityState(), differentAssociationVector.expected.authority_state_delta, 'Second cube create');
|
|
435
514
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
|
436
|
-
const beforeRevokedCreate = await environment.admin.observeAuthorityState();
|
|
437
515
|
expectSecretFreeError(await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-revoked', {
|
|
438
516
|
...cubeRequest,
|
|
439
517
|
retry_key: '00000000-0000-4000-8000-000000000215',
|
|
440
518
|
})), 401, ErrorCode.SESSION_REVOKED, 'Revoked owner cube create', ['00000000-0000-4000-8000-000000000215']);
|
|
441
|
-
|
|
519
|
+
return {
|
|
520
|
+
enrollment_status: 201,
|
|
521
|
+
exact_retry_status: 201,
|
|
522
|
+
mismatched_retry: ErrorCode.AUTH_INVALID,
|
|
523
|
+
response_secret_free: true,
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
try {
|
|
442
527
|
await environment.admin.reset();
|
|
443
528
|
principalA = await environment.admin.createPrincipal('principal-a');
|
|
444
529
|
principalB = await environment.admin.createPrincipal('principal-b');
|
|
@@ -450,42 +535,24 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
450
535
|
const invitationB = await environment.admin.issueSingleUseInvitation(principalB, 'client');
|
|
451
536
|
credentialA = 'A'.repeat(43);
|
|
452
537
|
credentialB = 'E'.repeat(43);
|
|
453
|
-
const
|
|
538
|
+
const enrolledAResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-a1', {
|
|
454
539
|
invitation: invitationA,
|
|
455
540
|
retry_key: '00000000-0000-4000-8000-000000000201',
|
|
456
541
|
client_credential: credentialA,
|
|
457
542
|
client_name: 'conformance-a',
|
|
458
|
-
});
|
|
459
|
-
const
|
|
543
|
+
}));
|
|
544
|
+
const enrolledBResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-b1', {
|
|
460
545
|
invitation: invitationB,
|
|
461
546
|
retry_key: '00000000-0000-4000-8000-000000000202',
|
|
462
547
|
client_credential: credentialB,
|
|
463
548
|
client_name: 'conformance-b',
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
invariant(enrolledA.purpose === 'client' && enrolledB.purpose === 'client', 'Ordinary enrollment returned owner authority.');
|
|
472
|
-
invariant(enrolledA.server_capabilities.length === 0 && enrolledB.server_capabilities.length === 0, 'Ordinary enrollment returned a server capability.');
|
|
473
|
-
invariant(!('credential' in enrolledA) && !('credential' in enrolledB), 'Enrollment response returned a bearer.');
|
|
474
|
-
const retriedAResponse = await environment.operations.enroll(enrollmentARequest);
|
|
475
|
-
expectStatus(retriedAResponse, 201, 'Exact enrollment retry');
|
|
476
|
-
invariant(JSON.stringify(decodeEnrollmentExchangeResponseEnvelope(retriedAResponse.body).payload) ===
|
|
477
|
-
JSON.stringify(enrolledA), 'Exact enrollment retry returned different identities.');
|
|
478
|
-
expectError(await environment.operations.enroll(createProtocolEnvelope('enroll-a-mismatch', {
|
|
479
|
-
...enrollmentARequest.payload,
|
|
480
|
-
retry_key: '00000000-0000-4000-8000-000000000203',
|
|
481
|
-
})), 401, ErrorCode.AUTH_INVALID, 'Enrollment retry mismatch');
|
|
482
|
-
return {
|
|
483
|
-
enrollment_status: 201,
|
|
484
|
-
exact_retry_status: 201,
|
|
485
|
-
mismatched_retry: ErrorCode.AUTH_INVALID,
|
|
486
|
-
response_secret_free: true,
|
|
487
|
-
};
|
|
488
|
-
});
|
|
549
|
+
}));
|
|
550
|
+
expectStatus(enrolledAResponse, 201, 'Shared principal A enrollment');
|
|
551
|
+
expectStatus(enrolledBResponse, 201, 'Shared principal B enrollment');
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
prerequisiteFailures.set('shared.enrolled-principals', error instanceof Error ? error.message : String(error));
|
|
555
|
+
}
|
|
489
556
|
await record('repository.explicit-association', async () => {
|
|
490
557
|
const repository = {
|
|
491
558
|
kind: 'origin',
|
|
@@ -499,16 +566,24 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
499
566
|
working_repo_name: 'legacy-repository',
|
|
500
567
|
repository,
|
|
501
568
|
};
|
|
502
|
-
const
|
|
569
|
+
const alternateResolveRequest = {
|
|
570
|
+
working_repo_name: 'other-repository',
|
|
571
|
+
repository: {
|
|
572
|
+
kind: 'origin',
|
|
573
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
const resolve = async (requestId, request) => {
|
|
577
|
+
const response = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope(requestId, request));
|
|
578
|
+
expectStatus(response, 200, requestId);
|
|
579
|
+
return decodeResolveRepositoryCubeResponseEnvelope(response.body).payload;
|
|
580
|
+
};
|
|
503
581
|
const noneResponse = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve-none', resolveRequest));
|
|
504
582
|
expectStatus(noneResponse, 200, 'Unassociated repository resolution');
|
|
505
583
|
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }), 'Unassociated repository did not return explicit none.');
|
|
506
|
-
|
|
584
|
+
invariant(same(await resolve('repository-resolve-none-readback', resolveRequest), { result: 'none' }), 'Read-only repository resolution created an association.');
|
|
507
585
|
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
508
|
-
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
509
586
|
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialB, createProtocolEnvelope('repository-associate-denied', associationRequest)), 403, ErrorCode.ACCESS_DENIED, 'Inaccessible repository cube association', [associationRequest.cube_id, repository.value]);
|
|
510
|
-
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
511
|
-
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
512
587
|
const associatedResponse = await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate', associationRequest));
|
|
513
588
|
expectStatus(associatedResponse, 200, 'Explicit repository cube association');
|
|
514
589
|
const associated = decodeAssociateRepositoryCubeResponseEnvelope(associatedResponse.body).payload;
|
|
@@ -524,52 +599,42 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
524
599
|
access: prepared.access,
|
|
525
600
|
};
|
|
526
601
|
invariant(same(associated, authoritative), 'Repository association did not return authoritative stored fields.');
|
|
527
|
-
assertStateDelta(beforeAssociation, await environment.admin.observeAuthorityState(), { repository_associations: 1 }, 'Explicit repository association');
|
|
528
|
-
const beforeResolve = await environment.admin.observeAuthorityState();
|
|
529
602
|
const resolvedResponse = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve', {
|
|
530
603
|
...resolveRequest,
|
|
531
604
|
working_repo_name: 'ignored-new-display',
|
|
532
605
|
}));
|
|
533
606
|
expectStatus(resolvedResponse, 200, 'Associated repository resolution');
|
|
534
607
|
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative), 'Associated repository did not return stored authoritative display.');
|
|
535
|
-
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
536
|
-
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
537
608
|
const retryResponse = await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-retry', associationRequest));
|
|
538
609
|
expectStatus(retryResponse, 200, 'Exact repository association retry');
|
|
539
610
|
invariant(same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative), 'Exact repository association retry changed authoritative fields.');
|
|
540
|
-
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
541
611
|
const cubeC = await environment.admin.createCube('cube-c');
|
|
542
612
|
await environment.admin.grantCube(principalA, cubeC);
|
|
543
613
|
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
544
614
|
name: 'Other Legacy Cube',
|
|
545
615
|
template: 'starter',
|
|
546
616
|
});
|
|
547
|
-
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
548
617
|
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-repository-conflict', {
|
|
549
618
|
...associationRequest,
|
|
550
619
|
cube_id: cubeC.id,
|
|
551
620
|
})), 409, ErrorCode.REPOSITORY_ALREADY_ASSOCIATED, 'Repository-to-other-cube conflict', [cubeA.id, cubeC.id, repository.value]);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
621
|
+
invariant(same(await resolve('repository-resolve-after-repository-conflict', resolveRequest), authoritative), 'Repository conflict changed the authoritative original binding.');
|
|
622
|
+
invariant(same(await resolve('repository-resolve-alternate-after-repository-conflict', alternateResolveRequest), { result: 'none' }), 'Repository conflict created an alternate binding.');
|
|
623
|
+
const alternateAssociationResponse = await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-second-repository', {
|
|
555
624
|
cube_id: cubeA.id,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
assertStateDelta(beforeCubeConflict, await environment.admin.observeAuthorityState(), {}, 'Cube-to-other-repository conflict');
|
|
625
|
+
...alternateResolveRequest,
|
|
626
|
+
}));
|
|
627
|
+
expectStatus(alternateAssociationResponse, 200, 'Second repository association');
|
|
628
|
+
const alternateAuthoritative = decodeAssociateRepositoryCubeResponseEnvelope(alternateAssociationResponse.body).payload;
|
|
629
|
+
invariant(same(await resolve('repository-resolve-after-second-association', resolveRequest), authoritative), 'Second repository association changed the authoritative original binding.');
|
|
630
|
+
invariant(same(await resolve('repository-resolve-alternate-after-second-association', alternateResolveRequest), alternateAuthoritative), 'Second repository association was not observable.');
|
|
563
631
|
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
564
632
|
name: 'Other Client Legacy Cube',
|
|
565
633
|
template: 'starter',
|
|
566
634
|
});
|
|
567
|
-
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
568
635
|
const crossClientResolve = await environment.operations.resolveRepositoryCube(credentialB, createProtocolEnvelope('repository-resolve-cross-client', resolveRequest));
|
|
569
636
|
expectStatus(crossClientResolve, 200, 'Cross-client repository resolution');
|
|
570
637
|
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }), 'Another client repository binding was exposed by resolution.');
|
|
571
|
-
assertStateDelta(beforeCrossClientResolve, await environment.admin.observeAuthorityState(), {}, 'Cross-client repository resolution');
|
|
572
|
-
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
573
638
|
const crossClientAssociation = await environment.operations.associateRepositoryCube(credentialB, createProtocolEnvelope('repository-associate-cross-client', {
|
|
574
639
|
...associationRequest,
|
|
575
640
|
cube_id: cubeB.id,
|
|
@@ -578,26 +643,26 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
578
643
|
const crossClientAssociated = decodeAssociateRepositoryCubeResponseEnvelope(crossClientAssociation.body).payload;
|
|
579
644
|
invariant(crossClientAssociated.cube_id === preparedB.cube_id &&
|
|
580
645
|
crossClientAssociated.repository.value === repository.value, 'Another client binding changed cross-client association identity.');
|
|
581
|
-
assertStateDelta(beforeCrossClientAssociation, await environment.admin.observeAuthorityState(), { repository_associations: 1 }, 'Cross-client repository association');
|
|
582
646
|
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
583
|
-
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
584
647
|
const hiddenResolve = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest));
|
|
585
648
|
expectStatus(hiddenResolve, 200, 'Inaccessible repository binding resolution');
|
|
586
649
|
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }), 'Inaccessible repository binding was exposed by read-only resolution.');
|
|
587
|
-
assertStateDelta(beforeHiddenResolve, await environment.admin.observeAuthorityState(), {}, 'Inaccessible repository binding resolution');
|
|
588
|
-
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
589
650
|
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-inaccessible-binding', {
|
|
590
651
|
...associationRequest,
|
|
591
652
|
cube_id: cubeC.id,
|
|
592
653
|
})), 403, ErrorCode.ACCESS_DENIED, 'Inaccessible existing repository binding', [cubeA.id, cubeC.id, repository.value]);
|
|
593
|
-
|
|
654
|
+
const hiddenAfterRefusal = await resolve('repository-resolve-after-inaccessible-refusal', resolveRequest);
|
|
655
|
+
const alternateAfterRefusal = await resolve('repository-resolve-alternate-after-inaccessible-refusal', alternateResolveRequest);
|
|
594
656
|
await environment.admin.grantCube(principalA, cubeA);
|
|
657
|
+
invariant(same(hiddenAfterRefusal, { result: 'none' }), 'Inaccessible-binding refusal changed the hidden authoritative binding.');
|
|
658
|
+
invariant(same(alternateAfterRefusal, { result: 'none' }), 'Inaccessible-binding refusal created an alternate binding.');
|
|
659
|
+
invariant(same(await resolve('repository-resolve-restored-after-inaccessible-refusal', resolveRequest), authoritative), 'Inaccessible-binding refusal did not preserve the original binding.');
|
|
595
660
|
return {
|
|
596
661
|
none: true,
|
|
597
662
|
resolved: true,
|
|
598
663
|
idempotent: true,
|
|
599
664
|
repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
600
|
-
|
|
665
|
+
multiple_repositories_per_cube: true,
|
|
601
666
|
inaccessible: ErrorCode.ACCESS_DENIED,
|
|
602
667
|
inaccessible_binding_hidden: true,
|
|
603
668
|
cross_client_binding_hidden: true,
|
|
@@ -890,12 +955,10 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
890
955
|
});
|
|
891
956
|
const recipient = await environment.admin.createDrone(principalA, cube, role);
|
|
892
957
|
const message = 'START NOW exact slice';
|
|
893
|
-
const before = await environment.admin.observeAuthorityState();
|
|
894
958
|
expectError(await environment.operations.append(credentialA, cube, createProtocolEnvelope('mandatory-addressing-omitted', {
|
|
895
959
|
post_id: '30000000-0000-4000-8000-000000000001',
|
|
896
960
|
message,
|
|
897
961
|
})), 400, ErrorCode.INVALID_INPUT, 'Omitted mandatory addressing');
|
|
898
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Mandatory-addressing refusal');
|
|
899
962
|
const broadcast = await environment.operations.append(credentialA, cube, createProtocolEnvelope('mandatory-addressing-broadcast', {
|
|
900
963
|
post_id: '30000000-0000-4000-8000-000000000002',
|
|
901
964
|
message,
|
|
@@ -916,6 +979,10 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
916
979
|
invariant(directedResult.entry.visibility === 'direct', 'Explicit recipient selectors did not produce direct visibility.');
|
|
917
980
|
invariant(directedResult.entry.recipient_drone_ids?.includes(recipient.id) === true, 'Explicit recipient selectors did not resolve the named recipient.');
|
|
918
981
|
invariant(directedResult.entry.recipient_drone_ids?.length === 1, 'Classification changed or fell open the explicit recipient set.');
|
|
982
|
+
const readback = await environment.operations.read(credentialA, cube, createProtocolEnvelope('mandatory-addressing-readback', { cursor: null, limit: 10 }));
|
|
983
|
+
expectStatus(readback, 200, 'Mandatory-addressing persistence readback');
|
|
984
|
+
const persistedIds = decodeReadLogResultEnvelope(readback.body).payload.entries.map((entry) => entry.id);
|
|
985
|
+
invariant(same(persistedIds, [broadcastResult.entry.id, directedResult.entry.id]), 'Rejected mandatory-addressing append was persisted.');
|
|
919
986
|
return { refused_status: 400, code: ErrorCode.INVALID_INPUT, persisted: false, broadcast: true, union: true, no_fall_open: true };
|
|
920
987
|
});
|
|
921
988
|
await record('log.entry-query', async () => {
|
|
@@ -936,6 +1003,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
936
1003
|
'22222222-0000-4000-8000-000000000002',
|
|
937
1004
|
];
|
|
938
1005
|
const entries = [];
|
|
1006
|
+
const idSeeds = [];
|
|
939
1007
|
for (const [index, id] of ids.entries()) {
|
|
940
1008
|
const response = await environment.operations.append(credentialA, cube, createProtocolEnvelope(`entry-query-append-${index}`, {
|
|
941
1009
|
post_id: `40000000-0000-4000-8000-00000000000${index}`,
|
|
@@ -944,12 +1012,12 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
944
1012
|
}));
|
|
945
1013
|
expectStatus(response, 201, `Entry-query fixture append ${index}`);
|
|
946
1014
|
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
947
|
-
|
|
1015
|
+
idSeeds.push({ current_id: entry.id, query_id: id });
|
|
948
1016
|
entries.push({ ...entry, id });
|
|
949
1017
|
}
|
|
1018
|
+
await environment.admin.seedEntryQueryIds(cube, idSeeds);
|
|
950
1019
|
expectStatus(await environment.operations.ack(acknowledgedCredential, cube, createProtocolEnvelope('entry-query-seed-ack', { entry_id: ids[0], kind: 'ack' })), 204, 'Entry-query acknowledgement seed');
|
|
951
1020
|
expectStatus(await environment.operations.ack(claimingCredential, cube, createProtocolEnvelope('entry-query-seed-claim', { entry_id: ids[1], kind: 'claim' })), 204, 'Entry-query claim seed');
|
|
952
|
-
const before = await environment.admin.observeAuthorityState();
|
|
953
1021
|
for (const [requestId, selector, expectedId] of [
|
|
954
1022
|
['entry-query-full', ids[1], ids[1]],
|
|
955
1023
|
['entry-query-prefix', '11111111', ids[0]],
|
|
@@ -961,15 +1029,19 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
961
1029
|
}
|
|
962
1030
|
expectError(await environment.operations.entryQuery(credentialA, cube, createProtocolEnvelope('entry-query-unknown', { entry_id: 'ffffffff' })), 404, ErrorCode.NOT_FOUND, 'Unknown entry query');
|
|
963
1031
|
expectError(await environment.operations.entryQuery(credentialA, cube, createProtocolEnvelope('entry-query-ambiguous', { entry_id: '22222222' })), 409, ErrorCode.LOG_ENTRY_PREFIX_AMBIGUOUS, 'Ambiguous entry query');
|
|
964
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Entry query');
|
|
965
1032
|
const acknowledgementStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(credentialA, cube, createProtocolEnvelope('entry-query-ack-status', { entry_id: ids[0] }))).body).payload;
|
|
966
1033
|
const claimStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(credentialA, cube, createProtocolEnvelope('entry-query-claim-status', { entry_id: ids[1] }))).body).payload;
|
|
967
|
-
|
|
968
|
-
invariant(
|
|
1034
|
+
const acknowledgedRecipients = acknowledgementStatus.recipients.filter((recipient) => recipient.acknowledged_at !== null);
|
|
1035
|
+
invariant(acknowledgedRecipients.length === 1 &&
|
|
1036
|
+
acknowledgedRecipients[0].drone_id === acknowledgedRecipient.id &&
|
|
1037
|
+
acknowledgementStatus.recipients.find((recipient) => recipient.drone_id === claimingRecipient.id)?.acknowledged_at === null, 'Entry query changed the seeded acknowledgement set.');
|
|
1038
|
+
invariant(claimStatus.claims.length === 1 &&
|
|
1039
|
+
claimStatus.claims[0].drone_id === claimingRecipient.id &&
|
|
1040
|
+
claimStatus.recipients.every((recipient) => recipient.acknowledged_at === null), 'Entry query changed the seeded claim or acknowledgement set.');
|
|
969
1041
|
const unreadResponse = await environment.operations.read(credentialA, cube, createProtocolEnvelope('entry-query-unread', { cursor: null, limit: 10 }));
|
|
970
1042
|
expectStatus(unreadResponse, 200, 'Entry-query unread read');
|
|
971
1043
|
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
972
|
-
invariant(unread.entries
|
|
1044
|
+
invariant(same(unread.entries, entries), 'Entry query mutated, consumed, or advanced unread activity.');
|
|
973
1045
|
return {
|
|
974
1046
|
full_uuid: true,
|
|
975
1047
|
unique_prefix: true,
|
|
@@ -981,9 +1053,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
981
1053
|
mutation: 'none',
|
|
982
1054
|
};
|
|
983
1055
|
});
|
|
984
|
-
const entries = [];
|
|
985
|
-
let readCursor = null;
|
|
986
1056
|
await record('log.read-cursor-tuple', async () => {
|
|
1057
|
+
const entries = [];
|
|
987
1058
|
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
988
1059
|
const response = await environment.operations.append(credentialA, cubeA, createProtocolEnvelope(`append-a${index + 1}`, {
|
|
989
1060
|
post_id: `00000000-0000-4000-8000-${String(305 + index).padStart(12, '0')}`,
|
|
@@ -1001,18 +1072,32 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1001
1072
|
invariant(page.has_more && page.behind_by === 1, 'Read pagination metadata is incorrect.');
|
|
1002
1073
|
invariant(page.cursor !== null, 'Read did not return a cursor.');
|
|
1003
1074
|
invariant(page.cursor.id === entries[1].id && page.cursor.created_at === entries[1].created_at, 'Read cursor does not equal the final delivered entry tuple.');
|
|
1004
|
-
readCursor = page.cursor;
|
|
1005
1075
|
return { messages: ['alpha', 'beta'], has_more: true, behind_by: 1, cursor_matches_last_entry: true };
|
|
1006
1076
|
});
|
|
1007
|
-
let liveCursor = null;
|
|
1008
1077
|
await record('sse.replay-live-transition', async () => {
|
|
1009
|
-
|
|
1078
|
+
const principal = await environment.admin.createPrincipal('stream-transition');
|
|
1079
|
+
const cube = await environment.admin.createCube('stream-transition');
|
|
1080
|
+
await environment.admin.grantCube(principal, cube);
|
|
1081
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
1082
|
+
let readCursor;
|
|
1083
|
+
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
1084
|
+
const response = await environment.operations.append(credential, cube, createProtocolEnvelope(`stream-transition-${message}`, {
|
|
1085
|
+
post_id: `00000000-0000-4000-8000-${String(316 + index).padStart(12, '0')}`,
|
|
1086
|
+
to: 'broadcast',
|
|
1087
|
+
message,
|
|
1088
|
+
}));
|
|
1089
|
+
expectStatus(response, 201, `Stream transition append ${message}`);
|
|
1090
|
+
if (message === 'beta') {
|
|
1091
|
+
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
1092
|
+
readCursor = { id: entry.id, created_at: entry.created_at };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1010
1095
|
const barrier = environment.admin.armReplayTransition();
|
|
1011
|
-
const openPromise = environment.operations.openStream(
|
|
1096
|
+
const openPromise = environment.operations.openStream(credential, cube, readCursor);
|
|
1012
1097
|
await within(barrier.reached, 'Replay transition boundary', streamDeadlineMs);
|
|
1013
1098
|
try {
|
|
1014
|
-
const appendDelta = environment.operations.append(
|
|
1015
|
-
post_id: '00000000-0000-4000-8000-
|
|
1099
|
+
const appendDelta = environment.operations.append(credential, cube, createProtocolEnvelope('stream-transition-delta', {
|
|
1100
|
+
post_id: '00000000-0000-4000-8000-000000000319',
|
|
1016
1101
|
to: 'broadcast',
|
|
1017
1102
|
message: 'delta',
|
|
1018
1103
|
}));
|
|
@@ -1036,8 +1121,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1036
1121
|
invariant(bookmark.type === 'bookmark' && bookmark.replay_complete, 'Stream omitted its replay-complete bookmark.');
|
|
1037
1122
|
const noDuplicate = reader.next();
|
|
1038
1123
|
await provePending(noDuplicate, 'Live stream after delta', pendingProbeMs);
|
|
1039
|
-
const epsilonResponse = await environment.operations.append(
|
|
1040
|
-
post_id: '00000000-0000-4000-8000-
|
|
1124
|
+
const epsilonResponse = await environment.operations.append(credential, cube, createProtocolEnvelope('stream-transition-epsilon', {
|
|
1125
|
+
post_id: '00000000-0000-4000-8000-000000000320',
|
|
1041
1126
|
to: 'broadcast',
|
|
1042
1127
|
message: 'epsilon',
|
|
1043
1128
|
}));
|
|
@@ -1046,7 +1131,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1046
1131
|
invariant(epsilon.entry.message === 'epsilon', 'Live stream duplicated or reordered an event.');
|
|
1047
1132
|
invariant(compareLogCursor(delta.cursor, epsilon.cursor) < 0, 'Live stream cursors are not ordered.');
|
|
1048
1133
|
invariant(new Set([replay.entry.id, delta.entry.id, epsilon.entry.id]).size === 3, 'Stream delivered a duplicate entry.');
|
|
1049
|
-
liveCursor = epsilon.cursor;
|
|
1050
1134
|
return { replay: ['gamma'], transition: 'bookmark', live: ['delta', 'epsilon'], duplicates: 0 };
|
|
1051
1135
|
}
|
|
1052
1136
|
finally {
|
|
@@ -1054,20 +1138,37 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1054
1138
|
}
|
|
1055
1139
|
});
|
|
1056
1140
|
await record('cursor.explicit-expiry', async () => {
|
|
1057
|
-
|
|
1058
|
-
await environment.admin.
|
|
1059
|
-
const
|
|
1141
|
+
const cube = await environment.admin.createCube('cursor-expiry');
|
|
1142
|
+
await environment.admin.grantCube(principalA, cube);
|
|
1143
|
+
const appended = await environment.operations.append(credentialA, cube, createProtocolEnvelope('cursor-expiry-entry', {
|
|
1144
|
+
post_id: '00000000-0000-4000-8000-000000000321',
|
|
1145
|
+
to: 'broadcast',
|
|
1146
|
+
message: 'expire this cursor',
|
|
1147
|
+
}));
|
|
1148
|
+
expectStatus(appended, 201, 'Cursor expiry fixture append');
|
|
1149
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
1150
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
1151
|
+
await environment.admin.expireCursor(cube, cursor);
|
|
1152
|
+
const response = await environment.operations.read(credentialA, cube, createProtocolEnvelope('read-expired', { cursor, limit: 10 }));
|
|
1060
1153
|
expectError(response, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor read');
|
|
1061
|
-
const stream = await environment.operations.openStream(credentialA,
|
|
1154
|
+
const stream = await environment.operations.openStream(credentialA, cube, cursor);
|
|
1062
1155
|
expectError(stream, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor stream');
|
|
1063
1156
|
invariant(stream.stream === null, 'Expired cursor stream exposed a body stream.');
|
|
1064
1157
|
return { read_status: 410, stream_status: 410, code: ErrorCode.CURSOR_EXPIRED };
|
|
1065
1158
|
});
|
|
1066
1159
|
await record('acks.idempotent', async () => {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1160
|
+
const cube = await environment.admin.createCube('ack-idempotency');
|
|
1161
|
+
await environment.admin.grantCube(principalA, cube);
|
|
1162
|
+
const appended = await environment.operations.append(credentialA, cube, createProtocolEnvelope('ack-idempotency-entry', {
|
|
1163
|
+
post_id: '00000000-0000-4000-8000-000000000322',
|
|
1164
|
+
to: 'broadcast',
|
|
1165
|
+
message: 'acknowledge this entry',
|
|
1166
|
+
}));
|
|
1167
|
+
expectStatus(appended, 201, 'Acknowledgement fixture append');
|
|
1168
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
1169
|
+
const request = createProtocolEnvelope('ack-entry1', { entry_id: entry.id, kind: 'ack' });
|
|
1170
|
+
const first = await environment.operations.ack(credentialA, cube, request);
|
|
1171
|
+
const second = await environment.operations.ack(credentialA, cube, request);
|
|
1071
1172
|
expectStatus(first, 204, 'First acknowledgement');
|
|
1072
1173
|
expectStatus(second, 204, 'Repeated acknowledgement');
|
|
1073
1174
|
invariant((first.body === '' || first.body === undefined) && (second.body === '' || second.body === undefined), 'Acknowledgement responses must be bodyless.');
|
|
@@ -1092,7 +1193,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1092
1193
|
}));
|
|
1093
1194
|
expectStatus(append, 201, 'Acknowledgement-status fixture append');
|
|
1094
1195
|
const entry = decodeAppendLogResultEnvelope(append.body).payload.entry;
|
|
1095
|
-
const beforeMissing = await environment.admin.observeAuthorityState();
|
|
1096
1196
|
const missingResponse = await environment.operations.ackStatus(credentialA, cube, createProtocolEnvelope('ack-status-missing', { entry_id: entry.id }));
|
|
1097
1197
|
expectStatus(missingResponse, 200, 'Missing acknowledgement status');
|
|
1098
1198
|
const missing = decodeAckStatusResultEnvelope(missingResponse.body).payload;
|
|
@@ -1100,10 +1200,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1100
1200
|
missing.recipients.length === 2 &&
|
|
1101
1201
|
missing.recipients.every((recipient) => recipient.acknowledged_at === null) &&
|
|
1102
1202
|
missing.claims.length === 0, 'Missing acknowledgement was not represented as nullable per-recipient state.');
|
|
1103
|
-
assertStateDelta(beforeMissing, await environment.admin.observeAuthorityState(), {}, 'Missing acknowledgement status query');
|
|
1104
1203
|
expectStatus(await environment.operations.ack(acknowledgedCredential, cube, createProtocolEnvelope('ack-status-ack', { entry_id: entry.id, kind: 'ack' })), 204, 'Acknowledgement-status acknowledgement');
|
|
1105
1204
|
expectStatus(await environment.operations.ack(claimingCredential, cube, createProtocolEnvelope('ack-status-claim', { entry_id: entry.id, kind: 'claim' })), 204, 'Acknowledgement-status claim');
|
|
1106
|
-
const beforeDistinct = await environment.admin.observeAuthorityState();
|
|
1107
1205
|
const distinctResponse = await environment.operations.ackStatus(credentialA, cube, createProtocolEnvelope('ack-status-distinct', { entry_id: entry.id }));
|
|
1108
1206
|
expectStatus(distinctResponse, 200, 'Distinct acknowledgement and claim status');
|
|
1109
1207
|
const distinct = decodeAckStatusResultEnvelope(distinctResponse.body).payload;
|
|
@@ -1113,16 +1211,13 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1113
1211
|
distinct.claims.length === 1 &&
|
|
1114
1212
|
distinct.claims[0].drone_id === claimingRecipient.id &&
|
|
1115
1213
|
distinct.recipients.find((recipient) => recipient.drone_id === claimingRecipient.id)?.acknowledged_at === null, 'Acknowledgement status conflated acknowledgement and claim records.');
|
|
1116
|
-
assertStateDelta(beforeDistinct, await environment.admin.observeAuthorityState(), {}, 'Distinct acknowledgement and claim status query');
|
|
1117
|
-
const unreadResponse = await environment.operations.read(credentialA, cube, createProtocolEnvelope('ack-status-unread', { cursor: null, limit: 10 }));
|
|
1118
|
-
expectStatus(unreadResponse, 200, 'Post-query unread read');
|
|
1119
|
-
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
1120
|
-
invariant(unread.entries.length === 1 && unread.entries[0].id === entry.id, 'Acknowledgement-status query advanced unread delivery state.');
|
|
1121
|
-
const beforeUnknown = await environment.admin.observeAuthorityState();
|
|
1122
1214
|
expectError(await environment.operations.ackStatus(credentialA, cube, createProtocolEnvelope('ack-status-unknown', {
|
|
1123
1215
|
entry_id: '00000000-0000-4000-8000-000000000311',
|
|
1124
1216
|
})), 404, ErrorCode.NOT_FOUND, 'Unknown acknowledgement-status entry');
|
|
1125
|
-
|
|
1217
|
+
const unreadResponse = await environment.operations.read(credentialA, cube, createProtocolEnvelope('ack-status-unread', { cursor: null, limit: 10 }));
|
|
1218
|
+
expectStatus(unreadResponse, 200, 'Post-query unread read');
|
|
1219
|
+
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
1220
|
+
invariant(same(unread.entries, [entry]), 'Acknowledgement-status query mutated or advanced unread delivery state.');
|
|
1126
1221
|
return {
|
|
1127
1222
|
missing_acknowledgement: null,
|
|
1128
1223
|
acknowledgements: 1,
|
|
@@ -1133,15 +1228,26 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1133
1228
|
};
|
|
1134
1229
|
});
|
|
1135
1230
|
await record('claims.durable-noncursor', async () => {
|
|
1136
|
-
|
|
1137
|
-
const
|
|
1231
|
+
const principal = await environment.admin.createPrincipal('claim-noncursor');
|
|
1232
|
+
const cube = await environment.admin.createCube('claim-noncursor');
|
|
1233
|
+
await environment.admin.grantCube(principal, cube);
|
|
1234
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
1235
|
+
const appended = await environment.operations.append(credential, cube, createProtocolEnvelope('claim-fixture-entry', {
|
|
1236
|
+
post_id: '00000000-0000-4000-8000-000000000314',
|
|
1237
|
+
to: 'broadcast',
|
|
1238
|
+
message: 'claim cursor baseline',
|
|
1239
|
+
}));
|
|
1240
|
+
expectStatus(appended, 201, 'Claim fixture append');
|
|
1241
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
1242
|
+
const baselineCursor = { id: entry.id, created_at: entry.created_at };
|
|
1243
|
+
const claim = await environment.operations.ack(credential, cube, createProtocolEnvelope('claim-fixture-claim', { entry_id: entry.id, kind: 'claim' }));
|
|
1138
1244
|
expectStatus(claim, 204, 'Claim');
|
|
1139
|
-
const read = await environment.operations.read(
|
|
1245
|
+
const read = await environment.operations.read(credential, cube, createProtocolEnvelope('claim-fixture-readback', { cursor: baselineCursor, limit: 10 }));
|
|
1140
1246
|
expectStatus(read, 200, 'Claim-state read');
|
|
1141
1247
|
const page = decodeReadLogResultEnvelope(read.body).payload;
|
|
1142
1248
|
invariant(page.entries.length === 0, 'Claim unexpectedly created a log entry.');
|
|
1143
|
-
invariant(page.cursor !== null && compareLogCursor(page.cursor,
|
|
1144
|
-
invariant(page.claims.some((item) => item.log_entry_id ===
|
|
1249
|
+
invariant(page.cursor !== null && compareLogCursor(page.cursor, baselineCursor) === 0, 'Claim advanced the log cursor.');
|
|
1250
|
+
invariant(page.claims.some((item) => item.log_entry_id === entry.id), 'Claim was not durable in a later read.');
|
|
1145
1251
|
return { durable_claims: 1, entries: 0, cursor_advanced: false };
|
|
1146
1252
|
});
|
|
1147
1253
|
await record('decisions.topic-supersession', async () => {
|
|
@@ -1168,6 +1274,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1168
1274
|
let metadataDroneB;
|
|
1169
1275
|
let metadataSessionA = '';
|
|
1170
1276
|
await record('security.drone-management-authorization', async () => {
|
|
1277
|
+
const observationCredential = await environment.admin.issueDroneSession(principalA);
|
|
1171
1278
|
workerRoleA = await environment.admin.createRole(cubeA, {
|
|
1172
1279
|
roleClass: 'worker', isHumanSeat: false,
|
|
1173
1280
|
});
|
|
@@ -1193,8 +1300,15 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1193
1300
|
deniedCredentials.push({ access: access, credential });
|
|
1194
1301
|
}
|
|
1195
1302
|
for (const { access, credential } of deniedCredentials) {
|
|
1303
|
+
const rosterBefore = await environment.operations.listDrones(observationCredential, cubeA);
|
|
1304
|
+
expectStatus(rosterBefore, 200, `${access} principal pre-denial roster`);
|
|
1305
|
+
const workerBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
1306
|
+
invariant(workerBefore, `${access} principal pre-denial roster omitted the managed worker.`);
|
|
1196
1307
|
expectError(await environment.operations.reassignDrone(credential, cubeA, managedWorker, createProtocolEnvelope(`${access}-reassign-denied`, { role_id: workerRoleA.id })), 403, ErrorCode.ACCESS_DENIED, `${access} principal reassignment`);
|
|
1197
1308
|
expectError(await environment.operations.evictDrone(credential, cubeA, managedWorker, createProtocolEnvelope(`${access}-evict-denied`, {})), 403, ErrorCode.ACCESS_DENIED, `${access} principal eviction`);
|
|
1309
|
+
const rosterAfter = await environment.operations.listDrones(observationCredential, cubeA);
|
|
1310
|
+
expectStatus(rosterAfter, 200, `${access} principal post-denial roster`);
|
|
1311
|
+
invariant(same(listedDrone(rosterAfter, managedWorker.id), workerBefore), `${access} principal management denial mutated the managed worker.`);
|
|
1198
1312
|
}
|
|
1199
1313
|
return { read_status: 403, write_status: 403, manage_required: true };
|
|
1200
1314
|
});
|
|
@@ -1213,12 +1327,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1213
1327
|
roleClass: 'worker', isHumanSeat: false,
|
|
1214
1328
|
});
|
|
1215
1329
|
const evictionTarget = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1216
|
-
await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
1330
|
+
const evictionTargetCredential = await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
1217
1331
|
const unknownCube = { id: '00000000-0000-4000-8000-000000000399' };
|
|
1218
|
-
const snapshot = async () => ({
|
|
1219
|
-
cubeA: await environment.admin.inspectCubeManagementState(cubeA),
|
|
1220
|
-
cubeB: await environment.admin.inspectCubeManagementState(cubeB),
|
|
1221
|
-
});
|
|
1222
1332
|
const operations = [
|
|
1223
1333
|
{
|
|
1224
1334
|
name: 'cube-update',
|
|
@@ -1253,15 +1363,42 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1253
1363
|
invoke: (credential, cube) => environment.operations.evictDrone(credential, cube, evictionTarget, createProtocolEnvelope('matrix-drone-evict', {})),
|
|
1254
1364
|
},
|
|
1255
1365
|
];
|
|
1366
|
+
const observableState = async (operationName) => {
|
|
1367
|
+
if (operationName === 'decision-record') {
|
|
1368
|
+
const response = await environment.operations.listDecisions(credentialA, cubeA, createProtocolEnvelope('matrix-observe-decisions', {}));
|
|
1369
|
+
expectStatus(response, 200, 'Matrix decision observation');
|
|
1370
|
+
return decodeDecisionsResultEnvelope(response.body).payload;
|
|
1371
|
+
}
|
|
1372
|
+
if (operationName === 'drone-reassign' || operationName === 'drone-evict') {
|
|
1373
|
+
const response = await environment.operations.listDrones(credentialA, cubeA);
|
|
1374
|
+
expectStatus(response, 200, 'Matrix drone observation');
|
|
1375
|
+
return listedDrone(response, operationName === 'drone-reassign' ? managedWorker.id : evictionTarget.id);
|
|
1376
|
+
}
|
|
1377
|
+
return null;
|
|
1378
|
+
};
|
|
1379
|
+
const assertObservableDenial = async (operationName, before) => {
|
|
1380
|
+
if (operationName === 'role-create') {
|
|
1381
|
+
expectError(await environment.operations.roleRationale(credentialA, cubeA, createProtocolEnvelope('matrix-observe-role', {
|
|
1382
|
+
role: 'Matrix Role',
|
|
1383
|
+
section: 'Workflow rationale',
|
|
1384
|
+
})), 404, ErrorCode.ROLE_NOT_FOUND, 'Denied matrix role observation');
|
|
1385
|
+
}
|
|
1386
|
+
else if (before !== null) {
|
|
1387
|
+
invariant(same(await observableState(operationName), before), `${operationName} changed protocol-visible state after denial.`);
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1256
1390
|
for (const operation of operations) {
|
|
1257
1391
|
for (const [kind, credential] of [
|
|
1258
1392
|
['read', readCredential],
|
|
1259
1393
|
['write', writeCredential],
|
|
1260
1394
|
['drone-session', droneCredential],
|
|
1261
1395
|
]) {
|
|
1262
|
-
const before = await
|
|
1396
|
+
const before = await observableState(operation.name);
|
|
1263
1397
|
expectError(await operation.invoke(credential, cubeA), 403, ErrorCode.ACCESS_DENIED, `${operation.name} by ${kind} principal`);
|
|
1264
|
-
|
|
1398
|
+
await assertObservableDenial(operation.name, before);
|
|
1399
|
+
if (operation.name === 'drone-evict') {
|
|
1400
|
+
expectStatus(await environment.operations.read(evictionTargetCredential, cubeA, createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 })), 200, `${kind} denied eviction target probe`);
|
|
1401
|
+
}
|
|
1265
1402
|
}
|
|
1266
1403
|
for (const [kind, credential, cube] of [
|
|
1267
1404
|
['no-grant', noGrantCredential, cubeA],
|
|
@@ -1269,14 +1406,15 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1269
1406
|
['foreign-cube', credentialA, cubeB],
|
|
1270
1407
|
['unknown-cube', credentialA, unknownCube],
|
|
1271
1408
|
]) {
|
|
1272
|
-
const before = await
|
|
1409
|
+
const before = await observableState(operation.name);
|
|
1273
1410
|
expectError(await operation.invoke(credential, cube), 404, ErrorCode.NOT_FOUND, `${operation.name} against ${kind}`);
|
|
1274
|
-
|
|
1411
|
+
await assertObservableDenial(operation.name, before);
|
|
1412
|
+
if (operation.name === 'drone-evict') {
|
|
1413
|
+
expectStatus(await environment.operations.read(evictionTargetCredential, cubeA, createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 })), 200, `${kind} denied eviction target probe`);
|
|
1414
|
+
}
|
|
1275
1415
|
}
|
|
1276
|
-
const beforeSuccess = await environment.admin.inspectCubeManagementState(cubeA);
|
|
1277
1416
|
const success = await operation.invoke(credentialA, cubeA);
|
|
1278
1417
|
expectStatus(success, operation.successStatus, `${operation.name} by managing principal`);
|
|
1279
|
-
invariant(!same(await environment.admin.inspectCubeManagementState(cubeA), beforeSuccess), `${operation.name} managing success did not mutate its declared state.`);
|
|
1280
1418
|
}
|
|
1281
1419
|
return {
|
|
1282
1420
|
operation_count: operations.length,
|
|
@@ -1284,7 +1422,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1284
1422
|
read_write_status: 403,
|
|
1285
1423
|
drone_session_status: 403,
|
|
1286
1424
|
hidden_status: 404,
|
|
1287
|
-
|
|
1425
|
+
observable_non_mutation_operations: 4,
|
|
1426
|
+
write_only_operations: 2,
|
|
1288
1427
|
role_labels_authoritative: false,
|
|
1289
1428
|
};
|
|
1290
1429
|
});
|
|
@@ -1327,22 +1466,33 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1327
1466
|
roleClass: 'worker', isHumanSeat: false,
|
|
1328
1467
|
});
|
|
1329
1468
|
const foreignDrone = await environment.admin.createDrone(principalB, cubeB, foreignRole);
|
|
1469
|
+
const foreignRosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
1470
|
+
const localRosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
1471
|
+
expectStatus(foreignRosterBefore, 200, 'Pre-denial foreign roster');
|
|
1472
|
+
expectStatus(localRosterBefore, 200, 'Pre-denial local roster');
|
|
1473
|
+
const foreignDroneBefore = listedDrone(foreignRosterBefore, foreignDrone.id);
|
|
1474
|
+
const managedWorkerBefore = listedDrone(localRosterBefore, managedWorker.id);
|
|
1475
|
+
invariant(foreignDroneBefore && managedWorkerBefore, 'Pre-denial roster omitted a drone-management target.');
|
|
1330
1476
|
expectError(await environment.operations.reassignDrone(credentialA, cubeB, foreignDrone, createProtocolEnvelope('cross-cube-reassign', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Cross-cube reassignment');
|
|
1331
1477
|
expectError(await environment.operations.evictDrone(credentialA, cubeB, foreignDrone, createProtocolEnvelope('cross-cube-evict', {})), 404, ErrorCode.NOT_FOUND, 'Cross-cube eviction');
|
|
1332
1478
|
expectError(await environment.operations.reassignDrone(credentialA, cubeA, foreignDrone, createProtocolEnvelope('foreign-drone-local-route', { role_id: workerRoleA.id })), 404, ErrorCode.NOT_FOUND, 'Foreign drone reassignment through authorized cube route');
|
|
1333
1479
|
expectError(await environment.operations.evictDrone(credentialA, cubeA, foreignDrone, createProtocolEnvelope('foreign-drone-local-evict', {})), 404, ErrorCode.NOT_FOUND, 'Foreign drone eviction through authorized cube route');
|
|
1334
1480
|
expectError(await environment.operations.reassignDrone(credentialA, cubeA, managedWorker, createProtocolEnvelope('foreign-role-local-drone', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Foreign role reassignment through authorized cube route');
|
|
1335
1481
|
await environment.admin.grantCube(principalA, cubeB, 'read');
|
|
1336
|
-
const foreignDroneBefore = await environment.admin.inspectManagedDrone(foreignDrone);
|
|
1337
1482
|
expectError(await environment.operations.reassignDrone(workerSession, cubeB, foreignDrone, createProtocolEnvelope('bound-drone-cross-cube-reassign', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Bound drone cross-cube reassignment');
|
|
1338
|
-
invariant(same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore), 'Bound drone cross-cube reassignment mutated the foreign target.');
|
|
1339
1483
|
expectError(await environment.operations.evictDrone(workerSession, cubeB, foreignDrone, createProtocolEnvelope('bound-drone-cross-cube-evict', {})), 404, ErrorCode.NOT_FOUND, 'Bound drone cross-cube eviction');
|
|
1340
|
-
|
|
1484
|
+
const foreignRosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
1485
|
+
const localRosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
1486
|
+
expectStatus(foreignRosterAfter, 200, 'Post-denial foreign roster');
|
|
1487
|
+
expectStatus(localRosterAfter, 200, 'Post-denial local roster');
|
|
1488
|
+
invariant(same(listedDrone(foreignRosterAfter, foreignDrone.id), foreignDroneBefore), 'Denied cross-cube management mutated the foreign drone.');
|
|
1489
|
+
invariant(same(listedDrone(localRosterAfter, managedWorker.id), managedWorkerBefore), 'Denied foreign-role management mutated the local drone.');
|
|
1341
1490
|
return {
|
|
1342
1491
|
unauthorized_cube_status: 404,
|
|
1343
1492
|
foreign_drone_status: 404,
|
|
1344
1493
|
foreign_role_status: 404,
|
|
1345
1494
|
bound_drone_cross_cube_status: 404,
|
|
1495
|
+
targets_unchanged: true,
|
|
1346
1496
|
code: ErrorCode.NOT_FOUND,
|
|
1347
1497
|
};
|
|
1348
1498
|
});
|
|
@@ -1359,11 +1509,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1359
1509
|
drone_id: evictedDrone.id,
|
|
1360
1510
|
evicted: true,
|
|
1361
1511
|
}), 'Eviction response did not identify the terminal seat.');
|
|
1362
|
-
invariant(same(await environment.admin.inspectManagedDrone(evictedDrone), {
|
|
1363
|
-
role_id: workerRoleA.id,
|
|
1364
|
-
evicted: true,
|
|
1365
|
-
session_revoked: true,
|
|
1366
|
-
}), 'Eviction did not atomically mark the drone evicted and revoke its session.');
|
|
1367
1512
|
const after = await environment.operations.listDrones(credentialA, cubeA);
|
|
1368
1513
|
expectStatus(after, 200, 'Post-eviction roster');
|
|
1369
1514
|
invariant(!listedDroneIds(after).includes(evictedDrone.id), 'Evicted drone remained in roster.');
|
|
@@ -1405,44 +1550,78 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1405
1550
|
retry_key: '00000000-0000-4000-8000-000000000352',
|
|
1406
1551
|
client_credential: roleContractReadCredential,
|
|
1407
1552
|
})), 201, 'Role-contract reader enrollment');
|
|
1553
|
+
const refusalRationale = 'Workflow rationale:\nRefusal role remains.';
|
|
1408
1554
|
const defaultRole = await environment.admin.createRole(roleContractCube, {
|
|
1409
1555
|
roleClass: 'worker',
|
|
1410
1556
|
isHumanSeat: false,
|
|
1411
1557
|
name: 'Default Worker',
|
|
1558
|
+
detailedDescription: refusalRationale,
|
|
1412
1559
|
isDefault: true,
|
|
1413
1560
|
});
|
|
1414
1561
|
const mandatoryRole = await environment.admin.createRole(roleContractCube, {
|
|
1415
1562
|
roleClass: 'worker',
|
|
1416
1563
|
isHumanSeat: false,
|
|
1417
1564
|
name: 'Mandatory Worker',
|
|
1565
|
+
detailedDescription: refusalRationale,
|
|
1418
1566
|
isMandatory: true,
|
|
1419
1567
|
});
|
|
1420
1568
|
const humanRole = await environment.admin.createRole(roleContractCube, {
|
|
1421
1569
|
roleClass: 'queen',
|
|
1422
1570
|
isHumanSeat: true,
|
|
1423
1571
|
name: 'Human Seat',
|
|
1572
|
+
detailedDescription: refusalRationale,
|
|
1424
1573
|
});
|
|
1425
1574
|
const activeRole = await environment.admin.createRole(roleContractCube, {
|
|
1426
1575
|
roleClass: 'worker',
|
|
1427
1576
|
isHumanSeat: false,
|
|
1428
1577
|
name: 'Active Worker',
|
|
1578
|
+
detailedDescription: refusalRationale,
|
|
1429
1579
|
});
|
|
1430
|
-
await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
1580
|
+
const activeDrone = await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
1581
|
+
const activeRosterBefore = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
1582
|
+
expectStatus(activeRosterBefore, 200, 'Pre-refusal active-role roster');
|
|
1583
|
+
const activeDroneBefore = listedDrone(activeRosterBefore, activeDrone.id);
|
|
1584
|
+
invariant(activeDroneBefore, 'Pre-refusal roster omitted the active-role drone.');
|
|
1431
1585
|
const refusals = [
|
|
1432
|
-
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role'],
|
|
1433
|
-
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role'],
|
|
1434
|
-
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role'],
|
|
1435
|
-
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role'],
|
|
1586
|
+
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role', 'Default Worker', 'worker', false],
|
|
1587
|
+
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role', 'Mandatory Worker', 'worker', false],
|
|
1588
|
+
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role', 'Human Seat', 'queen', true],
|
|
1589
|
+
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role', 'Active Worker', 'worker', false],
|
|
1436
1590
|
];
|
|
1437
|
-
for (const [index, [role, code, label]] of refusals.entries()) {
|
|
1438
|
-
const before = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
1591
|
+
for (const [index, [role, code, label, roleName, roleClass, isHumanSeat]] of refusals.entries()) {
|
|
1439
1592
|
const response = await environment.operations.deleteRole(roleContractCredential, roleContractCube, role, createProtocolEnvelope(`delete-refusal-${index}`, {}));
|
|
1440
1593
|
expectError(response, 409, code, `Delete ${label}`);
|
|
1441
|
-
invariant(same(await environment.admin.inspectCubeManagementState(roleContractCube), before), `Delete ${label} mutated cube state after refusal.`);
|
|
1442
1594
|
if (code === ErrorCode.ROLE_IN_USE) {
|
|
1443
1595
|
const message = protocolErrorMessage(response) ?? '';
|
|
1444
1596
|
invariant(message === ROLE_IN_USE_DELETE_MESSAGE, 'ROLE_IN_USE did not direct the caller to reassign or evict the drones first.');
|
|
1597
|
+
const roster = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
1598
|
+
expectStatus(roster, 200, 'Active-role refusal roster readback');
|
|
1599
|
+
invariant(same(listedDrone(roster, activeDrone.id), activeDroneBefore), 'Active-role deletion refusal changed the active drone.');
|
|
1445
1600
|
}
|
|
1601
|
+
const retained = await environment.operations.roleRationale(roleContractReadCredential, roleContractCube, createProtocolEnvelope(`delete-refusal-${index}-readback`, {
|
|
1602
|
+
role: role.id,
|
|
1603
|
+
section: 'Workflow rationale',
|
|
1604
|
+
}));
|
|
1605
|
+
expectStatus(retained, 200, `Retained ${label} rationale lookup`);
|
|
1606
|
+
const retainedPayload = decodeRoleRationaleResultEnvelope(retained.body).payload;
|
|
1607
|
+
invariant(retainedPayload.role_id === role.id &&
|
|
1608
|
+
retainedPayload.role_name === roleName &&
|
|
1609
|
+
same(retainedPayload.section, {
|
|
1610
|
+
heading: 'Workflow rationale',
|
|
1611
|
+
body: refusalRationale,
|
|
1612
|
+
}), `Retained ${label} rationale lookup returned a different role or section.`);
|
|
1613
|
+
const shapeSession = `${String(index).repeat(42)}S`;
|
|
1614
|
+
const shapeProbe = await environment.operations.attach(roleContractCredential, createProtocolEnvelope(`delete-refusal-${index}-shape-probe`, {
|
|
1615
|
+
cube_id: roleContractCube.id,
|
|
1616
|
+
role_id: role.id,
|
|
1617
|
+
session_credential: shapeSession,
|
|
1618
|
+
}));
|
|
1619
|
+
expectStatus(shapeProbe, 200, `Retained ${label} shape probe`);
|
|
1620
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
1621
|
+
invariant(shapePayload.role.id === role.id &&
|
|
1622
|
+
shapePayload.role.role_class === roleClass &&
|
|
1623
|
+
shapePayload.role.is_human_seat === isHumanSeat, `Retained ${label} shape probe returned changed role semantics.`);
|
|
1624
|
+
expectStatus(await environment.operations.evictDrone(roleContractCredential, roleContractCube, { id: shapePayload.drone.id }, createProtocolEnvelope(`delete-refusal-${index}-shape-cleanup`, {})), 200, `Retained ${label} shape cleanup`);
|
|
1446
1625
|
}
|
|
1447
1626
|
const unknownRole = { id: '00000000-0000-4000-8000-000000000398' };
|
|
1448
1627
|
expectError(await environment.operations.deleteRole(roleContractCredential, roleContractCube, unknownRole, createProtocolEnvelope('delete-unknown-role', {})), 404, ErrorCode.NOT_FOUND, 'Delete unknown role');
|
|
@@ -1450,10 +1629,32 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1450
1629
|
roleClass: 'worker',
|
|
1451
1630
|
isHumanSeat: false,
|
|
1452
1631
|
name: 'Disposable Worker',
|
|
1632
|
+
detailedDescription: refusalRationale,
|
|
1453
1633
|
});
|
|
1454
|
-
const beforeReadDenial = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
1455
1634
|
expectError(await environment.operations.deleteRole(roleContractReadCredential, roleContractCube, deletableRole, createProtocolEnvelope('delete-role-read-denied', {})), 403, ErrorCode.ACCESS_DENIED, 'Delete role with read authority');
|
|
1456
|
-
|
|
1635
|
+
const retainedDeletable = await environment.operations.roleRationale(roleContractReadCredential, roleContractCube, createProtocolEnvelope('delete-role-read-denied-readback', {
|
|
1636
|
+
role: deletableRole.id,
|
|
1637
|
+
section: 'Workflow rationale',
|
|
1638
|
+
}));
|
|
1639
|
+
expectStatus(retainedDeletable, 200, 'Read-denied retained role rationale lookup');
|
|
1640
|
+
const retainedDeletablePayload = decodeRoleRationaleResultEnvelope(retainedDeletable.body).payload;
|
|
1641
|
+
invariant(retainedDeletablePayload.role_id === deletableRole.id &&
|
|
1642
|
+
retainedDeletablePayload.role_name === 'Disposable Worker' &&
|
|
1643
|
+
same(retainedDeletablePayload.section, {
|
|
1644
|
+
heading: 'Workflow rationale',
|
|
1645
|
+
body: refusalRationale,
|
|
1646
|
+
}), 'Read-authority deletion denial changed the target role.');
|
|
1647
|
+
const shapeProbe = await environment.operations.attach(roleContractCredential, createProtocolEnvelope('delete-role-read-denied-shape-probe', {
|
|
1648
|
+
cube_id: roleContractCube.id,
|
|
1649
|
+
role_id: deletableRole.id,
|
|
1650
|
+
session_credential: 'S'.repeat(43),
|
|
1651
|
+
}));
|
|
1652
|
+
expectStatus(shapeProbe, 200, 'Read-denied retained role shape probe');
|
|
1653
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
1654
|
+
invariant(shapePayload.role.id === deletableRole.id &&
|
|
1655
|
+
shapePayload.role.role_class === 'worker' &&
|
|
1656
|
+
shapePayload.role.is_human_seat === false, 'Read-authority deletion denial changed the target role shape.');
|
|
1657
|
+
expectStatus(await environment.operations.evictDrone(roleContractCredential, roleContractCube, { id: shapePayload.drone.id }, createProtocolEnvelope('delete-role-read-denied-shape-cleanup', {})), 200, 'Read-denied retained role shape cleanup');
|
|
1457
1658
|
const deleted = await environment.operations.deleteRole(roleContractCredential, roleContractCube, deletableRole, createProtocolEnvelope('delete-empty-role', {}));
|
|
1458
1659
|
expectStatus(deleted, 200, 'Delete unassigned role');
|
|
1459
1660
|
invariant(same(decodeDeleteRoleResultEnvelope(deleted.body).payload, {
|
|
@@ -1476,7 +1677,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1476
1677
|
const attributedEntry = decodeAppendLogResultEnvelope(attributed.body).payload.entry;
|
|
1477
1678
|
expectStatus(await environment.operations.evictDrone(roleContractCredential, roleContractCube, evictedDrone, createProtocolEnvelope('role-delete-evict', {})), 200, 'Pre-delete drone eviction');
|
|
1478
1679
|
expectStatus(await environment.operations.deleteRole(roleContractCredential, roleContractCube, evictedRole, createProtocolEnvelope('delete-evicted-role', {})), 200, 'Delete role held only by an evicted drone');
|
|
1479
|
-
invariant((await environment.admin.inspectManagedDrone(evictedDrone)).role_id === defaultRole.id, 'Role deletion did not retarget the evicted drone to the surviving default role.');
|
|
1480
1680
|
const read = await environment.operations.read(roleContractCredential, roleContractCube, createProtocolEnvelope('read-role-delete-attribution', { cursor: null, limit: 500 }));
|
|
1481
1681
|
expectStatus(read, 200, 'Post-delete attributed log read');
|
|
1482
1682
|
invariant(decodeReadLogResultEnvelope(read.body).payload.entries.some((entry) => entry.id === attributedEntry.id && entry.drone_id === evictedDrone.id), 'Deleting the evicted drone role lost the drone attribution of an existing log entry.');
|
|
@@ -1489,7 +1689,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1489
1689
|
ErrorCode.ROLE_REQUIRED,
|
|
1490
1690
|
],
|
|
1491
1691
|
in_use_message_actionable: true,
|
|
1492
|
-
|
|
1692
|
+
refusals_non_mutating: true,
|
|
1493
1693
|
activity_log_attribution_preserved: true,
|
|
1494
1694
|
};
|
|
1495
1695
|
});
|
|
@@ -1548,16 +1748,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1548
1748
|
name: 'Ambiguous Role',
|
|
1549
1749
|
detailedDescription,
|
|
1550
1750
|
});
|
|
1551
|
-
await environment.
|
|
1552
|
-
|
|
1553
|
-
isHumanSeat: false,
|
|
1554
|
-
name: 'AMBIGUOUS ROLE',
|
|
1555
|
-
detailedDescription,
|
|
1556
|
-
});
|
|
1557
|
-
expectError(await environment.operations.roleRationale(roleContractReadCredential, roleContractCube, createProtocolEnvelope('rationale-ambiguous-role', {
|
|
1558
|
-
role: 'ambiguous role',
|
|
1559
|
-
section: 'Workflow rationale',
|
|
1560
|
-
})), 400, ErrorCode.INVALID_INPUT, 'Ambiguous role rationale lookup');
|
|
1751
|
+
expectError(await environment.operations.createRole(roleContractCredential, roleContractCube, createProtocolEnvelope('role-case-variant-duplicate', { name: 'AMBIGUOUS ROLE' })), 409, ErrorCode.INVALID_INPUT, 'Case-variant duplicate role name');
|
|
1752
|
+
expectError(await environment.operations.createRole(roleContractCredential, roleContractCube, createProtocolEnvelope('role-invalid-character', { name: 'Invalid/Role' })), 400, ErrorCode.INVALID_INPUT, 'Invalid role name character');
|
|
1561
1753
|
expectError(await environment.operations.roleRationale(roleContractReadCredential, roleContractCube, createProtocolEnvelope('rationale-invalid-selector', {
|
|
1562
1754
|
role: ' Rationale Builder',
|
|
1563
1755
|
section: 'Workflow rationale',
|
|
@@ -1570,7 +1762,8 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1570
1762
|
unknown_role_code: ErrorCode.ROLE_NOT_FOUND,
|
|
1571
1763
|
inaccessible_role_code: ErrorCode.ROLE_NOT_FOUND,
|
|
1572
1764
|
unknown_section_code: ErrorCode.ROLE_SECTION_NOT_FOUND,
|
|
1573
|
-
|
|
1765
|
+
case_variant_duplicate_status: 409,
|
|
1766
|
+
invalid_name_code: ErrorCode.INVALID_INPUT,
|
|
1574
1767
|
invalid_selector_code: ErrorCode.INVALID_INPUT,
|
|
1575
1768
|
};
|
|
1576
1769
|
});
|
|
@@ -1580,12 +1773,19 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1580
1773
|
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
1581
1774
|
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
1582
1775
|
};
|
|
1776
|
+
try {
|
|
1777
|
+
metadataDroneA = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1778
|
+
metadataSessionA = await environment.admin.issueManagedDroneSession(metadataDroneA);
|
|
1779
|
+
}
|
|
1780
|
+
catch (error) {
|
|
1781
|
+
prerequisiteFailures.set('shared.metadata-baseline', error instanceof Error ? error.message : String(error));
|
|
1782
|
+
}
|
|
1583
1783
|
await record('metadata.attach-report', async () => {
|
|
1584
|
-
|
|
1784
|
+
const attachSession = 'M'.repeat(43);
|
|
1585
1785
|
const created = await environment.operations.attach(credentialA, createProtocolEnvelope('metadata-attach-created', {
|
|
1586
1786
|
cube_id: cubeA.id,
|
|
1587
1787
|
role_id: workerRoleA.id,
|
|
1588
|
-
session_credential:
|
|
1788
|
+
session_credential: attachSession,
|
|
1589
1789
|
runtime_metadata: {
|
|
1590
1790
|
...knownMetadata,
|
|
1591
1791
|
working_repo_origin: 'git@github.com:Byte-Ventures/borg-mcp.git',
|
|
@@ -1596,7 +1796,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1596
1796
|
invariant(createdPayload.result === 'created', 'First metadata attach did not create a seat.');
|
|
1597
1797
|
invariant(same(createdPayload.drone.runtime_metadata, knownMetadata), 'Attach did not echo canonical metadata.');
|
|
1598
1798
|
invariant(createdPayload.drone.runtime_metadata_reported, 'Present attach report was not marked reported.');
|
|
1599
|
-
|
|
1799
|
+
const attachedDrone = { id: createdPayload.drone.id };
|
|
1600
1800
|
const unavailableSession = 'N'.repeat(43);
|
|
1601
1801
|
const unavailable = await environment.operations.attach(credentialA, createProtocolEnvelope('metadata-attach-unavailable', {
|
|
1602
1802
|
cube_id: cubeA.id,
|
|
@@ -1629,13 +1829,13 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1629
1829
|
const reused = await environment.operations.attach(credentialA, createProtocolEnvelope('metadata-attach-reused', {
|
|
1630
1830
|
cube_id: cubeA.id,
|
|
1631
1831
|
role_id: workerRoleA.id,
|
|
1632
|
-
prior_drone_id:
|
|
1633
|
-
session_credential:
|
|
1832
|
+
prior_drone_id: attachedDrone.id,
|
|
1833
|
+
session_credential: attachSession,
|
|
1634
1834
|
runtime_metadata: knownMetadata,
|
|
1635
1835
|
}));
|
|
1636
1836
|
expectStatus(reused, 200, 'Metadata attach reuse');
|
|
1637
1837
|
invariant(decodeAttachResponseEnvelope(reused.body).payload.result === 'reused', 'Prior seat was not reused.');
|
|
1638
|
-
const cleared = await environment.operations.selfMetadataUpdate(
|
|
1838
|
+
const cleared = await environment.operations.selfMetadataUpdate(attachSession, cubeA, createProtocolEnvelope('metadata-clear-all', {
|
|
1639
1839
|
agent_kind: null,
|
|
1640
1840
|
reported_model: null,
|
|
1641
1841
|
working_repo_name: null,
|
|
@@ -1645,7 +1845,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1645
1845
|
const clearedPayload = decodeUpdateDroneRuntimeMetadataResponseEnvelope(cleared.body).payload;
|
|
1646
1846
|
invariant(clearedPayload.runtime_metadata_reported, 'Clear-all patch lost reported state.');
|
|
1647
1847
|
invariant(same(clearedPayload.runtime_metadata, explicitUnknownDrone.runtime_metadata), 'Clear-all patch did not produce explicit unknown metadata.');
|
|
1648
|
-
expectStatus(await environment.operations.selfMetadataUpdate(
|
|
1848
|
+
expectStatus(await environment.operations.selfMetadataUpdate(attachSession, cubeA, createProtocolEnvelope('metadata-restore-known', knownMetadata)), 200, 'Restore known metadata');
|
|
1649
1849
|
return {
|
|
1650
1850
|
created: true,
|
|
1651
1851
|
reused: true,
|
|
@@ -1664,17 +1864,29 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1664
1864
|
{ request_id: 'metadata-null-repeat', patch: { reported_model: null } },
|
|
1665
1865
|
{ request_id: 'metadata-same-value', patch: { agent_kind: 'opencode' } },
|
|
1666
1866
|
];
|
|
1867
|
+
let state;
|
|
1667
1868
|
for (const update of updates) {
|
|
1668
1869
|
const response = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeA, createProtocolEnvelope(update.request_id, update.patch));
|
|
1669
1870
|
expectStatus(response, 200, update.request_id);
|
|
1670
|
-
decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body);
|
|
1871
|
+
state = decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body).payload;
|
|
1671
1872
|
}
|
|
1672
|
-
|
|
1673
|
-
invariant(state.
|
|
1674
|
-
invariant(state.metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
1873
|
+
invariant(state?.runtime_metadata.reported_model === null, 'Explicit null did not clear model metadata.');
|
|
1874
|
+
invariant(state.runtime_metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
1675
1875
|
return { known_replace: true, omitted_unchanged: true, null_clear: true, repeat_safe: true };
|
|
1676
1876
|
});
|
|
1677
1877
|
await record('security.metadata-invalid-atomic', async () => {
|
|
1878
|
+
const baselineResponse = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeA, createProtocolEnvelope('metadata-invalid-baseline', {
|
|
1879
|
+
agent_kind: 'opencode',
|
|
1880
|
+
reported_model: null,
|
|
1881
|
+
working_repo_name: 'Byte-Ventures/borg-mcp-shared',
|
|
1882
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp-shared',
|
|
1883
|
+
}));
|
|
1884
|
+
expectStatus(baselineResponse, 200, 'Invalid metadata baseline');
|
|
1885
|
+
decodeUpdateDroneRuntimeMetadataResponseEnvelope(baselineResponse.body);
|
|
1886
|
+
const baselineRoster = await environment.operations.listDrones(credentialA, cubeA);
|
|
1887
|
+
expectStatus(baselineRoster, 200, 'Invalid metadata baseline roster');
|
|
1888
|
+
const baselineDrone = listedDrone(baselineRoster, metadataDroneA.id);
|
|
1889
|
+
invariant(baselineDrone, 'Invalid metadata baseline roster omitted the metadata seat.');
|
|
1678
1890
|
const invalidPatches = [
|
|
1679
1891
|
{},
|
|
1680
1892
|
{ agent_kind: 'OpenCode' },
|
|
@@ -1692,23 +1904,29 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1692
1904
|
{ last_seen: '2026-07-14T10:00:00.000Z' },
|
|
1693
1905
|
];
|
|
1694
1906
|
for (const [index, patch] of invalidPatches.entries()) {
|
|
1695
|
-
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1696
1907
|
const response = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeA, createProtocolEnvelope(`metadata-invalid-${index}`, patch));
|
|
1697
1908
|
expectError(response, 400, ErrorCode.INVALID_INPUT, `Invalid metadata patch ${index}`);
|
|
1698
|
-
|
|
1909
|
+
const readbackResponse = await environment.operations.listDrones(credentialA, cubeA);
|
|
1910
|
+
expectStatus(readbackResponse, 200, `Invalid metadata readback ${index}`);
|
|
1911
|
+
invariant(same(listedDrone(readbackResponse, metadataDroneA.id), baselineDrone), `Invalid metadata patch ${index} partially mutated state.`);
|
|
1699
1912
|
}
|
|
1700
1913
|
return { cases: invalidPatches.length, atomic: true, prior_safe_state_retained: true };
|
|
1701
1914
|
});
|
|
1702
1915
|
await record('security.metadata-own-seat', async () => {
|
|
1703
1916
|
const peer = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
const
|
|
1917
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
1918
|
+
expectStatus(rosterBefore, 200, 'Pre-update metadata roster');
|
|
1919
|
+
const peerBefore = listedDrone(rosterBefore, peer.id);
|
|
1920
|
+
const managedBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
1921
|
+
const ownBefore = listedDrone(rosterBefore, metadataDroneA.id);
|
|
1922
|
+
invariant(peerBefore && managedBefore && ownBefore, 'Pre-update metadata roster omitted a fixture drone.');
|
|
1707
1923
|
const response = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeA, createProtocolEnvelope('metadata-own-seat', { agent_kind: 'claude' }));
|
|
1708
1924
|
expectStatus(response, 200, 'Own-seat metadata update');
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
invariant((
|
|
1925
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
1926
|
+
expectStatus(rosterAfter, 200, 'Post-update metadata roster');
|
|
1927
|
+
invariant(same(listedDrone(rosterAfter, peer.id), peerBefore), 'Own-seat update mutated a peer seat.');
|
|
1928
|
+
invariant(same(listedDrone(rosterAfter, managedWorker.id), managedBefore), 'Own-seat update mutated another existing seat.');
|
|
1929
|
+
invariant(listedDrone(rosterAfter, metadataDroneA.id)?.role_id === ownBefore.role_id, 'Metadata update changed the current seat role.');
|
|
1712
1930
|
expectError(await environment.operations.selfMetadataUpdate(credentialA, cubeA, createProtocolEnvelope('metadata-manager-denied', { agent_kind: 'codex' })), 403, ErrorCode.ACCESS_DENIED, 'Manager metadata update');
|
|
1713
1931
|
expectError(await environment.operations.selfMetadataUpdate('Z'.repeat(43), cubeA, createProtocolEnvelope('metadata-invalid-session', { agent_kind: null })), 401, ErrorCode.AUTH_INVALID, 'Unknown metadata session');
|
|
1714
1932
|
const rejectedStates = [];
|
|
@@ -1721,9 +1939,20 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1721
1939
|
expectStatus(await environment.operations.evictDrone(credentialA, cubeA, evicted, createProtocolEnvelope('metadata-evict-seat', {})), 200, 'Metadata seat eviction');
|
|
1722
1940
|
rejectedStates.push(['evicted', evicted, evictedSession, 410, ErrorCode.DRONE_EVICTED]);
|
|
1723
1941
|
for (const [label, drone, session, status, code] of rejectedStates) {
|
|
1724
|
-
const
|
|
1942
|
+
const rosterBefore = label === 'revoked'
|
|
1943
|
+
? await environment.operations.listDrones(credentialA, cubeA)
|
|
1944
|
+
: null;
|
|
1945
|
+
if (rosterBefore)
|
|
1946
|
+
expectStatus(rosterBefore, 200, 'Pre-denial revoked metadata roster');
|
|
1947
|
+
const droneBefore = rosterBefore ? listedDrone(rosterBefore, drone.id) : null;
|
|
1948
|
+
if (rosterBefore)
|
|
1949
|
+
invariant(droneBefore, 'Pre-denial roster omitted the revoked metadata seat.');
|
|
1725
1950
|
expectError(await environment.operations.selfMetadataUpdate(session, cubeA, createProtocolEnvelope(`metadata-${label}-denied`, { agent_kind: null })), status, code, `${label} metadata session`);
|
|
1726
|
-
|
|
1951
|
+
if (droneBefore) {
|
|
1952
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
1953
|
+
expectStatus(rosterAfter, 200, 'Post-denial revoked metadata roster');
|
|
1954
|
+
invariant(same(listedDrone(rosterAfter, drone.id), droneBefore), 'Revoked metadata denial mutated the protocol-visible seat.');
|
|
1955
|
+
}
|
|
1727
1956
|
}
|
|
1728
1957
|
return {
|
|
1729
1958
|
own_seat_only: true,
|
|
@@ -1750,19 +1979,27 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1750
1979
|
}));
|
|
1751
1980
|
expectStatus(attachedB, 200, 'Cube B metadata attach');
|
|
1752
1981
|
metadataDroneB = { id: decodeAttachResponseEnvelope(attachedB.body).payload.drone.id };
|
|
1753
|
-
const
|
|
1982
|
+
const rosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
1983
|
+
expectStatus(rosterBefore, 200, 'Pre-probe cube B roster');
|
|
1984
|
+
const beforeB = listedDrone(rosterBefore, metadataDroneB.id);
|
|
1985
|
+
invariant(beforeB, 'Pre-probe cube B roster omitted its metadata seat.');
|
|
1754
1986
|
const foreign = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeB, createProtocolEnvelope('metadata-cross-cube', { reported_model: 'foreign/probe' }));
|
|
1755
1987
|
expectError(foreign, 404, ErrorCode.NOT_FOUND, 'Cross-cube metadata update');
|
|
1756
1988
|
const unknown = await environment.operations.selfMetadataUpdate(metadataSessionA, { id: '90000000-0000-4000-8000-000000000001' }, createProtocolEnvelope('metadata-unknown-cube', { reported_model: 'foreign/probe' }));
|
|
1757
1989
|
expectError(unknown, 404, ErrorCode.NOT_FOUND, 'Unknown-cube metadata update');
|
|
1758
1990
|
invariant(same(foreign.body, unknown.body), 'Foreign and unknown metadata probes were distinguishable.');
|
|
1759
|
-
|
|
1991
|
+
const rosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
1992
|
+
expectStatus(rosterAfter, 200, 'Post-probe cube B roster');
|
|
1993
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneB.id), beforeB), 'Cross-cube metadata update mutated the foreign seat.');
|
|
1760
1994
|
const listedA = await environment.operations.listDrones(credentialA, cubeA);
|
|
1761
1995
|
invariant(!listedDroneIds(listedA).includes(metadataDroneB.id), 'Cube A roster disclosed cube B metadata seat.');
|
|
1762
1996
|
return { update_status: 404, foreign_unknown_indistinguishable: true, foreign_unchanged: true, roster_isolated: true };
|
|
1763
1997
|
});
|
|
1764
1998
|
await record('security.metadata-noninterference', async () => {
|
|
1765
|
-
const
|
|
1999
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
2000
|
+
expectStatus(rosterBefore, 200, 'Pre-update noninterference roster');
|
|
2001
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
2002
|
+
invariant(before, 'Pre-update noninterference roster omitted the metadata seat.');
|
|
1766
2003
|
for (const [requestId, patch] of [
|
|
1767
2004
|
['metadata-noninterference-known', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
1768
2005
|
['metadata-noninterference-repeat', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
@@ -1778,28 +2015,28 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1778
2015
|
session_credential: metadataSessionA,
|
|
1779
2016
|
}));
|
|
1780
2017
|
expectStatus(reattached, 200, 'Metadata non-interference reattach');
|
|
1781
|
-
const
|
|
1782
|
-
|
|
1783
|
-
const
|
|
1784
|
-
invariant(
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
role_unchanged: true,
|
|
1788
|
-
liveness_unchanged: true,
|
|
1789
|
-
logs_unchanged: true,
|
|
1790
|
-
model_turns_unchanged: true,
|
|
1791
|
-
};
|
|
2018
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
2019
|
+
expectStatus(rosterAfter, 200, 'Post-update noninterference roster');
|
|
2020
|
+
const after = listedDrone(rosterAfter, metadataDroneA.id);
|
|
2021
|
+
invariant(after, 'Post-update noninterference roster omitted the metadata seat.');
|
|
2022
|
+
invariant(same(withoutRuntimeMetadata(before), withoutRuntimeMetadata(after)), 'Metadata update changed non-metadata roster state.');
|
|
2023
|
+
return { non_metadata_roster_state_unchanged: true };
|
|
1792
2024
|
});
|
|
1793
2025
|
await record('security.metadata-secret-non-echo', async () => {
|
|
1794
2026
|
const marker = 'SECRET-METADATA-MARKER';
|
|
1795
|
-
const
|
|
2027
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
2028
|
+
expectStatus(rosterBefore, 200, 'Pre-secret metadata roster');
|
|
2029
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
2030
|
+
invariant(before, 'Pre-secret metadata roster omitted the metadata seat.');
|
|
1796
2031
|
const response = await environment.operations.selfMetadataUpdate(metadataSessionA, cubeA, createProtocolEnvelope('metadata-secret', {
|
|
1797
2032
|
working_repo_name: 'owner/repo',
|
|
1798
2033
|
working_repo_origin: `https://user:${marker}@github.com/owner/repo?token=${marker}`,
|
|
1799
2034
|
}));
|
|
1800
2035
|
expectError(response, 400, ErrorCode.INVALID_INPUT, 'Secret-bearing metadata update');
|
|
1801
2036
|
invariant(!JSON.stringify(response.body).includes(marker), 'Metadata error echoed hostile secret input.');
|
|
1802
|
-
|
|
2037
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
2038
|
+
expectStatus(rosterAfter, 200, 'Post-secret metadata roster');
|
|
2039
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneA.id), before), 'Secret-bearing metadata input was persisted.');
|
|
1803
2040
|
const hostileKeys = [
|
|
1804
2041
|
'SECRET-METADATA-KEY-MARKER',
|
|
1805
2042
|
`control\u001b[2J`,
|
|
@@ -1825,8 +2062,19 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1825
2062
|
};
|
|
1826
2063
|
});
|
|
1827
2064
|
await record('security.active-stream-revocation', async () => {
|
|
1828
|
-
|
|
1829
|
-
const
|
|
2065
|
+
const principal = await environment.admin.createPrincipal('stream-revocation');
|
|
2066
|
+
const cube = await environment.admin.createCube('stream-revocation');
|
|
2067
|
+
await environment.admin.grantCube(principal, cube);
|
|
2068
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
2069
|
+
const appended = await environment.operations.append(credential, cube, createProtocolEnvelope('stream-revocation-entry', {
|
|
2070
|
+
post_id: '00000000-0000-4000-8000-000000000315',
|
|
2071
|
+
to: 'broadcast',
|
|
2072
|
+
message: 'revocation cursor baseline',
|
|
2073
|
+
}));
|
|
2074
|
+
expectStatus(appended, 201, 'Revocation fixture append');
|
|
2075
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2076
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
2077
|
+
const opened = await environment.operations.openStream(credential, cube, cursor);
|
|
1830
2078
|
expectStatus(opened, 200, 'Revocation stream open');
|
|
1831
2079
|
invariant(opened.stream, 'Successful revocation stream omitted its AsyncIterable.');
|
|
1832
2080
|
const reader = new SseEventReader(opened.stream);
|
|
@@ -1835,7 +2083,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1835
2083
|
invariant(bookmark.type === 'bookmark' && bookmark.replay_complete, 'Fresh live stream did not complete replay.');
|
|
1836
2084
|
const pending = reader.next();
|
|
1837
2085
|
await provePending(pending, 'Idle live stream', pendingProbeMs);
|
|
1838
|
-
await environment.admin.revokePrincipal(
|
|
2086
|
+
await environment.admin.revokePrincipal(principal);
|
|
1839
2087
|
let terminated = false;
|
|
1840
2088
|
try {
|
|
1841
2089
|
await within(pending, 'Revoked stream termination', streamDeadlineMs);
|
|
@@ -1850,7 +2098,7 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1850
2098
|
finally {
|
|
1851
2099
|
await reader.close();
|
|
1852
2100
|
}
|
|
1853
|
-
const rejected = await environment.operations.read(
|
|
2101
|
+
const rejected = await environment.operations.read(credential, cube, createProtocolEnvelope('read-revoked', { cursor, limit: 10 }));
|
|
1854
2102
|
expectError(rejected, 401, ErrorCode.SESSION_REVOKED, 'Post-revocation request');
|
|
1855
2103
|
return { stream_terminated: true, subsequent_status: 401, subsequent_code: ErrorCode.SESSION_REVOKED };
|
|
1856
2104
|
});
|
|
@@ -1873,13 +2121,14 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1873
2121
|
const writer = await enrollParent('delete-writer', 'W', '704');
|
|
1874
2122
|
const outsider = await enrollParent('delete-outsider', 'O', '705');
|
|
1875
2123
|
await environment.admin.grantCreateCubeCapability(creator.principal);
|
|
1876
|
-
const
|
|
2124
|
+
const deleteCubeRequest = {
|
|
1877
2125
|
retry_key: '00000000-0000-4000-8000-000000000706',
|
|
1878
2126
|
name: 'Disposable Cube',
|
|
1879
2127
|
working_repo_name: 'disposable-cube',
|
|
1880
2128
|
repository: { kind: 'local', value: '00000000-0000-4000-8000-000000000707' },
|
|
1881
|
-
template: '
|
|
1882
|
-
}
|
|
2129
|
+
template: 'software-dev',
|
|
2130
|
+
};
|
|
2131
|
+
const createResponse = await environment.operations.createCube(creator.credential, createProtocolEnvelope('delete-cube-create', deleteCubeRequest));
|
|
1883
2132
|
expectStatus(createResponse, 201, 'Disposable cube creation');
|
|
1884
2133
|
const created = decodeCreateCubeResponseEnvelope(createResponse.body).payload;
|
|
1885
2134
|
const cube = { id: created.cube_id };
|
|
@@ -1889,6 +2138,14 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1889
2138
|
await environment.admin.grantCube(writer.principal, cube, 'write');
|
|
1890
2139
|
const drone = await environment.admin.createDrone(creator.principal, cube, role);
|
|
1891
2140
|
const droneCredential = await environment.admin.issueManagedDroneSession(drone);
|
|
2141
|
+
const roleProbeSession = 'T'.repeat(43);
|
|
2142
|
+
const initialRoleProbe = await environment.operations.attach(manager.credential, createProtocolEnvelope('delete-role-shape-baseline', {
|
|
2143
|
+
cube_id: cube.id,
|
|
2144
|
+
role_id: role.id,
|
|
2145
|
+
session_credential: roleProbeSession,
|
|
2146
|
+
}));
|
|
2147
|
+
expectStatus(initialRoleProbe, 200, 'Deletion default-role shape baseline');
|
|
2148
|
+
const roleProbeDrone = decodeAttachResponseEnvelope(initialRoleProbe.body).payload.drone;
|
|
1892
2149
|
const append = await environment.operations.append(creator.credential, cube, createProtocolEnvelope('delete-log', {
|
|
1893
2150
|
post_id: '00000000-0000-4000-8000-000000000312',
|
|
1894
2151
|
to: 'broadcast',
|
|
@@ -1898,16 +2155,45 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1898
2155
|
const entry = decodeAppendLogResultEnvelope(append.body).payload.entry;
|
|
1899
2156
|
expectStatus(await environment.operations.ack(creator.credential, cube, createProtocolEnvelope('delete-claim', { entry_id: entry.id, kind: 'claim' })), 204, 'Deletion fixture claim');
|
|
1900
2157
|
expectStatus(await environment.operations.recordDecision(manager.credential, cube, createProtocolEnvelope('delete-decision', { topic: 'cleanup', decision: 'delete' })), 201, 'Deletion fixture decision');
|
|
1901
|
-
const
|
|
2158
|
+
const readDenialState = async (label) => {
|
|
2159
|
+
const decisions = await environment.operations.listDecisions(manager.credential, cube, createProtocolEnvelope(`delete-${label}-decision-readback`, {}));
|
|
2160
|
+
expectStatus(decisions, 200, `${label} deletion decision readback`);
|
|
2161
|
+
const active = decodeDecisionsResultEnvelope(decisions.body).payload.decisions;
|
|
2162
|
+
const log = await environment.operations.read(manager.credential, cube, createProtocolEnvelope(`delete-${label}-log-readback`, { cursor: null, limit: 500 }));
|
|
2163
|
+
expectStatus(log, 200, `${label} deletion log readback`);
|
|
2164
|
+
const logPayload = decodeReadLogResultEnvelope(log.body).payload;
|
|
2165
|
+
const roster = await environment.operations.listDrones(manager.credential, cube);
|
|
2166
|
+
expectStatus(roster, 200, `${label} deletion roster readback`);
|
|
2167
|
+
const roleReadback = await environment.operations.attach(manager.credential, createProtocolEnvelope(`delete-${label}-role-readback`, {
|
|
2168
|
+
cube_id: cube.id,
|
|
2169
|
+
role_id: role.id,
|
|
2170
|
+
prior_drone_id: roleProbeDrone.id,
|
|
2171
|
+
session_credential: roleProbeSession,
|
|
2172
|
+
}));
|
|
2173
|
+
expectStatus(roleReadback, 200, `${label} deletion role readback`);
|
|
2174
|
+
const rolePayload = decodeAttachResponseEnvelope(roleReadback.body).payload.role;
|
|
2175
|
+
return {
|
|
2176
|
+
decisions: active,
|
|
2177
|
+
entries: logPayload.entries,
|
|
2178
|
+
claims: logPayload.claims,
|
|
2179
|
+
drone: listedDrone(roster, drone.id),
|
|
2180
|
+
role: rolePayload,
|
|
2181
|
+
};
|
|
2182
|
+
};
|
|
2183
|
+
const denialBaseline = await readDenialState('baseline');
|
|
2184
|
+
const assertDenialState = async (label) => {
|
|
2185
|
+
invariant(same(await readDenialState(label), denialBaseline), `${label} cube deletion denial mutated protocol-visible cube state.`);
|
|
2186
|
+
};
|
|
1902
2187
|
for (const [kind, credential] of [
|
|
1903
2188
|
['read', reader.credential],
|
|
1904
2189
|
['write', writer.credential],
|
|
1905
2190
|
['drone-session', droneCredential],
|
|
1906
2191
|
]) {
|
|
1907
2192
|
expectError(await environment.operations.deleteCube(credential, cube, createProtocolEnvelope(`delete-${kind}-denied`, {})), 403, ErrorCode.ACCESS_DENIED, `${kind} cube deletion`);
|
|
1908
|
-
|
|
2193
|
+
await assertDenialState(kind);
|
|
1909
2194
|
}
|
|
1910
2195
|
expectError(await environment.operations.deleteCube(outsider.credential, cube, createProtocolEnvelope('delete-outsider-denied', {})), 404, ErrorCode.NOT_FOUND, 'Never-authorized cube deletion');
|
|
2196
|
+
await assertDenialState('outsider');
|
|
1911
2197
|
const parentStream = await environment.operations.openStream(manager.credential, cube, null);
|
|
1912
2198
|
const droneStream = await environment.operations.openStream(droneCredential, cube, null);
|
|
1913
2199
|
expectStatus(parentStream, 200, 'Deletion parent stream');
|
|
@@ -1949,19 +2235,6 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1949
2235
|
invariant(closed, 'Deleted cube stream yielded after its terminal error.');
|
|
1950
2236
|
await streamReader.close();
|
|
1951
2237
|
}
|
|
1952
|
-
invariant(same(await environment.admin.inspectDeletedCube(cube), {
|
|
1953
|
-
cube_exists: false,
|
|
1954
|
-
role_count: 0,
|
|
1955
|
-
drone_count: 0,
|
|
1956
|
-
log_count: 0,
|
|
1957
|
-
claim_count: 0,
|
|
1958
|
-
decision_count: 0,
|
|
1959
|
-
grant_count: 0,
|
|
1960
|
-
cube_create_binding_count: 0,
|
|
1961
|
-
repository_association_count: 0,
|
|
1962
|
-
active_stream_count: 0,
|
|
1963
|
-
terminal_credential_count: 5,
|
|
1964
|
-
}), 'Cube deletion did not atomically remove cube-owned state and preserve only terminal credentials.');
|
|
1965
2238
|
for (const [kind, credential] of [
|
|
1966
2239
|
['creator', creator.credential],
|
|
1967
2240
|
['manager', manager.credential],
|
|
@@ -1974,30 +2247,22 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
1974
2247
|
}
|
|
1975
2248
|
expectError(await environment.operations.deleteCube(outsider.credential, cube, createProtocolEnvelope('delete-outsider-hidden-repeat', {})), 404, ErrorCode.NOT_FOUND, 'Never-authorized post-delete DELETE');
|
|
1976
2249
|
expectError(await environment.operations.read(outsider.credential, cube, createProtocolEnvelope('delete-outsider-hidden', { cursor: null, limit: 1 })), 404, ErrorCode.NOT_FOUND, 'Never-authorized post-delete request');
|
|
1977
|
-
await environment.
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
['reader', reader.credential],
|
|
1982
|
-
['writer', writer.credential],
|
|
1983
|
-
['drone', droneCredential],
|
|
1984
|
-
]) {
|
|
1985
|
-
expectError(await environment.operations.read(credential, cube, createProtocolEnvelope(`delete-${kind}-after-restart`, { cursor: null, limit: 1 })), PROTOCOL_HTTP_CONTRACT.cube_deleted_status, ErrorCode.CUBE_DELETED, `${kind} post-restart deleted-cube request`);
|
|
1986
|
-
expectError(await environment.operations.deleteCube(credential, cube, createProtocolEnvelope(`delete-${kind}-repeat-after-restart`, {})), PROTOCOL_HTTP_CONTRACT.cube_deleted_status, ErrorCode.CUBE_DELETED, `${kind} post-restart DELETE`);
|
|
1987
|
-
}
|
|
1988
|
-
expectError(await environment.operations.deleteCube(outsider.credential, cube, createProtocolEnvelope('delete-outsider-hidden-repeat-after-restart', {})), 404, ErrorCode.NOT_FOUND, 'Never-authorized post-restart DELETE');
|
|
1989
|
-
expectError(await environment.operations.read(outsider.credential, cube, createProtocolEnvelope('delete-outsider-hidden-after-restart', { cursor: null, limit: 1 })), 404, ErrorCode.NOT_FOUND, 'Never-authorized post-restart request');
|
|
2250
|
+
const recreatedResponse = await environment.operations.createCube(creator.credential, createProtocolEnvelope('delete-cube-recreate', deleteCubeRequest));
|
|
2251
|
+
expectStatus(recreatedResponse, 201, 'Post-delete cube recreation');
|
|
2252
|
+
const recreated = decodeCreateCubeResponseEnvelope(recreatedResponse.body).payload;
|
|
2253
|
+
invariant(recreated.result === 'created' && recreated.cube_id !== cube.id, 'Post-delete recreation resolved retained cube-owned creation state.');
|
|
1990
2254
|
return {
|
|
1991
2255
|
deletion_status: 200,
|
|
1992
|
-
cascade_complete: true,
|
|
1993
2256
|
terminal_stream_error: ErrorCode.CUBE_DELETED,
|
|
1994
2257
|
terminal_http_status: 410,
|
|
1995
|
-
terminal_state_durable_after_restart: true,
|
|
1996
2258
|
unknown_hidden_status: 404,
|
|
1997
2259
|
non_member_manager: true,
|
|
2260
|
+
recreation_created_new_cube: true,
|
|
1998
2261
|
};
|
|
1999
2262
|
});
|
|
2000
|
-
const normalizedTranscript = results
|
|
2263
|
+
const normalizedTranscript = results
|
|
2264
|
+
.filter((result) => !result.skipped)
|
|
2265
|
+
.map(({ id, observations }) => ({ id, observations }));
|
|
2001
2266
|
return { ok: results.every((result) => result.ok), results, normalizedTranscript };
|
|
2002
2267
|
}
|
|
2003
2268
|
//# sourceMappingURL=adapter.js.map
|