borgmcp-shared 1.0.0 → 1.1.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 +28 -5
- 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 +528 -267
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +2 -39
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +13 -42
- 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 +3 -3
- 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.0.1.md +5 -0
- package/docs/releases/1.1.0.md +19 -0
- package/docs/releasing.md +13 -23
- package/package.json +1 -2
- package/src/conformance/adapter.ts +779 -614
- package/src/conformance/index.ts +19 -62
- package/src/protocol/contract.ts +3 -3
- 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 -247
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
decodeEnrollmentExchangeResponseEnvelope,
|
|
14
14
|
decodeInvitationArtifact,
|
|
15
15
|
decodeAttachResponseEnvelope,
|
|
16
|
-
decodeDroneRuntimeMetadataPatch,
|
|
17
16
|
decodeEvictDroneResultEnvelope,
|
|
18
17
|
decodeDeleteRoleResultEnvelope,
|
|
19
18
|
decodeRoleRationaleResultEnvelope,
|
|
@@ -34,13 +33,10 @@ import {
|
|
|
34
33
|
PROTOCOL_VERSION,
|
|
35
34
|
ROLE_IN_USE_DELETE_MESSAGE,
|
|
36
35
|
utf8ByteLength,
|
|
37
|
-
type CreateCubeResponse,
|
|
38
36
|
type CreateCubeRepository,
|
|
39
37
|
type CubeTemplate,
|
|
40
|
-
type ResolvedRepositoryCube,
|
|
41
38
|
type LogCursor,
|
|
42
39
|
type StreamEvent,
|
|
43
|
-
type DroneRuntimeMetadata,
|
|
44
40
|
type CubeDocument,
|
|
45
41
|
type EnrichedStreamEntry,
|
|
46
42
|
} from '../protocol/index.js';
|
|
@@ -74,95 +70,8 @@ export interface ConformanceDrone {
|
|
|
74
70
|
readonly id: string;
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
export interface ConformanceDroneRuntimeState {
|
|
78
|
-
readonly metadata: DroneRuntimeMetadata;
|
|
79
|
-
readonly metadata_reported: boolean;
|
|
80
|
-
readonly metadata_revision: number;
|
|
81
|
-
readonly cube_id: string;
|
|
82
|
-
readonly role_id: string;
|
|
83
|
-
readonly session_state: 'active' | 'revoked';
|
|
84
|
-
readonly evicted: boolean;
|
|
85
|
-
readonly last_seen: string;
|
|
86
|
-
readonly heartbeat_count: number;
|
|
87
|
-
readonly wake_count: number;
|
|
88
|
-
readonly log_count: number;
|
|
89
|
-
readonly model_turn_count: number;
|
|
90
|
-
readonly grant_access: ConformanceCubeAccess | null;
|
|
91
|
-
readonly server_capabilities: readonly string[];
|
|
92
|
-
readonly principal_revoked: boolean;
|
|
93
|
-
readonly session_bound: boolean;
|
|
94
|
-
readonly last_log_post: string | null;
|
|
95
|
-
readonly last_regen_at: string | null;
|
|
96
|
-
readonly last_read_log_at: string | null;
|
|
97
|
-
readonly last_event_received_at: string | null;
|
|
98
|
-
readonly wake_path: 'live';
|
|
99
|
-
readonly wake_alert: null;
|
|
100
|
-
readonly monitor_armed: boolean;
|
|
101
|
-
readonly sse_connected: boolean;
|
|
102
|
-
readonly claim_count: number;
|
|
103
|
-
readonly decision_count: number;
|
|
104
|
-
readonly routing_eligible: boolean;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
73
|
export type ConformanceCubeAccess = 'read' | 'write' | 'manage';
|
|
108
74
|
|
|
109
|
-
export interface ConformanceCubeManagementState {
|
|
110
|
-
readonly directive: string;
|
|
111
|
-
readonly taxonomy_marker: string | null;
|
|
112
|
-
readonly role_ids: readonly string[];
|
|
113
|
-
readonly active_decision_ids: readonly string[];
|
|
114
|
-
readonly drones: ReadonlyArray<{
|
|
115
|
-
readonly id: string;
|
|
116
|
-
readonly role_id: string;
|
|
117
|
-
readonly evicted: boolean;
|
|
118
|
-
readonly session_revoked: boolean;
|
|
119
|
-
}>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface ConformanceAuthorityState {
|
|
123
|
-
enrolled_clients: number;
|
|
124
|
-
enrollment_claims: number;
|
|
125
|
-
activity_acknowledgements: number;
|
|
126
|
-
activity_claims: number;
|
|
127
|
-
activity_log_entries: number;
|
|
128
|
-
cubes: number;
|
|
129
|
-
roles: number;
|
|
130
|
-
grants: number;
|
|
131
|
-
server_capabilities: number;
|
|
132
|
-
cube_create_bindings: number;
|
|
133
|
-
repository_associations: number;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface ConformanceCreatedCubeState {
|
|
137
|
-
cube_exists: boolean;
|
|
138
|
-
creator_has_grant: boolean;
|
|
139
|
-
creator_access: 'manage' | null;
|
|
140
|
-
grant_count: number;
|
|
141
|
-
role_count: number;
|
|
142
|
-
name: string;
|
|
143
|
-
working_repo_name: string;
|
|
144
|
-
repository: CreateCubeRepository;
|
|
145
|
-
template: CubeTemplate;
|
|
146
|
-
human_seat_role_id: string;
|
|
147
|
-
default_worker_role_id: string;
|
|
148
|
-
human_seat_role_matches: boolean;
|
|
149
|
-
default_worker_role_matches: boolean;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export interface ConformanceDeletedCubeState {
|
|
153
|
-
readonly cube_exists: boolean;
|
|
154
|
-
readonly role_count: number;
|
|
155
|
-
readonly drone_count: number;
|
|
156
|
-
readonly log_count: number;
|
|
157
|
-
readonly claim_count: number;
|
|
158
|
-
readonly decision_count: number;
|
|
159
|
-
readonly grant_count: number;
|
|
160
|
-
readonly cube_create_binding_count: number;
|
|
161
|
-
readonly repository_association_count: number;
|
|
162
|
-
readonly active_stream_count: number;
|
|
163
|
-
readonly terminal_credential_count: number;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
75
|
export interface ConformanceRepositoryCubeFixture {
|
|
167
76
|
cube_id: string;
|
|
168
77
|
name: string;
|
|
@@ -172,18 +81,6 @@ export interface ConformanceRepositoryCubeFixture {
|
|
|
172
81
|
access: 'manage';
|
|
173
82
|
}
|
|
174
83
|
|
|
175
|
-
export interface ConformanceEnrollmentPrincipalState {
|
|
176
|
-
response_client_matches: boolean;
|
|
177
|
-
active_credential_bindings: number;
|
|
178
|
-
/**
|
|
179
|
-
* The principal's currently-bound credential still equals the one it enrolled
|
|
180
|
-
* with. Since the credential-free tag preflight cannot probe credentials, this
|
|
181
|
-
* out-of-band authority check is what proves a rejected mismatch retry did not
|
|
182
|
-
* overwrite the good credential.
|
|
183
|
-
*/
|
|
184
|
-
bound_credential_matches_enrollment: boolean;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
84
|
export interface ConformanceReplayBarrier {
|
|
188
85
|
/** Resolves after the replay snapshot is read but before live delivery is active. */
|
|
189
86
|
readonly reached: Promise<void>;
|
|
@@ -201,8 +98,6 @@ export interface ConformanceStreamResponse extends ConformanceHttpResponse {
|
|
|
201
98
|
*/
|
|
202
99
|
export interface ConformanceAdmin {
|
|
203
100
|
reset(): Promise<void>;
|
|
204
|
-
/** Restarts the authority while preserving durable state. */
|
|
205
|
-
restartAuthority(): Promise<void>;
|
|
206
101
|
createPrincipal(name: string): Promise<ConformancePrincipal>;
|
|
207
102
|
createCube(name: string): Promise<ConformanceCube>;
|
|
208
103
|
/** Grants the requested cube authority; omitted access defaults to manage. */
|
|
@@ -220,7 +115,15 @@ export interface ConformanceAdmin {
|
|
|
220
115
|
readonly isDefault?: boolean;
|
|
221
116
|
readonly isMandatory?: boolean;
|
|
222
117
|
}): Promise<ConformanceRole>;
|
|
223
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Seeds deterministic IDs required to exercise full, unique-prefix, and
|
|
120
|
+
* ambiguous-prefix lookup. Protocol-assigned random IDs cannot portably
|
|
121
|
+
* guarantee those prefix shapes, so this remains a semantic synthetic control.
|
|
122
|
+
*/
|
|
123
|
+
seedEntryQueryIds(
|
|
124
|
+
cube: ConformanceCube,
|
|
125
|
+
entries: readonly { current_id: string; query_id: string }[],
|
|
126
|
+
): Promise<void>;
|
|
224
127
|
createDrone(
|
|
225
128
|
principal: ConformancePrincipal,
|
|
226
129
|
cube: ConformanceCube,
|
|
@@ -228,23 +131,9 @@ export interface ConformanceAdmin {
|
|
|
228
131
|
): Promise<ConformanceDrone>;
|
|
229
132
|
issueManagedDroneSession(drone: ConformanceDrone): Promise<string>;
|
|
230
133
|
revokeManagedDroneSession(drone: ConformanceDrone): Promise<void>;
|
|
231
|
-
inspectManagedDrone(drone: ConformanceDrone): Promise<{
|
|
232
|
-
readonly role_id: string;
|
|
233
|
-
readonly evicted: boolean;
|
|
234
|
-
readonly session_revoked: boolean;
|
|
235
|
-
}>;
|
|
236
|
-
inspectDroneRuntimeState(drone: ConformanceDrone): Promise<ConformanceDroneRuntimeState>;
|
|
237
|
-
/** Observes every cube field mutated by a represented manage-scoped operation. */
|
|
238
|
-
inspectCubeManagementState(cube: ConformanceCube): Promise<ConformanceCubeManagementState>;
|
|
239
134
|
grantCreateCubeCapability(principal: ConformancePrincipal): Promise<void>;
|
|
240
135
|
issueDroneSession(principal: ConformancePrincipal): Promise<string>;
|
|
241
136
|
issueSingleUseInvitation(principal: ConformancePrincipal, purpose: 'owner' | 'client'): Promise<string>;
|
|
242
|
-
observeAuthorityState(): Promise<ConformanceAuthorityState>;
|
|
243
|
-
inspectCreatedCube(
|
|
244
|
-
creator: ConformancePrincipal,
|
|
245
|
-
response: CreateCubeResponse,
|
|
246
|
-
): Promise<ConformanceCreatedCubeState>;
|
|
247
|
-
inspectDeletedCube(cube: ConformanceCube): Promise<ConformanceDeletedCubeState>;
|
|
248
137
|
prepareRepositoryCube(
|
|
249
138
|
cube: ConformanceCube,
|
|
250
139
|
input: {
|
|
@@ -252,10 +141,6 @@ export interface ConformanceAdmin {
|
|
|
252
141
|
template: CubeTemplate;
|
|
253
142
|
},
|
|
254
143
|
): Promise<ConformanceRepositoryCubeFixture>;
|
|
255
|
-
inspectEnrollmentPrincipal(
|
|
256
|
-
principal: ConformancePrincipal,
|
|
257
|
-
responseClientId: string,
|
|
258
|
-
): Promise<ConformanceEnrollmentPrincipalState>;
|
|
259
144
|
revokePrincipal(principal: ConformancePrincipal): Promise<void>;
|
|
260
145
|
expireCursor(cube: ConformanceCube, cursor: LogCursor): Promise<void>;
|
|
261
146
|
armReplayTransition(): ConformanceReplayBarrier;
|
|
@@ -420,9 +305,39 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
420
305
|
export type AdapterConformanceFixtureId =
|
|
421
306
|
(typeof ADAPTER_CONFORMANCE_FIXTURES)[number]['id'];
|
|
422
307
|
|
|
308
|
+
const FIXTURE_PREREQUISITES: Partial<Record<AdapterConformanceFixtureId, readonly string[]>> = {
|
|
309
|
+
'repository.explicit-association': ['shared.enrolled-principals'],
|
|
310
|
+
'documents.lifecycle': ['shared.enrolled-principals'],
|
|
311
|
+
'security.adapter-boundary-injection': ['shared.enrolled-principals'],
|
|
312
|
+
'security.oversize-request': ['shared.enrolled-principals'],
|
|
313
|
+
'security.cross-cube-isolation': ['shared.enrolled-principals'],
|
|
314
|
+
'log.mandatory-addressing': ['shared.enrolled-principals'],
|
|
315
|
+
'log.entry-query': ['shared.enrolled-principals'],
|
|
316
|
+
'log.read-cursor-tuple': ['shared.enrolled-principals'],
|
|
317
|
+
'sse.replay-live-transition': ['shared.enrolled-principals'],
|
|
318
|
+
'cursor.explicit-expiry': ['shared.enrolled-principals'],
|
|
319
|
+
'acks.idempotent': ['shared.enrolled-principals'],
|
|
320
|
+
'acks.status-query': ['shared.enrolled-principals'],
|
|
321
|
+
'claims.durable-noncursor': ['shared.enrolled-principals'],
|
|
322
|
+
'decisions.topic-supersession': ['shared.enrolled-principals'],
|
|
323
|
+
'security.manage-access-matrix': ['shared.enrolled-principals'],
|
|
324
|
+
'drones.reassign-invariants': ['shared.enrolled-principals'],
|
|
325
|
+
'security.cross-cube-drone-management': ['shared.enrolled-principals'],
|
|
326
|
+
'drones.evict-terminal-signal': ['shared.enrolled-principals'],
|
|
327
|
+
'metadata.attach-report': ['shared.enrolled-principals'],
|
|
328
|
+
'metadata.self-heal-patch': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
329
|
+
'security.metadata-invalid-atomic': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
330
|
+
'security.metadata-own-seat': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
331
|
+
'security.metadata-cross-cube-isolation': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
332
|
+
'security.metadata-noninterference': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
333
|
+
'security.metadata-secret-non-echo': ['shared.enrolled-principals', 'shared.metadata-baseline'],
|
|
334
|
+
};
|
|
335
|
+
|
|
423
336
|
export interface AdapterConformanceResult {
|
|
424
337
|
id: AdapterConformanceFixtureId;
|
|
425
338
|
ok: boolean;
|
|
339
|
+
/** True when the fixture was not exercised because a named prerequisite failed. */
|
|
340
|
+
skipped?: boolean;
|
|
426
341
|
observations: Record<string, unknown>;
|
|
427
342
|
error?: string;
|
|
428
343
|
}
|
|
@@ -604,18 +519,29 @@ function listedDroneIds(response: ConformanceHttpResponse): string[] {
|
|
|
604
519
|
}).payload;
|
|
605
520
|
}
|
|
606
521
|
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
after[key] - before[key] === (expected[key] ?? 0),
|
|
616
|
-
`${description} changed ${key} by ${after[key] - before[key]}; expected ${expected[key] ?? 0}.`,
|
|
522
|
+
function listedDrone(response: ConformanceHttpResponse, droneId: string): Record<string, unknown> | null {
|
|
523
|
+
return decodeProtocolEnvelope(response.body, (payload) => {
|
|
524
|
+
invariant(typeof payload === 'object' && payload !== null, 'Roster payload is not an object.');
|
|
525
|
+
const drones = (payload as { drones?: unknown }).drones;
|
|
526
|
+
invariant(Array.isArray(drones), 'Roster payload omitted drones.');
|
|
527
|
+
const drone = drones.find((candidate) =>
|
|
528
|
+
typeof candidate === 'object' && candidate !== null &&
|
|
529
|
+
(candidate as { id?: unknown }).id === droneId
|
|
617
530
|
);
|
|
618
|
-
|
|
531
|
+
return drone ? { ...drone as Record<string, unknown> } : null;
|
|
532
|
+
}).payload;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function withoutRuntimeMetadata(drone: Record<string, unknown>): Record<string, unknown> {
|
|
536
|
+
const {
|
|
537
|
+
agent_kind: _agentKind,
|
|
538
|
+
reported_model: _reportedModel,
|
|
539
|
+
working_repo_name: _workingRepoName,
|
|
540
|
+
working_repo_origin: _workingRepoOrigin,
|
|
541
|
+
runtime_metadata_reported: _runtimeMetadataReported,
|
|
542
|
+
...rest
|
|
543
|
+
} = drone;
|
|
544
|
+
return rest;
|
|
619
545
|
}
|
|
620
546
|
|
|
621
547
|
function assertEnrollmentErrorIsSecretFree(
|
|
@@ -657,18 +583,33 @@ export async function runAdapterConformance(
|
|
|
657
583
|
invariant(streamDeadlineMs > 0, 'streamDeadlineMs must be positive.');
|
|
658
584
|
invariant(pendingProbeMs > 0, 'pendingProbeMs must be positive.');
|
|
659
585
|
const results: AdapterConformanceResult[] = [];
|
|
586
|
+
const prerequisiteFailures = new Map<string, string>();
|
|
660
587
|
const record = async (
|
|
661
588
|
id: AdapterConformanceFixtureId,
|
|
662
589
|
execute: () => Promise<Record<string, unknown>>,
|
|
590
|
+
prerequisites: readonly string[] = [],
|
|
663
591
|
): Promise<void> => {
|
|
592
|
+
const required = [...(FIXTURE_PREREQUISITES[id] ?? []), ...prerequisites];
|
|
593
|
+
const failedPrerequisite = required.find((name) => prerequisiteFailures.has(name));
|
|
594
|
+
if (failedPrerequisite) {
|
|
595
|
+
results.push({
|
|
596
|
+
id,
|
|
597
|
+
ok: false,
|
|
598
|
+
skipped: true,
|
|
599
|
+
observations: {},
|
|
600
|
+
error: `Skipped because prerequisite ${failedPrerequisite} failed: ${prerequisiteFailures.get(failedPrerequisite)}`,
|
|
601
|
+
});
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
664
604
|
try {
|
|
665
605
|
results.push({ id, ok: true, observations: await execute() });
|
|
666
606
|
} catch (error) {
|
|
607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
667
608
|
results.push({
|
|
668
609
|
id,
|
|
669
610
|
ok: false,
|
|
670
611
|
observations: {},
|
|
671
|
-
error:
|
|
612
|
+
error: message,
|
|
672
613
|
});
|
|
673
614
|
}
|
|
674
615
|
};
|
|
@@ -689,10 +630,8 @@ export async function runAdapterConformance(
|
|
|
689
630
|
});
|
|
690
631
|
|
|
691
632
|
await record('protocol.credential-free-preflight', async () => {
|
|
692
|
-
// The tag preflight is credential-free
|
|
693
|
-
//
|
|
694
|
-
// credential. The server must answer 200 with ONLY the exact tag.
|
|
695
|
-
const before = await environment.admin.observeAuthorityState();
|
|
633
|
+
// The tag preflight is credential-free: a client verifies pinned TLS and the
|
|
634
|
+
// exact tag before it creates or sends any credential.
|
|
696
635
|
const response = await environment.operations.protocol(null);
|
|
697
636
|
expectStatus(response, 200, 'Credential-free protocol-tag preflight');
|
|
698
637
|
const preflight = decodeProtocolTagPreflight(response.body);
|
|
@@ -700,8 +639,7 @@ export async function runAdapterConformance(
|
|
|
700
639
|
Object.keys(preflight).length === 1 && preflight.protocol_version === PROTOCOL_VERSION,
|
|
701
640
|
'Protocol-tag preflight exposed more than the exact tag.',
|
|
702
641
|
);
|
|
703
|
-
|
|
704
|
-
return { authenticated: false, mutation_free: true, protocol_version: preflight.protocol_version };
|
|
642
|
+
return { authenticated: false, protocol_version: preflight.protocol_version };
|
|
705
643
|
});
|
|
706
644
|
|
|
707
645
|
await record('enrollment.invitation-artifact', async () => {
|
|
@@ -738,7 +676,6 @@ export async function runAdapterConformance(
|
|
|
738
676
|
const invitation = await environment.admin.issueSingleUseInvitation(principal, purpose);
|
|
739
677
|
const initialPayload = { ...vector.initial, invitation };
|
|
740
678
|
const retryPayload = { ...vector.retry, invitation };
|
|
741
|
-
const beforeInitial = await environment.admin.observeAuthorityState();
|
|
742
679
|
const initialResponse = await environment.operations.enroll(
|
|
743
680
|
createProtocolEnvelope(`retry-${index}-initial`, initialPayload),
|
|
744
681
|
);
|
|
@@ -749,19 +686,6 @@ export async function runAdapterConformance(
|
|
|
749
686
|
same(initial.server_capabilities, purpose === 'owner' ? ['create_cube'] : []),
|
|
750
687
|
`${purpose} ${vector.name} returned incorrect server authority.`,
|
|
751
688
|
);
|
|
752
|
-
const afterInitial = await environment.admin.observeAuthorityState();
|
|
753
|
-
assertStateDelta(
|
|
754
|
-
beforeInitial,
|
|
755
|
-
afterInitial,
|
|
756
|
-
{
|
|
757
|
-
enrolled_clients: 1,
|
|
758
|
-
enrollment_claims: 1,
|
|
759
|
-
server_capabilities: purpose === 'owner' ? 1 : 0,
|
|
760
|
-
},
|
|
761
|
-
`${purpose} ${vector.name} initial request`,
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
765
689
|
const retryResponse = await environment.operations.enroll(
|
|
766
690
|
createProtocolEnvelope(`retry-${index}-retry`, retryPayload),
|
|
767
691
|
);
|
|
@@ -780,15 +704,40 @@ export async function runAdapterConformance(
|
|
|
780
704
|
`${purpose} ${vector.name}`,
|
|
781
705
|
);
|
|
782
706
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
707
|
+
const credentialProbeCube = await environment.admin.createCube(`${purpose}-credential-probe-${index}`);
|
|
708
|
+
await environment.admin.grantCube(principal, credentialProbeCube, 'read');
|
|
709
|
+
expectStatus(
|
|
710
|
+
await environment.operations.read(
|
|
711
|
+
initialPayload.client_credential,
|
|
712
|
+
credentialProbeCube,
|
|
713
|
+
createProtocolEnvelope(`retry-${index}-credential-probe`, { cursor: null, limit: 1 }),
|
|
714
|
+
),
|
|
715
|
+
200,
|
|
716
|
+
`${purpose} ${vector.name} original credential probe`,
|
|
717
|
+
);
|
|
718
|
+
const capabilityProbe = await environment.operations.createCube(
|
|
719
|
+
initialPayload.client_credential,
|
|
720
|
+
createProtocolEnvelope(`retry-${index}-${purpose}-capability-probe`, {
|
|
721
|
+
retry_key: `10000000-0000-4000-8000-${String(index + (purpose === 'owner' ? 700 : 600)).padStart(12, '0')}`,
|
|
722
|
+
name: `${purpose} capability probe ${index}`,
|
|
723
|
+
working_repo_name: `${purpose}-capability-probe-${index}`,
|
|
724
|
+
repository: {
|
|
725
|
+
kind: 'local',
|
|
726
|
+
value: `20000000-0000-4000-8000-${String(index + (purpose === 'owner' ? 700 : 600)).padStart(12, '0')}`,
|
|
727
|
+
},
|
|
728
|
+
template: 'software-dev',
|
|
789
729
|
}),
|
|
790
|
-
`${purpose} ${vector.name} changed enrollment binding ownership.`,
|
|
791
730
|
);
|
|
731
|
+
if (purpose === 'owner') {
|
|
732
|
+
expectStatus(capabilityProbe, 201, `${purpose} ${vector.name} capability probe`);
|
|
733
|
+
} else {
|
|
734
|
+
expectError(
|
|
735
|
+
capabilityProbe,
|
|
736
|
+
403,
|
|
737
|
+
ErrorCode.ACCESS_DENIED,
|
|
738
|
+
`${purpose} ${vector.name} capability probe`,
|
|
739
|
+
);
|
|
740
|
+
}
|
|
792
741
|
} catch (error) {
|
|
793
742
|
retryVectorErrors.push(
|
|
794
743
|
`${purpose} ${vector.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -805,7 +754,6 @@ export async function runAdapterConformance(
|
|
|
805
754
|
const ordinaryInvitation = await environment.admin.issueSingleUseInvitation(ordinaryPrincipal, 'client');
|
|
806
755
|
const ownerCredential = `${'Q'.repeat(42)}U`;
|
|
807
756
|
const ordinaryCredential = `${'Y'.repeat(42)}U`;
|
|
808
|
-
const beforeAuthorityEnrollment = await environment.admin.observeAuthorityState();
|
|
809
757
|
const ownerResponse = await environment.operations.enroll(createProtocolEnvelope('owner-enroll', {
|
|
810
758
|
invitation: ownerInvitation,
|
|
811
759
|
retry_key: '00000000-0000-4000-8000-000000000211',
|
|
@@ -824,13 +772,6 @@ export async function runAdapterConformance(
|
|
|
824
772
|
const ordinary = decodeEnrollmentExchangeResponseEnvelope(ordinaryResponse.body).payload;
|
|
825
773
|
invariant(owner.purpose === 'owner' && same(owner.server_capabilities, ['create_cube']), 'Owner enrollment lacked exact create-cube authority.');
|
|
826
774
|
invariant(ordinary.purpose === 'client' && ordinary.server_capabilities.length === 0, 'Ordinary enrollment gained authority.');
|
|
827
|
-
assertStateDelta(
|
|
828
|
-
beforeAuthorityEnrollment,
|
|
829
|
-
await environment.admin.observeAuthorityState(),
|
|
830
|
-
{ enrolled_clients: 2, enrollment_claims: 2, server_capabilities: 1 },
|
|
831
|
-
'Owner and ordinary enrollment',
|
|
832
|
-
);
|
|
833
|
-
|
|
834
775
|
const cubeRequest = {
|
|
835
776
|
retry_key: '00000000-0000-4000-8000-000000000213',
|
|
836
777
|
name: 'Repository One',
|
|
@@ -839,10 +780,9 @@ export async function runAdapterConformance(
|
|
|
839
780
|
kind: 'origin' as const,
|
|
840
781
|
value: 'https://github.com/Byte-Ventures/repository-one',
|
|
841
782
|
},
|
|
842
|
-
template: '
|
|
783
|
+
template: 'software-dev',
|
|
843
784
|
};
|
|
844
785
|
const droneCredential = await environment.admin.issueDroneSession(ownerPrincipal);
|
|
845
|
-
const beforeDeniedCreate = await environment.admin.observeAuthorityState();
|
|
846
786
|
expectSecretFreeError(
|
|
847
787
|
await environment.operations.createCube(null, createProtocolEnvelope('cube-missing-auth', cubeRequest)),
|
|
848
788
|
401,
|
|
@@ -871,36 +811,42 @@ export async function runAdapterConformance(
|
|
|
871
811
|
'Drone-session cube create',
|
|
872
812
|
[cubeRequest.retry_key],
|
|
873
813
|
);
|
|
874
|
-
assertStateDelta(beforeDeniedCreate, await environment.admin.observeAuthorityState(), {}, 'Denied ordinary cube create');
|
|
875
|
-
const beforeCreate = await environment.admin.observeAuthorityState();
|
|
876
814
|
const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
|
|
877
815
|
expectStatus(createdResponse, 201, 'Owner cube create');
|
|
878
816
|
const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
817
|
+
const createdRoleProbes = new Map<string, {
|
|
818
|
+
role: unknown;
|
|
819
|
+
droneId: string;
|
|
820
|
+
sessionCredential: string;
|
|
821
|
+
}>();
|
|
822
|
+
for (const [kind, roleId, roleClass, isHumanSeat, marker] of [
|
|
823
|
+
['human-seat', created.human_seat_role_id, 'queen', true, 'H'],
|
|
824
|
+
['default-worker', created.default_worker_role_id, 'worker', false, 'D'],
|
|
825
|
+
] as const) {
|
|
826
|
+
const sessionCredential = marker.repeat(43);
|
|
827
|
+
const attached = await environment.operations.attach(
|
|
828
|
+
ownerCredential,
|
|
829
|
+
createProtocolEnvelope(`cube-create-${kind}-probe`, {
|
|
830
|
+
cube_id: created.cube_id,
|
|
831
|
+
role_id: roleId,
|
|
832
|
+
session_credential: sessionCredential,
|
|
833
|
+
}),
|
|
834
|
+
);
|
|
835
|
+
expectStatus(attached, 200, `Created ${kind} role probe`);
|
|
836
|
+
const attachedPayload = decodeAttachResponseEnvelope(attached.body).payload;
|
|
837
|
+
const attachedRole = attachedPayload.role;
|
|
838
|
+
invariant(
|
|
839
|
+
attachedRole.id === roleId &&
|
|
840
|
+
attachedRole.role_class === roleClass &&
|
|
841
|
+
attachedRole.is_human_seat === isHumanSeat,
|
|
842
|
+
`Created ${kind} role identity did not match its protocol role.`,
|
|
843
|
+
);
|
|
844
|
+
createdRoleProbes.set(kind, {
|
|
845
|
+
role: attachedRole,
|
|
846
|
+
droneId: attachedPayload.drone.id,
|
|
847
|
+
sessionCredential,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
904
850
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
905
851
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
906
852
|
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
@@ -909,13 +855,11 @@ export async function runAdapterConformance(
|
|
|
909
855
|
same({ ...retriedCreate, result: 'created' }, created),
|
|
910
856
|
'Exact cube-create retry returned different authoritative fields.',
|
|
911
857
|
);
|
|
912
|
-
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
913
858
|
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find(
|
|
914
859
|
(vector) => vector.expected.outcome === 'resolved_response' &&
|
|
915
860
|
vector.retry.working_repo_name !== vector.initial.working_repo_name,
|
|
916
861
|
);
|
|
917
862
|
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
918
|
-
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
919
863
|
const displayRetryResponse = await environment.operations.createCube(
|
|
920
864
|
ownerCredential,
|
|
921
865
|
createProtocolEnvelope('cube-display-retry', {
|
|
@@ -928,16 +872,9 @@ export async function runAdapterConformance(
|
|
|
928
872
|
same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate),
|
|
929
873
|
'Changed repository display retry did not return stored authoritative display.',
|
|
930
874
|
);
|
|
931
|
-
assertStateDelta(
|
|
932
|
-
beforeDisplayRetry,
|
|
933
|
-
await environment.admin.observeAuthorityState(),
|
|
934
|
-
{},
|
|
935
|
-
'Changed repository display retry',
|
|
936
|
-
);
|
|
937
875
|
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
938
876
|
if (vector.expected.outcome !== 'retry_tuple_mismatch') continue;
|
|
939
877
|
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
940
|
-
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
941
878
|
expectSecretFreeError(
|
|
942
879
|
await environment.operations.createCube(
|
|
943
880
|
ownerCredential,
|
|
@@ -948,18 +885,32 @@ export async function runAdapterConformance(
|
|
|
948
885
|
`Cube-create ${vector.name}`,
|
|
949
886
|
[cubeRequest.retry_key],
|
|
950
887
|
);
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
888
|
+
}
|
|
889
|
+
for (const [kind, roleId] of [
|
|
890
|
+
['human-seat', created.human_seat_role_id],
|
|
891
|
+
['default-worker', created.default_worker_role_id],
|
|
892
|
+
] as const) {
|
|
893
|
+
const initialProbe = createdRoleProbes.get(kind);
|
|
894
|
+
invariant(initialProbe !== undefined, `Missing initial ${kind} role probe.`);
|
|
895
|
+
const attached = await environment.operations.attach(
|
|
896
|
+
ownerCredential,
|
|
897
|
+
createProtocolEnvelope(`cube-create-${kind}-post-refusal-probe`, {
|
|
898
|
+
cube_id: created.cube_id,
|
|
899
|
+
role_id: roleId,
|
|
900
|
+
session_credential: initialProbe.sessionCredential,
|
|
901
|
+
prior_drone_id: initialProbe.droneId,
|
|
902
|
+
}),
|
|
903
|
+
);
|
|
904
|
+
expectStatus(attached, 200, `Post-refusal ${kind} role probe`);
|
|
905
|
+
invariant(
|
|
906
|
+
same(decodeAttachResponseEnvelope(attached.body).payload.role, initialProbe.role),
|
|
907
|
+
`Cube-create retry refusal changed the full ${kind} role shape.`,
|
|
956
908
|
);
|
|
957
909
|
}
|
|
958
910
|
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
959
911
|
(vector) => vector.expected.outcome === 'resolved',
|
|
960
912
|
);
|
|
961
913
|
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
962
|
-
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
963
914
|
const associationResolveResponse = await environment.operations.createCube(
|
|
964
915
|
ownerCredential,
|
|
965
916
|
createProtocolEnvelope('cube-association-resolve', {
|
|
@@ -974,12 +925,6 @@ export async function runAdapterConformance(
|
|
|
974
925
|
same({ ...associationResolved, result: 'created' }, created),
|
|
975
926
|
'Associated repository did not return stored authoritative fields.',
|
|
976
927
|
);
|
|
977
|
-
assertStateDelta(
|
|
978
|
-
beforeAssociationResolve,
|
|
979
|
-
await environment.admin.observeAuthorityState(),
|
|
980
|
-
sameAssociationVector.expected.authority_state_delta,
|
|
981
|
-
'Fresh retry for associated repository',
|
|
982
|
-
);
|
|
983
928
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
984
929
|
const crossClientRequest = {
|
|
985
930
|
...cubeRequest,
|
|
@@ -989,7 +934,6 @@ export async function runAdapterConformance(
|
|
|
989
934
|
value: '00000000-0000-4000-8000-000000000216',
|
|
990
935
|
},
|
|
991
936
|
};
|
|
992
|
-
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
993
937
|
const crossClientResponse = await environment.operations.createCube(
|
|
994
938
|
ordinaryCredential,
|
|
995
939
|
createProtocolEnvelope('cube-cross-client', crossClientRequest),
|
|
@@ -997,17 +941,27 @@ export async function runAdapterConformance(
|
|
|
997
941
|
expectStatus(crossClientResponse, 201, 'Cross-client cube create with reused retry key');
|
|
998
942
|
const crossClientCreated = decodeCreateCubeResponseEnvelope(crossClientResponse.body).payload;
|
|
999
943
|
invariant(crossClientCreated.cube_id !== created.cube_id, 'Cross-client retry key reused another client\'s cube.');
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
944
|
+
for (const [kind, roleId, originalRoleId, marker] of [
|
|
945
|
+
['human-seat', crossClientCreated.human_seat_role_id, created.human_seat_role_id, 'V'],
|
|
946
|
+
['default-worker', crossClientCreated.default_worker_role_id, created.default_worker_role_id, 'W'],
|
|
947
|
+
] as const) {
|
|
948
|
+
const originalProbe = createdRoleProbes.get(kind);
|
|
949
|
+
invariant(originalProbe !== undefined, `Missing original ${kind} role probe.`);
|
|
950
|
+
const attached = await environment.operations.attach(
|
|
951
|
+
ordinaryCredential,
|
|
952
|
+
createProtocolEnvelope(`cube-cross-client-${kind}-probe`, {
|
|
953
|
+
cube_id: crossClientCreated.cube_id,
|
|
954
|
+
role_id: roleId,
|
|
955
|
+
session_credential: marker.repeat(43),
|
|
956
|
+
}),
|
|
957
|
+
);
|
|
958
|
+
expectStatus(attached, 200, `Cross-client ${kind} role probe`);
|
|
959
|
+
const attachedRole = decodeAttachResponseEnvelope(attached.body).payload.role;
|
|
960
|
+
invariant(
|
|
961
|
+
same({ ...attachedRole, id: originalRoleId }, originalProbe.role),
|
|
962
|
+
`Cross-client ${kind} role did not match the full template role shape.`,
|
|
963
|
+
);
|
|
964
|
+
}
|
|
1011
965
|
const crossClientRetry = await environment.operations.createCube(
|
|
1012
966
|
ordinaryCredential,
|
|
1013
967
|
createProtocolEnvelope('cube-cross-client-retry', crossClientRequest),
|
|
@@ -1025,12 +979,10 @@ export async function runAdapterConformance(
|
|
|
1025
979
|
),
|
|
1026
980
|
'Exact cross-client cube-create retry returned different authoritative fields.',
|
|
1027
981
|
);
|
|
1028
|
-
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
1029
982
|
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
1030
983
|
(vector) => vector.expected.outcome === 'created',
|
|
1031
984
|
);
|
|
1032
985
|
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
1033
|
-
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
1034
986
|
const secondCreatedResponse = await environment.operations.createCube(
|
|
1035
987
|
ownerCredential,
|
|
1036
988
|
createProtocolEnvelope('cube-create-second', differentAssociationVector.request),
|
|
@@ -1038,14 +990,7 @@ export async function runAdapterConformance(
|
|
|
1038
990
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
1039
991
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
1040
992
|
invariant(secondCreated.cube_id !== created.cube_id, 'Fresh cube-create retry key reused an existing cube.');
|
|
1041
|
-
assertStateDelta(
|
|
1042
|
-
beforeSecondCreate,
|
|
1043
|
-
await environment.admin.observeAuthorityState(),
|
|
1044
|
-
differentAssociationVector.expected.authority_state_delta,
|
|
1045
|
-
'Second cube create',
|
|
1046
|
-
);
|
|
1047
993
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
|
1048
|
-
const beforeRevokedCreate = await environment.admin.observeAuthorityState();
|
|
1049
994
|
expectSecretFreeError(
|
|
1050
995
|
await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-revoked', {
|
|
1051
996
|
...cubeRequest,
|
|
@@ -1056,8 +1001,16 @@ export async function runAdapterConformance(
|
|
|
1056
1001
|
'Revoked owner cube create',
|
|
1057
1002
|
['00000000-0000-4000-8000-000000000215'],
|
|
1058
1003
|
);
|
|
1059
|
-
assertStateDelta(beforeRevokedCreate, await environment.admin.observeAuthorityState(), {}, 'Revoked owner cube create');
|
|
1060
1004
|
|
|
1005
|
+
return {
|
|
1006
|
+
enrollment_status: 201,
|
|
1007
|
+
exact_retry_status: 201,
|
|
1008
|
+
mismatched_retry: ErrorCode.AUTH_INVALID,
|
|
1009
|
+
response_secret_free: true,
|
|
1010
|
+
};
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1061
1014
|
await environment.admin.reset();
|
|
1062
1015
|
principalA = await environment.admin.createPrincipal('principal-a');
|
|
1063
1016
|
principalB = await environment.admin.createPrincipal('principal-b');
|
|
@@ -1069,50 +1022,23 @@ export async function runAdapterConformance(
|
|
|
1069
1022
|
const invitationB = await environment.admin.issueSingleUseInvitation(principalB, 'client');
|
|
1070
1023
|
credentialA = 'A'.repeat(43);
|
|
1071
1024
|
credentialB = 'E'.repeat(43);
|
|
1072
|
-
const
|
|
1025
|
+
const enrolledAResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-a1', {
|
|
1073
1026
|
invitation: invitationA,
|
|
1074
1027
|
retry_key: '00000000-0000-4000-8000-000000000201',
|
|
1075
1028
|
client_credential: credentialA,
|
|
1076
1029
|
client_name: 'conformance-a',
|
|
1077
|
-
});
|
|
1078
|
-
const
|
|
1030
|
+
}));
|
|
1031
|
+
const enrolledBResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-b1', {
|
|
1079
1032
|
invitation: invitationB,
|
|
1080
1033
|
retry_key: '00000000-0000-4000-8000-000000000202',
|
|
1081
1034
|
client_credential: credentialB,
|
|
1082
1035
|
client_name: 'conformance-b',
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
const enrolledB = decodeEnrollmentExchangeResponseEnvelope(enrolledBResponse.body).payload;
|
|
1090
|
-
invariant(enrolledA.purpose === 'client' && enrolledB.purpose === 'client', 'Ordinary enrollment returned owner authority.');
|
|
1091
|
-
invariant(enrolledA.server_capabilities.length === 0 && enrolledB.server_capabilities.length === 0, 'Ordinary enrollment returned a server capability.');
|
|
1092
|
-
invariant(!('credential' in enrolledA) && !('credential' in enrolledB), 'Enrollment response returned a bearer.');
|
|
1093
|
-
const retriedAResponse = await environment.operations.enroll(enrollmentARequest);
|
|
1094
|
-
expectStatus(retriedAResponse, 201, 'Exact enrollment retry');
|
|
1095
|
-
invariant(
|
|
1096
|
-
JSON.stringify(decodeEnrollmentExchangeResponseEnvelope(retriedAResponse.body).payload) ===
|
|
1097
|
-
JSON.stringify(enrolledA),
|
|
1098
|
-
'Exact enrollment retry returned different identities.',
|
|
1099
|
-
);
|
|
1100
|
-
expectError(
|
|
1101
|
-
await environment.operations.enroll(createProtocolEnvelope('enroll-a-mismatch', {
|
|
1102
|
-
...enrollmentARequest.payload,
|
|
1103
|
-
retry_key: '00000000-0000-4000-8000-000000000203',
|
|
1104
|
-
})),
|
|
1105
|
-
401,
|
|
1106
|
-
ErrorCode.AUTH_INVALID,
|
|
1107
|
-
'Enrollment retry mismatch',
|
|
1108
|
-
);
|
|
1109
|
-
return {
|
|
1110
|
-
enrollment_status: 201,
|
|
1111
|
-
exact_retry_status: 201,
|
|
1112
|
-
mismatched_retry: ErrorCode.AUTH_INVALID,
|
|
1113
|
-
response_secret_free: true,
|
|
1114
|
-
};
|
|
1115
|
-
});
|
|
1036
|
+
}));
|
|
1037
|
+
expectStatus(enrolledAResponse, 201, 'Shared principal A enrollment');
|
|
1038
|
+
expectStatus(enrolledBResponse, 201, 'Shared principal B enrollment');
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
prerequisiteFailures.set('shared.enrolled-principals', error instanceof Error ? error.message : String(error));
|
|
1041
|
+
}
|
|
1116
1042
|
|
|
1117
1043
|
await record('repository.explicit-association', async () => {
|
|
1118
1044
|
const repository = {
|
|
@@ -1127,7 +1053,21 @@ export async function runAdapterConformance(
|
|
|
1127
1053
|
working_repo_name: 'legacy-repository',
|
|
1128
1054
|
repository,
|
|
1129
1055
|
};
|
|
1130
|
-
const
|
|
1056
|
+
const alternateResolveRequest = {
|
|
1057
|
+
working_repo_name: 'other-repository',
|
|
1058
|
+
repository: {
|
|
1059
|
+
kind: 'origin' as const,
|
|
1060
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1061
|
+
},
|
|
1062
|
+
};
|
|
1063
|
+
const resolve = async (requestId: string, request: typeof resolveRequest) => {
|
|
1064
|
+
const response = await environment.operations.resolveRepositoryCube(
|
|
1065
|
+
credentialA,
|
|
1066
|
+
createProtocolEnvelope(requestId, request),
|
|
1067
|
+
);
|
|
1068
|
+
expectStatus(response, 200, requestId);
|
|
1069
|
+
return decodeResolveRepositoryCubeResponseEnvelope(response.body).payload;
|
|
1070
|
+
};
|
|
1131
1071
|
const noneResponse = await environment.operations.resolveRepositoryCube(
|
|
1132
1072
|
credentialA,
|
|
1133
1073
|
createProtocolEnvelope('repository-resolve-none', resolveRequest),
|
|
@@ -1137,10 +1077,11 @@ export async function runAdapterConformance(
|
|
|
1137
1077
|
same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }),
|
|
1138
1078
|
'Unassociated repository did not return explicit none.',
|
|
1139
1079
|
);
|
|
1140
|
-
|
|
1141
|
-
|
|
1080
|
+
invariant(
|
|
1081
|
+
same(await resolve('repository-resolve-none-readback', resolveRequest), { result: 'none' }),
|
|
1082
|
+
'Read-only repository resolution created an association.',
|
|
1083
|
+
);
|
|
1142
1084
|
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
1143
|
-
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
1144
1085
|
expectSecretFreeError(
|
|
1145
1086
|
await environment.operations.associateRepositoryCube(
|
|
1146
1087
|
credentialB,
|
|
@@ -1151,9 +1092,6 @@ export async function runAdapterConformance(
|
|
|
1151
1092
|
'Inaccessible repository cube association',
|
|
1152
1093
|
[associationRequest.cube_id, repository.value],
|
|
1153
1094
|
);
|
|
1154
|
-
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
1155
|
-
|
|
1156
|
-
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
1157
1095
|
const associatedResponse = await environment.operations.associateRepositoryCube(
|
|
1158
1096
|
credentialA,
|
|
1159
1097
|
createProtocolEnvelope('repository-associate', associationRequest),
|
|
@@ -1172,14 +1110,6 @@ export async function runAdapterConformance(
|
|
|
1172
1110
|
access: prepared.access,
|
|
1173
1111
|
};
|
|
1174
1112
|
invariant(same(associated, authoritative), 'Repository association did not return authoritative stored fields.');
|
|
1175
|
-
assertStateDelta(
|
|
1176
|
-
beforeAssociation,
|
|
1177
|
-
await environment.admin.observeAuthorityState(),
|
|
1178
|
-
{ repository_associations: 1 },
|
|
1179
|
-
'Explicit repository association',
|
|
1180
|
-
);
|
|
1181
|
-
|
|
1182
|
-
const beforeResolve = await environment.admin.observeAuthorityState();
|
|
1183
1113
|
const resolvedResponse = await environment.operations.resolveRepositoryCube(
|
|
1184
1114
|
credentialA,
|
|
1185
1115
|
createProtocolEnvelope('repository-resolve', {
|
|
@@ -1192,9 +1122,6 @@ export async function runAdapterConformance(
|
|
|
1192
1122
|
same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative),
|
|
1193
1123
|
'Associated repository did not return stored authoritative display.',
|
|
1194
1124
|
);
|
|
1195
|
-
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
1196
|
-
|
|
1197
|
-
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
1198
1125
|
const retryResponse = await environment.operations.associateRepositoryCube(
|
|
1199
1126
|
credentialA,
|
|
1200
1127
|
createProtocolEnvelope('repository-associate-retry', associationRequest),
|
|
@@ -1204,15 +1131,12 @@ export async function runAdapterConformance(
|
|
|
1204
1131
|
same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative),
|
|
1205
1132
|
'Exact repository association retry changed authoritative fields.',
|
|
1206
1133
|
);
|
|
1207
|
-
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
1208
|
-
|
|
1209
1134
|
const cubeC = await environment.admin.createCube('cube-c');
|
|
1210
1135
|
await environment.admin.grantCube(principalA, cubeC);
|
|
1211
1136
|
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
1212
1137
|
name: 'Other Legacy Cube',
|
|
1213
1138
|
template: 'starter',
|
|
1214
1139
|
});
|
|
1215
|
-
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
1216
1140
|
expectSecretFreeError(
|
|
1217
1141
|
await environment.operations.associateRepositoryCube(
|
|
1218
1142
|
credentialA,
|
|
@@ -1226,24 +1150,20 @@ export async function runAdapterConformance(
|
|
|
1226
1150
|
'Repository-to-other-cube conflict',
|
|
1227
1151
|
[cubeA.id, cubeC.id, repository.value],
|
|
1228
1152
|
);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1153
|
+
invariant(
|
|
1154
|
+
same(await resolve('repository-resolve-after-repository-conflict', resolveRequest), authoritative),
|
|
1155
|
+
'Repository conflict changed the authoritative original binding.',
|
|
1156
|
+
);
|
|
1157
|
+
invariant(
|
|
1158
|
+
same(await resolve('repository-resolve-alternate-after-repository-conflict', alternateResolveRequest), { result: 'none' }),
|
|
1159
|
+
'Repository conflict created an alternate binding.',
|
|
1234
1160
|
);
|
|
1235
|
-
|
|
1236
|
-
const beforeCubeConflict = await environment.admin.observeAuthorityState();
|
|
1237
1161
|
expectSecretFreeError(
|
|
1238
1162
|
await environment.operations.associateRepositoryCube(
|
|
1239
1163
|
credentialA,
|
|
1240
1164
|
createProtocolEnvelope('repository-associate-cube-conflict', {
|
|
1241
1165
|
cube_id: cubeA.id,
|
|
1242
|
-
|
|
1243
|
-
repository: {
|
|
1244
|
-
kind: 'origin',
|
|
1245
|
-
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1246
|
-
},
|
|
1166
|
+
...alternateResolveRequest,
|
|
1247
1167
|
}),
|
|
1248
1168
|
),
|
|
1249
1169
|
409,
|
|
@@ -1251,18 +1171,18 @@ export async function runAdapterConformance(
|
|
|
1251
1171
|
'Cube-to-other-repository conflict',
|
|
1252
1172
|
[cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository'],
|
|
1253
1173
|
);
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1174
|
+
invariant(
|
|
1175
|
+
same(await resolve('repository-resolve-after-cube-conflict', resolveRequest), authoritative),
|
|
1176
|
+
'Cube conflict changed the authoritative original binding.',
|
|
1177
|
+
);
|
|
1178
|
+
invariant(
|
|
1179
|
+
same(await resolve('repository-resolve-alternate-after-cube-conflict', alternateResolveRequest), { result: 'none' }),
|
|
1180
|
+
'Cube conflict created the refused alternate binding.',
|
|
1259
1181
|
);
|
|
1260
|
-
|
|
1261
1182
|
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
1262
1183
|
name: 'Other Client Legacy Cube',
|
|
1263
1184
|
template: 'starter',
|
|
1264
1185
|
});
|
|
1265
|
-
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
1266
1186
|
const crossClientResolve = await environment.operations.resolveRepositoryCube(
|
|
1267
1187
|
credentialB,
|
|
1268
1188
|
createProtocolEnvelope('repository-resolve-cross-client', resolveRequest),
|
|
@@ -1272,13 +1192,6 @@ export async function runAdapterConformance(
|
|
|
1272
1192
|
same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }),
|
|
1273
1193
|
'Another client repository binding was exposed by resolution.',
|
|
1274
1194
|
);
|
|
1275
|
-
assertStateDelta(
|
|
1276
|
-
beforeCrossClientResolve,
|
|
1277
|
-
await environment.admin.observeAuthorityState(),
|
|
1278
|
-
{},
|
|
1279
|
-
'Cross-client repository resolution',
|
|
1280
|
-
);
|
|
1281
|
-
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
1282
1195
|
const crossClientAssociation = await environment.operations.associateRepositoryCube(
|
|
1283
1196
|
credentialB,
|
|
1284
1197
|
createProtocolEnvelope('repository-associate-cross-client', {
|
|
@@ -1295,15 +1208,7 @@ export async function runAdapterConformance(
|
|
|
1295
1208
|
crossClientAssociated.repository.value === repository.value,
|
|
1296
1209
|
'Another client binding changed cross-client association identity.',
|
|
1297
1210
|
);
|
|
1298
|
-
assertStateDelta(
|
|
1299
|
-
beforeCrossClientAssociation,
|
|
1300
|
-
await environment.admin.observeAuthorityState(),
|
|
1301
|
-
{ repository_associations: 1 },
|
|
1302
|
-
'Cross-client repository association',
|
|
1303
|
-
);
|
|
1304
|
-
|
|
1305
1211
|
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
1306
|
-
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
1307
1212
|
const hiddenResolve = await environment.operations.resolveRepositoryCube(
|
|
1308
1213
|
credentialA,
|
|
1309
1214
|
createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest),
|
|
@@ -1313,13 +1218,6 @@ export async function runAdapterConformance(
|
|
|
1313
1218
|
same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }),
|
|
1314
1219
|
'Inaccessible repository binding was exposed by read-only resolution.',
|
|
1315
1220
|
);
|
|
1316
|
-
assertStateDelta(
|
|
1317
|
-
beforeHiddenResolve,
|
|
1318
|
-
await environment.admin.observeAuthorityState(),
|
|
1319
|
-
{},
|
|
1320
|
-
'Inaccessible repository binding resolution',
|
|
1321
|
-
);
|
|
1322
|
-
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
1323
1221
|
expectSecretFreeError(
|
|
1324
1222
|
await environment.operations.associateRepositoryCube(
|
|
1325
1223
|
credentialA,
|
|
@@ -1333,13 +1231,24 @@ export async function runAdapterConformance(
|
|
|
1333
1231
|
'Inaccessible existing repository binding',
|
|
1334
1232
|
[cubeA.id, cubeC.id, repository.value],
|
|
1335
1233
|
);
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
'Inaccessible existing repository binding',
|
|
1234
|
+
const hiddenAfterRefusal = await resolve('repository-resolve-after-inaccessible-refusal', resolveRequest);
|
|
1235
|
+
const alternateAfterRefusal = await resolve(
|
|
1236
|
+
'repository-resolve-alternate-after-inaccessible-refusal',
|
|
1237
|
+
alternateResolveRequest,
|
|
1341
1238
|
);
|
|
1342
1239
|
await environment.admin.grantCube(principalA, cubeA);
|
|
1240
|
+
invariant(
|
|
1241
|
+
same(hiddenAfterRefusal, { result: 'none' }),
|
|
1242
|
+
'Inaccessible-binding refusal changed the hidden authoritative binding.',
|
|
1243
|
+
);
|
|
1244
|
+
invariant(
|
|
1245
|
+
same(alternateAfterRefusal, { result: 'none' }),
|
|
1246
|
+
'Inaccessible-binding refusal created an alternate binding.',
|
|
1247
|
+
);
|
|
1248
|
+
invariant(
|
|
1249
|
+
same(await resolve('repository-resolve-restored-after-inaccessible-refusal', resolveRequest), authoritative),
|
|
1250
|
+
'Inaccessible-binding refusal did not preserve the original binding.',
|
|
1251
|
+
);
|
|
1343
1252
|
return {
|
|
1344
1253
|
none: true,
|
|
1345
1254
|
resolved: true,
|
|
@@ -1887,7 +1796,6 @@ export async function runAdapterConformance(
|
|
|
1887
1796
|
});
|
|
1888
1797
|
const recipient = await environment.admin.createDrone(principalA, cube, role);
|
|
1889
1798
|
const message = 'START NOW exact slice';
|
|
1890
|
-
const before = await environment.admin.observeAuthorityState();
|
|
1891
1799
|
expectError(
|
|
1892
1800
|
await environment.operations.append(
|
|
1893
1801
|
credentialA,
|
|
@@ -1901,8 +1809,6 @@ export async function runAdapterConformance(
|
|
|
1901
1809
|
ErrorCode.INVALID_INPUT,
|
|
1902
1810
|
'Omitted mandatory addressing',
|
|
1903
1811
|
);
|
|
1904
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Mandatory-addressing refusal');
|
|
1905
|
-
|
|
1906
1812
|
const broadcast = await environment.operations.append(
|
|
1907
1813
|
credentialA,
|
|
1908
1814
|
cube,
|
|
@@ -1940,6 +1846,17 @@ export async function runAdapterConformance(
|
|
|
1940
1846
|
directedResult.entry.recipient_drone_ids?.length === 1,
|
|
1941
1847
|
'Classification changed or fell open the explicit recipient set.',
|
|
1942
1848
|
);
|
|
1849
|
+
const readback = await environment.operations.read(
|
|
1850
|
+
credentialA,
|
|
1851
|
+
cube,
|
|
1852
|
+
createProtocolEnvelope('mandatory-addressing-readback', { cursor: null, limit: 10 }),
|
|
1853
|
+
);
|
|
1854
|
+
expectStatus(readback, 200, 'Mandatory-addressing persistence readback');
|
|
1855
|
+
const persistedIds = decodeReadLogResultEnvelope(readback.body).payload.entries.map((entry) => entry.id);
|
|
1856
|
+
invariant(
|
|
1857
|
+
same(persistedIds, [broadcastResult.entry.id, directedResult.entry.id]),
|
|
1858
|
+
'Rejected mandatory-addressing append was persisted.',
|
|
1859
|
+
);
|
|
1943
1860
|
return { refused_status: 400, code: ErrorCode.INVALID_INPUT, persisted: false, broadcast: true, union: true, no_fall_open: true };
|
|
1944
1861
|
});
|
|
1945
1862
|
|
|
@@ -1961,6 +1878,7 @@ export async function runAdapterConformance(
|
|
|
1961
1878
|
'22222222-0000-4000-8000-000000000002',
|
|
1962
1879
|
];
|
|
1963
1880
|
const entries: EnrichedStreamEntry[] = [];
|
|
1881
|
+
const idSeeds: Array<{ current_id: string; query_id: string }> = [];
|
|
1964
1882
|
for (const [index, id] of ids.entries()) {
|
|
1965
1883
|
const response = await environment.operations.append(
|
|
1966
1884
|
credentialA,
|
|
@@ -1973,9 +1891,10 @@ export async function runAdapterConformance(
|
|
|
1973
1891
|
);
|
|
1974
1892
|
expectStatus(response, 201, `Entry-query fixture append ${index}`);
|
|
1975
1893
|
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
1976
|
-
|
|
1894
|
+
idSeeds.push({ current_id: entry.id, query_id: id });
|
|
1977
1895
|
entries.push({ ...entry, id });
|
|
1978
1896
|
}
|
|
1897
|
+
await environment.admin.seedEntryQueryIds(cube, idSeeds);
|
|
1979
1898
|
|
|
1980
1899
|
expectStatus(
|
|
1981
1900
|
await environment.operations.ack(
|
|
@@ -1996,7 +1915,6 @@ export async function runAdapterConformance(
|
|
|
1996
1915
|
'Entry-query claim seed',
|
|
1997
1916
|
);
|
|
1998
1917
|
|
|
1999
|
-
const before = await environment.admin.observeAuthorityState();
|
|
2000
1918
|
for (const [requestId, selector, expectedId] of [
|
|
2001
1919
|
['entry-query-full', ids[1], ids[1]],
|
|
2002
1920
|
['entry-query-prefix', '11111111', ids[0]],
|
|
@@ -2030,7 +1948,6 @@ export async function runAdapterConformance(
|
|
|
2030
1948
|
ErrorCode.LOG_ENTRY_PREFIX_AMBIGUOUS,
|
|
2031
1949
|
'Ambiguous entry query',
|
|
2032
1950
|
);
|
|
2033
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Entry query');
|
|
2034
1951
|
const acknowledgementStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(
|
|
2035
1952
|
credentialA,
|
|
2036
1953
|
cube,
|
|
@@ -2041,15 +1958,22 @@ export async function runAdapterConformance(
|
|
|
2041
1958
|
cube,
|
|
2042
1959
|
createProtocolEnvelope('entry-query-claim-status', { entry_id: ids[1] }),
|
|
2043
1960
|
)).body).payload;
|
|
1961
|
+
const acknowledgedRecipients = acknowledgementStatus.recipients.filter(
|
|
1962
|
+
(recipient) => recipient.acknowledged_at !== null,
|
|
1963
|
+
);
|
|
2044
1964
|
invariant(
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
1965
|
+
acknowledgedRecipients.length === 1 &&
|
|
1966
|
+
acknowledgedRecipients[0].drone_id === acknowledgedRecipient.id &&
|
|
1967
|
+
acknowledgementStatus.recipients.find(
|
|
1968
|
+
(recipient) => recipient.drone_id === claimingRecipient.id,
|
|
1969
|
+
)?.acknowledged_at === null,
|
|
1970
|
+
'Entry query changed the seeded acknowledgement set.',
|
|
2049
1971
|
);
|
|
2050
1972
|
invariant(
|
|
2051
|
-
claimStatus.claims.
|
|
2052
|
-
|
|
1973
|
+
claimStatus.claims.length === 1 &&
|
|
1974
|
+
claimStatus.claims[0].drone_id === claimingRecipient.id &&
|
|
1975
|
+
claimStatus.recipients.every((recipient) => recipient.acknowledged_at === null),
|
|
1976
|
+
'Entry query changed the seeded claim or acknowledgement set.',
|
|
2053
1977
|
);
|
|
2054
1978
|
const unreadResponse = await environment.operations.read(
|
|
2055
1979
|
credentialA,
|
|
@@ -2059,8 +1983,8 @@ export async function runAdapterConformance(
|
|
|
2059
1983
|
expectStatus(unreadResponse, 200, 'Entry-query unread read');
|
|
2060
1984
|
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2061
1985
|
invariant(
|
|
2062
|
-
unread.entries
|
|
2063
|
-
'Entry query consumed or advanced unread activity.',
|
|
1986
|
+
same(unread.entries, entries),
|
|
1987
|
+
'Entry query mutated, consumed, or advanced unread activity.',
|
|
2064
1988
|
);
|
|
2065
1989
|
return {
|
|
2066
1990
|
full_uuid: true,
|
|
@@ -2074,9 +1998,8 @@ export async function runAdapterConformance(
|
|
|
2074
1998
|
};
|
|
2075
1999
|
});
|
|
2076
2000
|
|
|
2077
|
-
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
2078
|
-
let readCursor: LogCursor | null = null;
|
|
2079
2001
|
await record('log.read-cursor-tuple', async () => {
|
|
2002
|
+
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
2080
2003
|
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
2081
2004
|
const response = await environment.operations.append(
|
|
2082
2005
|
credentialA,
|
|
@@ -2105,22 +2028,39 @@ export async function runAdapterConformance(
|
|
|
2105
2028
|
page.cursor.id === entries[1].id && page.cursor.created_at === entries[1].created_at,
|
|
2106
2029
|
'Read cursor does not equal the final delivered entry tuple.',
|
|
2107
2030
|
);
|
|
2108
|
-
readCursor = page.cursor;
|
|
2109
2031
|
return { messages: ['alpha', 'beta'], has_more: true, behind_by: 1, cursor_matches_last_entry: true };
|
|
2110
2032
|
});
|
|
2111
|
-
|
|
2112
|
-
let liveCursor: LogCursor | null = null;
|
|
2113
2033
|
await record('sse.replay-live-transition', async () => {
|
|
2114
|
-
|
|
2034
|
+
const principal = await environment.admin.createPrincipal('stream-transition');
|
|
2035
|
+
const cube = await environment.admin.createCube('stream-transition');
|
|
2036
|
+
await environment.admin.grantCube(principal, cube);
|
|
2037
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
2038
|
+
let readCursor!: LogCursor;
|
|
2039
|
+
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
2040
|
+
const response = await environment.operations.append(
|
|
2041
|
+
credential,
|
|
2042
|
+
cube,
|
|
2043
|
+
createProtocolEnvelope(`stream-transition-${message}`, {
|
|
2044
|
+
post_id: `00000000-0000-4000-8000-${String(316 + index).padStart(12, '0')}`,
|
|
2045
|
+
to: 'broadcast',
|
|
2046
|
+
message,
|
|
2047
|
+
}),
|
|
2048
|
+
);
|
|
2049
|
+
expectStatus(response, 201, `Stream transition append ${message}`);
|
|
2050
|
+
if (message === 'beta') {
|
|
2051
|
+
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
2052
|
+
readCursor = { id: entry.id, created_at: entry.created_at };
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2115
2055
|
const barrier = environment.admin.armReplayTransition();
|
|
2116
|
-
const openPromise = environment.operations.openStream(
|
|
2056
|
+
const openPromise = environment.operations.openStream(credential, cube, readCursor);
|
|
2117
2057
|
await within(barrier.reached, 'Replay transition boundary', streamDeadlineMs);
|
|
2118
2058
|
try {
|
|
2119
2059
|
const appendDelta = environment.operations.append(
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
createProtocolEnvelope('
|
|
2123
|
-
post_id: '00000000-0000-4000-8000-
|
|
2060
|
+
credential,
|
|
2061
|
+
cube,
|
|
2062
|
+
createProtocolEnvelope('stream-transition-delta', {
|
|
2063
|
+
post_id: '00000000-0000-4000-8000-000000000319',
|
|
2124
2064
|
to: 'broadcast',
|
|
2125
2065
|
message: 'delta',
|
|
2126
2066
|
}),
|
|
@@ -2145,10 +2085,10 @@ export async function runAdapterConformance(
|
|
|
2145
2085
|
const noDuplicate = reader.next();
|
|
2146
2086
|
await provePending(noDuplicate, 'Live stream after delta', pendingProbeMs);
|
|
2147
2087
|
const epsilonResponse = await environment.operations.append(
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
createProtocolEnvelope('
|
|
2151
|
-
post_id: '00000000-0000-4000-8000-
|
|
2088
|
+
credential,
|
|
2089
|
+
cube,
|
|
2090
|
+
createProtocolEnvelope('stream-transition-epsilon', {
|
|
2091
|
+
post_id: '00000000-0000-4000-8000-000000000320',
|
|
2152
2092
|
to: 'broadcast',
|
|
2153
2093
|
message: 'epsilon',
|
|
2154
2094
|
}),
|
|
@@ -2158,33 +2098,56 @@ export async function runAdapterConformance(
|
|
|
2158
2098
|
invariant(epsilon.entry.message === 'epsilon', 'Live stream duplicated or reordered an event.');
|
|
2159
2099
|
invariant(compareLogCursor(delta.cursor, epsilon.cursor) < 0, 'Live stream cursors are not ordered.');
|
|
2160
2100
|
invariant(new Set([replay.entry.id, delta.entry.id, epsilon.entry.id]).size === 3, 'Stream delivered a duplicate entry.');
|
|
2161
|
-
liveCursor = epsilon.cursor;
|
|
2162
2101
|
return { replay: ['gamma'], transition: 'bookmark', live: ['delta', 'epsilon'], duplicates: 0 };
|
|
2163
2102
|
} finally {
|
|
2164
2103
|
await reader.close();
|
|
2165
2104
|
}
|
|
2166
2105
|
});
|
|
2167
|
-
|
|
2168
2106
|
await record('cursor.explicit-expiry', async () => {
|
|
2169
|
-
|
|
2170
|
-
await environment.admin.
|
|
2107
|
+
const cube = await environment.admin.createCube('cursor-expiry');
|
|
2108
|
+
await environment.admin.grantCube(principalA, cube);
|
|
2109
|
+
const appended = await environment.operations.append(
|
|
2110
|
+
credentialA,
|
|
2111
|
+
cube,
|
|
2112
|
+
createProtocolEnvelope('cursor-expiry-entry', {
|
|
2113
|
+
post_id: '00000000-0000-4000-8000-000000000321',
|
|
2114
|
+
to: 'broadcast',
|
|
2115
|
+
message: 'expire this cursor',
|
|
2116
|
+
}),
|
|
2117
|
+
);
|
|
2118
|
+
expectStatus(appended, 201, 'Cursor expiry fixture append');
|
|
2119
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2120
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
2121
|
+
await environment.admin.expireCursor(cube, cursor);
|
|
2171
2122
|
const response = await environment.operations.read(
|
|
2172
2123
|
credentialA,
|
|
2173
|
-
|
|
2174
|
-
createProtocolEnvelope('read-expired', { cursor
|
|
2124
|
+
cube,
|
|
2125
|
+
createProtocolEnvelope('read-expired', { cursor, limit: 10 }),
|
|
2175
2126
|
);
|
|
2176
2127
|
expectError(response, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor read');
|
|
2177
|
-
const stream = await environment.operations.openStream(credentialA,
|
|
2128
|
+
const stream = await environment.operations.openStream(credentialA, cube, cursor);
|
|
2178
2129
|
expectError(stream, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor stream');
|
|
2179
2130
|
invariant(stream.stream === null, 'Expired cursor stream exposed a body stream.');
|
|
2180
2131
|
return { read_status: 410, stream_status: 410, code: ErrorCode.CURSOR_EXPIRED };
|
|
2181
2132
|
});
|
|
2182
2133
|
|
|
2183
2134
|
await record('acks.idempotent', async () => {
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2135
|
+
const cube = await environment.admin.createCube('ack-idempotency');
|
|
2136
|
+
await environment.admin.grantCube(principalA, cube);
|
|
2137
|
+
const appended = await environment.operations.append(
|
|
2138
|
+
credentialA,
|
|
2139
|
+
cube,
|
|
2140
|
+
createProtocolEnvelope('ack-idempotency-entry', {
|
|
2141
|
+
post_id: '00000000-0000-4000-8000-000000000322',
|
|
2142
|
+
to: 'broadcast',
|
|
2143
|
+
message: 'acknowledge this entry',
|
|
2144
|
+
}),
|
|
2145
|
+
);
|
|
2146
|
+
expectStatus(appended, 201, 'Acknowledgement fixture append');
|
|
2147
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2148
|
+
const request = createProtocolEnvelope('ack-entry1', { entry_id: entry.id, kind: 'ack' });
|
|
2149
|
+
const first = await environment.operations.ack(credentialA, cube, request);
|
|
2150
|
+
const second = await environment.operations.ack(credentialA, cube, request);
|
|
2188
2151
|
expectStatus(first, 204, 'First acknowledgement');
|
|
2189
2152
|
expectStatus(second, 204, 'Repeated acknowledgement');
|
|
2190
2153
|
invariant((first.body === '' || first.body === undefined) && (second.body === '' || second.body === undefined), 'Acknowledgement responses must be bodyless.');
|
|
@@ -2215,7 +2178,6 @@ export async function runAdapterConformance(
|
|
|
2215
2178
|
expectStatus(append, 201, 'Acknowledgement-status fixture append');
|
|
2216
2179
|
const entry = decodeAppendLogResultEnvelope(append.body).payload.entry;
|
|
2217
2180
|
|
|
2218
|
-
const beforeMissing = await environment.admin.observeAuthorityState();
|
|
2219
2181
|
const missingResponse = await environment.operations.ackStatus(
|
|
2220
2182
|
credentialA,
|
|
2221
2183
|
cube,
|
|
@@ -2230,13 +2192,6 @@ export async function runAdapterConformance(
|
|
|
2230
2192
|
missing.claims.length === 0,
|
|
2231
2193
|
'Missing acknowledgement was not represented as nullable per-recipient state.',
|
|
2232
2194
|
);
|
|
2233
|
-
assertStateDelta(
|
|
2234
|
-
beforeMissing,
|
|
2235
|
-
await environment.admin.observeAuthorityState(),
|
|
2236
|
-
{},
|
|
2237
|
-
'Missing acknowledgement status query',
|
|
2238
|
-
);
|
|
2239
|
-
|
|
2240
2195
|
expectStatus(
|
|
2241
2196
|
await environment.operations.ack(
|
|
2242
2197
|
acknowledgedCredential,
|
|
@@ -2255,7 +2210,6 @@ export async function runAdapterConformance(
|
|
|
2255
2210
|
204,
|
|
2256
2211
|
'Acknowledgement-status claim',
|
|
2257
2212
|
);
|
|
2258
|
-
const beforeDistinct = await environment.admin.observeAuthorityState();
|
|
2259
2213
|
const distinctResponse = await environment.operations.ackStatus(
|
|
2260
2214
|
credentialA,
|
|
2261
2215
|
cube,
|
|
@@ -2272,26 +2226,6 @@ export async function runAdapterConformance(
|
|
|
2272
2226
|
distinct.recipients.find((recipient) => recipient.drone_id === claimingRecipient.id)?.acknowledged_at === null,
|
|
2273
2227
|
'Acknowledgement status conflated acknowledgement and claim records.',
|
|
2274
2228
|
);
|
|
2275
|
-
assertStateDelta(
|
|
2276
|
-
beforeDistinct,
|
|
2277
|
-
await environment.admin.observeAuthorityState(),
|
|
2278
|
-
{},
|
|
2279
|
-
'Distinct acknowledgement and claim status query',
|
|
2280
|
-
);
|
|
2281
|
-
|
|
2282
|
-
const unreadResponse = await environment.operations.read(
|
|
2283
|
-
credentialA,
|
|
2284
|
-
cube,
|
|
2285
|
-
createProtocolEnvelope('ack-status-unread', { cursor: null, limit: 10 }),
|
|
2286
|
-
);
|
|
2287
|
-
expectStatus(unreadResponse, 200, 'Post-query unread read');
|
|
2288
|
-
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2289
|
-
invariant(
|
|
2290
|
-
unread.entries.length === 1 && unread.entries[0].id === entry.id,
|
|
2291
|
-
'Acknowledgement-status query advanced unread delivery state.',
|
|
2292
|
-
);
|
|
2293
|
-
|
|
2294
|
-
const beforeUnknown = await environment.admin.observeAuthorityState();
|
|
2295
2229
|
expectError(
|
|
2296
2230
|
await environment.operations.ackStatus(
|
|
2297
2231
|
credentialA,
|
|
@@ -2304,11 +2238,16 @@ export async function runAdapterConformance(
|
|
|
2304
2238
|
ErrorCode.NOT_FOUND,
|
|
2305
2239
|
'Unknown acknowledgement-status entry',
|
|
2306
2240
|
);
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
{},
|
|
2311
|
-
|
|
2241
|
+
const unreadResponse = await environment.operations.read(
|
|
2242
|
+
credentialA,
|
|
2243
|
+
cube,
|
|
2244
|
+
createProtocolEnvelope('ack-status-unread', { cursor: null, limit: 10 }),
|
|
2245
|
+
);
|
|
2246
|
+
expectStatus(unreadResponse, 200, 'Post-query unread read');
|
|
2247
|
+
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2248
|
+
invariant(
|
|
2249
|
+
same(unread.entries, [entry]),
|
|
2250
|
+
'Acknowledgement-status query mutated or advanced unread delivery state.',
|
|
2312
2251
|
);
|
|
2313
2252
|
return {
|
|
2314
2253
|
missing_acknowledgement: null,
|
|
@@ -2321,23 +2260,38 @@ export async function runAdapterConformance(
|
|
|
2321
2260
|
});
|
|
2322
2261
|
|
|
2323
2262
|
await record('claims.durable-noncursor', async () => {
|
|
2324
|
-
|
|
2263
|
+
const principal = await environment.admin.createPrincipal('claim-noncursor');
|
|
2264
|
+
const cube = await environment.admin.createCube('claim-noncursor');
|
|
2265
|
+
await environment.admin.grantCube(principal, cube);
|
|
2266
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
2267
|
+
const appended = await environment.operations.append(
|
|
2268
|
+
credential,
|
|
2269
|
+
cube,
|
|
2270
|
+
createProtocolEnvelope('claim-fixture-entry', {
|
|
2271
|
+
post_id: '00000000-0000-4000-8000-000000000314',
|
|
2272
|
+
to: 'broadcast',
|
|
2273
|
+
message: 'claim cursor baseline',
|
|
2274
|
+
}),
|
|
2275
|
+
);
|
|
2276
|
+
expectStatus(appended, 201, 'Claim fixture append');
|
|
2277
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2278
|
+
const baselineCursor = { id: entry.id, created_at: entry.created_at };
|
|
2325
2279
|
const claim = await environment.operations.ack(
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
createProtocolEnvelope('claim-
|
|
2280
|
+
credential,
|
|
2281
|
+
cube,
|
|
2282
|
+
createProtocolEnvelope('claim-fixture-claim', { entry_id: entry.id, kind: 'claim' }),
|
|
2329
2283
|
);
|
|
2330
2284
|
expectStatus(claim, 204, 'Claim');
|
|
2331
2285
|
const read = await environment.operations.read(
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
createProtocolEnvelope('
|
|
2286
|
+
credential,
|
|
2287
|
+
cube,
|
|
2288
|
+
createProtocolEnvelope('claim-fixture-readback', { cursor: baselineCursor, limit: 10 }),
|
|
2335
2289
|
);
|
|
2336
2290
|
expectStatus(read, 200, 'Claim-state read');
|
|
2337
2291
|
const page = decodeReadLogResultEnvelope(read.body).payload;
|
|
2338
2292
|
invariant(page.entries.length === 0, 'Claim unexpectedly created a log entry.');
|
|
2339
|
-
invariant(page.cursor !== null && compareLogCursor(page.cursor,
|
|
2340
|
-
invariant(page.claims.some((item) => item.log_entry_id ===
|
|
2293
|
+
invariant(page.cursor !== null && compareLogCursor(page.cursor, baselineCursor) === 0, 'Claim advanced the log cursor.');
|
|
2294
|
+
invariant(page.claims.some((item) => item.log_entry_id === entry.id), 'Claim was not durable in a later read.');
|
|
2341
2295
|
return { durable_claims: 1, entries: 0, cursor_advanced: false };
|
|
2342
2296
|
});
|
|
2343
2297
|
|
|
@@ -2378,6 +2332,7 @@ export async function runAdapterConformance(
|
|
|
2378
2332
|
let metadataDroneB!: ConformanceDrone;
|
|
2379
2333
|
let metadataSessionA = '';
|
|
2380
2334
|
await record('security.drone-management-authorization', async () => {
|
|
2335
|
+
const observationCredential = await environment.admin.issueDroneSession(principalA);
|
|
2381
2336
|
workerRoleA = await environment.admin.createRole(cubeA, {
|
|
2382
2337
|
roleClass: 'worker', isHumanSeat: false,
|
|
2383
2338
|
});
|
|
@@ -2404,6 +2359,10 @@ export async function runAdapterConformance(
|
|
|
2404
2359
|
deniedCredentials.push({ access: access as 'read' | 'write', credential });
|
|
2405
2360
|
}
|
|
2406
2361
|
for (const { access, credential } of deniedCredentials) {
|
|
2362
|
+
const rosterBefore = await environment.operations.listDrones(observationCredential, cubeA);
|
|
2363
|
+
expectStatus(rosterBefore, 200, `${access} principal pre-denial roster`);
|
|
2364
|
+
const workerBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
2365
|
+
invariant(workerBefore, `${access} principal pre-denial roster omitted the managed worker.`);
|
|
2407
2366
|
expectError(
|
|
2408
2367
|
await environment.operations.reassignDrone(
|
|
2409
2368
|
credential,
|
|
@@ -2426,10 +2385,15 @@ export async function runAdapterConformance(
|
|
|
2426
2385
|
ErrorCode.ACCESS_DENIED,
|
|
2427
2386
|
`${access} principal eviction`,
|
|
2428
2387
|
);
|
|
2388
|
+
const rosterAfter = await environment.operations.listDrones(observationCredential, cubeA);
|
|
2389
|
+
expectStatus(rosterAfter, 200, `${access} principal post-denial roster`);
|
|
2390
|
+
invariant(
|
|
2391
|
+
same(listedDrone(rosterAfter, managedWorker.id), workerBefore),
|
|
2392
|
+
`${access} principal management denial mutated the managed worker.`,
|
|
2393
|
+
);
|
|
2429
2394
|
}
|
|
2430
2395
|
return { read_status: 403, write_status: 403, manage_required: true };
|
|
2431
2396
|
});
|
|
2432
|
-
|
|
2433
2397
|
await record('security.manage-access-matrix', async () => {
|
|
2434
2398
|
const noGrantPrincipal = await environment.admin.createPrincipal('Coordinator role without cube grant');
|
|
2435
2399
|
const noGrantInvitation = await environment.admin.issueSingleUseInvitation(noGrantPrincipal, 'client');
|
|
@@ -2449,12 +2413,8 @@ export async function runAdapterConformance(
|
|
|
2449
2413
|
roleClass: 'worker', isHumanSeat: false,
|
|
2450
2414
|
});
|
|
2451
2415
|
const evictionTarget = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
2452
|
-
await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
2416
|
+
const evictionTargetCredential = await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
2453
2417
|
const unknownCube = { id: '00000000-0000-4000-8000-000000000399' };
|
|
2454
|
-
const snapshot = async (): Promise<unknown> => ({
|
|
2455
|
-
cubeA: await environment.admin.inspectCubeManagementState(cubeA),
|
|
2456
|
-
cubeB: await environment.admin.inspectCubeManagementState(cubeB),
|
|
2457
|
-
});
|
|
2458
2418
|
const operations: ReadonlyArray<{
|
|
2459
2419
|
name: string;
|
|
2460
2420
|
successStatus: number;
|
|
@@ -2519,6 +2479,48 @@ export async function runAdapterConformance(
|
|
|
2519
2479
|
),
|
|
2520
2480
|
},
|
|
2521
2481
|
];
|
|
2482
|
+
const observableState = async (operationName: string): Promise<unknown | null> => {
|
|
2483
|
+
if (operationName === 'decision-record') {
|
|
2484
|
+
const response = await environment.operations.listDecisions(
|
|
2485
|
+
credentialA,
|
|
2486
|
+
cubeA,
|
|
2487
|
+
createProtocolEnvelope('matrix-observe-decisions', {}),
|
|
2488
|
+
);
|
|
2489
|
+
expectStatus(response, 200, 'Matrix decision observation');
|
|
2490
|
+
return decodeDecisionsResultEnvelope(response.body).payload;
|
|
2491
|
+
}
|
|
2492
|
+
if (operationName === 'drone-reassign' || operationName === 'drone-evict') {
|
|
2493
|
+
const response = await environment.operations.listDrones(credentialA, cubeA);
|
|
2494
|
+
expectStatus(response, 200, 'Matrix drone observation');
|
|
2495
|
+
return listedDrone(
|
|
2496
|
+
response,
|
|
2497
|
+
operationName === 'drone-reassign' ? managedWorker.id : evictionTarget.id,
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
return null;
|
|
2501
|
+
};
|
|
2502
|
+
const assertObservableDenial = async (operationName: string, before: unknown | null): Promise<void> => {
|
|
2503
|
+
if (operationName === 'role-create') {
|
|
2504
|
+
expectError(
|
|
2505
|
+
await environment.operations.roleRationale(
|
|
2506
|
+
credentialA,
|
|
2507
|
+
cubeA,
|
|
2508
|
+
createProtocolEnvelope('matrix-observe-role', {
|
|
2509
|
+
role: 'Matrix Role',
|
|
2510
|
+
section: 'Workflow rationale',
|
|
2511
|
+
}),
|
|
2512
|
+
),
|
|
2513
|
+
404,
|
|
2514
|
+
ErrorCode.ROLE_NOT_FOUND,
|
|
2515
|
+
'Denied matrix role observation',
|
|
2516
|
+
);
|
|
2517
|
+
} else if (before !== null) {
|
|
2518
|
+
invariant(
|
|
2519
|
+
same(await observableState(operationName), before),
|
|
2520
|
+
`${operationName} changed protocol-visible state after denial.`,
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
};
|
|
2522
2524
|
|
|
2523
2525
|
for (const operation of operations) {
|
|
2524
2526
|
for (const [kind, credential] of [
|
|
@@ -2526,14 +2528,25 @@ export async function runAdapterConformance(
|
|
|
2526
2528
|
['write', writeCredential],
|
|
2527
2529
|
['drone-session', droneCredential],
|
|
2528
2530
|
] as const) {
|
|
2529
|
-
const before = await
|
|
2531
|
+
const before = await observableState(operation.name);
|
|
2530
2532
|
expectError(
|
|
2531
2533
|
await operation.invoke(credential, cubeA),
|
|
2532
2534
|
403,
|
|
2533
2535
|
ErrorCode.ACCESS_DENIED,
|
|
2534
2536
|
`${operation.name} by ${kind} principal`,
|
|
2535
2537
|
);
|
|
2536
|
-
|
|
2538
|
+
await assertObservableDenial(operation.name, before);
|
|
2539
|
+
if (operation.name === 'drone-evict') {
|
|
2540
|
+
expectStatus(
|
|
2541
|
+
await environment.operations.read(
|
|
2542
|
+
evictionTargetCredential,
|
|
2543
|
+
cubeA,
|
|
2544
|
+
createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 }),
|
|
2545
|
+
),
|
|
2546
|
+
200,
|
|
2547
|
+
`${kind} denied eviction target probe`,
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2537
2550
|
}
|
|
2538
2551
|
for (const [kind, credential, cube] of [
|
|
2539
2552
|
['no-grant', noGrantCredential, cubeA],
|
|
@@ -2541,22 +2554,28 @@ export async function runAdapterConformance(
|
|
|
2541
2554
|
['foreign-cube', credentialA, cubeB],
|
|
2542
2555
|
['unknown-cube', credentialA, unknownCube],
|
|
2543
2556
|
] as const) {
|
|
2544
|
-
const before = await
|
|
2557
|
+
const before = await observableState(operation.name);
|
|
2545
2558
|
expectError(
|
|
2546
2559
|
await operation.invoke(credential, cube),
|
|
2547
2560
|
404,
|
|
2548
2561
|
ErrorCode.NOT_FOUND,
|
|
2549
2562
|
`${operation.name} against ${kind}`,
|
|
2550
2563
|
);
|
|
2551
|
-
|
|
2564
|
+
await assertObservableDenial(operation.name, before);
|
|
2565
|
+
if (operation.name === 'drone-evict') {
|
|
2566
|
+
expectStatus(
|
|
2567
|
+
await environment.operations.read(
|
|
2568
|
+
evictionTargetCredential,
|
|
2569
|
+
cubeA,
|
|
2570
|
+
createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 }),
|
|
2571
|
+
),
|
|
2572
|
+
200,
|
|
2573
|
+
`${kind} denied eviction target probe`,
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2552
2576
|
}
|
|
2553
|
-
const beforeSuccess = await environment.admin.inspectCubeManagementState(cubeA);
|
|
2554
2577
|
const success = await operation.invoke(credentialA, cubeA);
|
|
2555
2578
|
expectStatus(success, operation.successStatus, `${operation.name} by managing principal`);
|
|
2556
|
-
invariant(
|
|
2557
|
-
!same(await environment.admin.inspectCubeManagementState(cubeA), beforeSuccess),
|
|
2558
|
-
`${operation.name} managing success did not mutate its declared state.`,
|
|
2559
|
-
);
|
|
2560
2579
|
}
|
|
2561
2580
|
return {
|
|
2562
2581
|
operation_count: operations.length,
|
|
@@ -2564,7 +2583,8 @@ export async function runAdapterConformance(
|
|
|
2564
2583
|
read_write_status: 403,
|
|
2565
2584
|
drone_session_status: 403,
|
|
2566
2585
|
hidden_status: 404,
|
|
2567
|
-
|
|
2586
|
+
observable_non_mutation_operations: 4,
|
|
2587
|
+
write_only_operations: 2,
|
|
2568
2588
|
role_labels_authoritative: false,
|
|
2569
2589
|
};
|
|
2570
2590
|
});
|
|
@@ -2651,12 +2671,18 @@ export async function runAdapterConformance(
|
|
|
2651
2671
|
occupied_human_seat_denied: true,
|
|
2652
2672
|
};
|
|
2653
2673
|
});
|
|
2654
|
-
|
|
2655
2674
|
await record('security.cross-cube-drone-management', async () => {
|
|
2656
2675
|
const foreignRole = await environment.admin.createRole(cubeB, {
|
|
2657
2676
|
roleClass: 'worker', isHumanSeat: false,
|
|
2658
2677
|
});
|
|
2659
2678
|
const foreignDrone = await environment.admin.createDrone(principalB, cubeB, foreignRole);
|
|
2679
|
+
const foreignRosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
2680
|
+
const localRosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
2681
|
+
expectStatus(foreignRosterBefore, 200, 'Pre-denial foreign roster');
|
|
2682
|
+
expectStatus(localRosterBefore, 200, 'Pre-denial local roster');
|
|
2683
|
+
const foreignDroneBefore = listedDrone(foreignRosterBefore, foreignDrone.id);
|
|
2684
|
+
const managedWorkerBefore = listedDrone(localRosterBefore, managedWorker.id);
|
|
2685
|
+
invariant(foreignDroneBefore && managedWorkerBefore, 'Pre-denial roster omitted a drone-management target.');
|
|
2660
2686
|
expectError(
|
|
2661
2687
|
await environment.operations.reassignDrone(
|
|
2662
2688
|
credentialA,
|
|
@@ -2713,7 +2739,6 @@ export async function runAdapterConformance(
|
|
|
2713
2739
|
'Foreign role reassignment through authorized cube route',
|
|
2714
2740
|
);
|
|
2715
2741
|
await environment.admin.grantCube(principalA, cubeB, 'read');
|
|
2716
|
-
const foreignDroneBefore = await environment.admin.inspectManagedDrone(foreignDrone);
|
|
2717
2742
|
expectError(
|
|
2718
2743
|
await environment.operations.reassignDrone(
|
|
2719
2744
|
workerSession,
|
|
@@ -2725,10 +2750,6 @@ export async function runAdapterConformance(
|
|
|
2725
2750
|
ErrorCode.NOT_FOUND,
|
|
2726
2751
|
'Bound drone cross-cube reassignment',
|
|
2727
2752
|
);
|
|
2728
|
-
invariant(
|
|
2729
|
-
same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore),
|
|
2730
|
-
'Bound drone cross-cube reassignment mutated the foreign target.',
|
|
2731
|
-
);
|
|
2732
2753
|
expectError(
|
|
2733
2754
|
await environment.operations.evictDrone(
|
|
2734
2755
|
workerSession,
|
|
@@ -2740,15 +2761,24 @@ export async function runAdapterConformance(
|
|
|
2740
2761
|
ErrorCode.NOT_FOUND,
|
|
2741
2762
|
'Bound drone cross-cube eviction',
|
|
2742
2763
|
);
|
|
2764
|
+
const foreignRosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
2765
|
+
const localRosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
2766
|
+
expectStatus(foreignRosterAfter, 200, 'Post-denial foreign roster');
|
|
2767
|
+
expectStatus(localRosterAfter, 200, 'Post-denial local roster');
|
|
2743
2768
|
invariant(
|
|
2744
|
-
same(
|
|
2745
|
-
'
|
|
2769
|
+
same(listedDrone(foreignRosterAfter, foreignDrone.id), foreignDroneBefore),
|
|
2770
|
+
'Denied cross-cube management mutated the foreign drone.',
|
|
2771
|
+
);
|
|
2772
|
+
invariant(
|
|
2773
|
+
same(listedDrone(localRosterAfter, managedWorker.id), managedWorkerBefore),
|
|
2774
|
+
'Denied foreign-role management mutated the local drone.',
|
|
2746
2775
|
);
|
|
2747
2776
|
return {
|
|
2748
2777
|
unauthorized_cube_status: 404,
|
|
2749
2778
|
foreign_drone_status: 404,
|
|
2750
2779
|
foreign_role_status: 404,
|
|
2751
2780
|
bound_drone_cross_cube_status: 404,
|
|
2781
|
+
targets_unchanged: true,
|
|
2752
2782
|
code: ErrorCode.NOT_FOUND,
|
|
2753
2783
|
};
|
|
2754
2784
|
});
|
|
@@ -2783,14 +2813,6 @@ export async function runAdapterConformance(
|
|
|
2783
2813
|
}),
|
|
2784
2814
|
'Eviction response did not identify the terminal seat.',
|
|
2785
2815
|
);
|
|
2786
|
-
invariant(
|
|
2787
|
-
same(await environment.admin.inspectManagedDrone(evictedDrone), {
|
|
2788
|
-
role_id: workerRoleA.id,
|
|
2789
|
-
evicted: true,
|
|
2790
|
-
session_revoked: true,
|
|
2791
|
-
}),
|
|
2792
|
-
'Eviction did not atomically mark the drone evicted and revoke its session.',
|
|
2793
|
-
);
|
|
2794
2816
|
const after = await environment.operations.listDrones(credentialA, cubeA);
|
|
2795
2817
|
expectStatus(after, 200, 'Post-eviction roster');
|
|
2796
2818
|
invariant(!listedDroneIds(after).includes(evictedDrone.id), 'Evicted drone remained in roster.');
|
|
@@ -2864,38 +2886,46 @@ export async function runAdapterConformance(
|
|
|
2864
2886
|
},
|
|
2865
2887
|
)), 201, 'Role-contract reader enrollment');
|
|
2866
2888
|
|
|
2889
|
+
const refusalRationale = 'Workflow rationale:\nRefusal role remains.';
|
|
2867
2890
|
const defaultRole = await environment.admin.createRole(roleContractCube, {
|
|
2868
2891
|
roleClass: 'worker',
|
|
2869
2892
|
isHumanSeat: false,
|
|
2870
2893
|
name: 'Default Worker',
|
|
2894
|
+
detailedDescription: refusalRationale,
|
|
2871
2895
|
isDefault: true,
|
|
2872
2896
|
});
|
|
2873
2897
|
const mandatoryRole = await environment.admin.createRole(roleContractCube, {
|
|
2874
2898
|
roleClass: 'worker',
|
|
2875
2899
|
isHumanSeat: false,
|
|
2876
2900
|
name: 'Mandatory Worker',
|
|
2901
|
+
detailedDescription: refusalRationale,
|
|
2877
2902
|
isMandatory: true,
|
|
2878
2903
|
});
|
|
2879
2904
|
const humanRole = await environment.admin.createRole(roleContractCube, {
|
|
2880
2905
|
roleClass: 'queen',
|
|
2881
2906
|
isHumanSeat: true,
|
|
2882
2907
|
name: 'Human Seat',
|
|
2908
|
+
detailedDescription: refusalRationale,
|
|
2883
2909
|
});
|
|
2884
2910
|
const activeRole = await environment.admin.createRole(roleContractCube, {
|
|
2885
2911
|
roleClass: 'worker',
|
|
2886
2912
|
isHumanSeat: false,
|
|
2887
2913
|
name: 'Active Worker',
|
|
2914
|
+
detailedDescription: refusalRationale,
|
|
2888
2915
|
});
|
|
2889
|
-
await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
2916
|
+
const activeDrone = await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
2917
|
+
const activeRosterBefore = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
2918
|
+
expectStatus(activeRosterBefore, 200, 'Pre-refusal active-role roster');
|
|
2919
|
+
const activeDroneBefore = listedDrone(activeRosterBefore, activeDrone.id);
|
|
2920
|
+
invariant(activeDroneBefore, 'Pre-refusal roster omitted the active-role drone.');
|
|
2890
2921
|
|
|
2891
2922
|
const refusals = [
|
|
2892
|
-
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role'],
|
|
2893
|
-
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role'],
|
|
2894
|
-
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role'],
|
|
2895
|
-
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role'],
|
|
2923
|
+
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role', 'Default Worker', 'worker', false],
|
|
2924
|
+
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role', 'Mandatory Worker', 'worker', false],
|
|
2925
|
+
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role', 'Human Seat', 'queen', true],
|
|
2926
|
+
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role', 'Active Worker', 'worker', false],
|
|
2896
2927
|
] as const;
|
|
2897
|
-
for (const [index, [role, code, label]] of refusals.entries()) {
|
|
2898
|
-
const before = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
2928
|
+
for (const [index, [role, code, label, roleName, roleClass, isHumanSeat]] of refusals.entries()) {
|
|
2899
2929
|
const response = await environment.operations.deleteRole(
|
|
2900
2930
|
roleContractCredential,
|
|
2901
2931
|
roleContractCube,
|
|
@@ -2903,17 +2933,65 @@ export async function runAdapterConformance(
|
|
|
2903
2933
|
createProtocolEnvelope(`delete-refusal-${index}`, {}),
|
|
2904
2934
|
);
|
|
2905
2935
|
expectError(response, 409, code, `Delete ${label}`);
|
|
2906
|
-
invariant(
|
|
2907
|
-
same(await environment.admin.inspectCubeManagementState(roleContractCube), before),
|
|
2908
|
-
`Delete ${label} mutated cube state after refusal.`,
|
|
2909
|
-
);
|
|
2910
2936
|
if (code === ErrorCode.ROLE_IN_USE) {
|
|
2911
2937
|
const message = protocolErrorMessage(response) ?? '';
|
|
2912
2938
|
invariant(
|
|
2913
2939
|
message === ROLE_IN_USE_DELETE_MESSAGE,
|
|
2914
2940
|
'ROLE_IN_USE did not direct the caller to reassign or evict the drones first.',
|
|
2915
2941
|
);
|
|
2942
|
+
const roster = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
2943
|
+
expectStatus(roster, 200, 'Active-role refusal roster readback');
|
|
2944
|
+
invariant(
|
|
2945
|
+
same(listedDrone(roster, activeDrone.id), activeDroneBefore),
|
|
2946
|
+
'Active-role deletion refusal changed the active drone.',
|
|
2947
|
+
);
|
|
2916
2948
|
}
|
|
2949
|
+
const retained = await environment.operations.roleRationale(
|
|
2950
|
+
roleContractReadCredential,
|
|
2951
|
+
roleContractCube,
|
|
2952
|
+
createProtocolEnvelope(`delete-refusal-${index}-readback`, {
|
|
2953
|
+
role: role.id,
|
|
2954
|
+
section: 'Workflow rationale',
|
|
2955
|
+
}),
|
|
2956
|
+
);
|
|
2957
|
+
expectStatus(retained, 200, `Retained ${label} rationale lookup`);
|
|
2958
|
+
const retainedPayload = decodeRoleRationaleResultEnvelope(retained.body).payload;
|
|
2959
|
+
invariant(
|
|
2960
|
+
retainedPayload.role_id === role.id &&
|
|
2961
|
+
retainedPayload.role_name === roleName &&
|
|
2962
|
+
same(retainedPayload.section, {
|
|
2963
|
+
heading: 'Workflow rationale',
|
|
2964
|
+
body: refusalRationale,
|
|
2965
|
+
}),
|
|
2966
|
+
`Retained ${label} rationale lookup returned a different role or section.`,
|
|
2967
|
+
);
|
|
2968
|
+
const shapeSession = `${String(index).repeat(42)}S`;
|
|
2969
|
+
const shapeProbe = await environment.operations.attach(
|
|
2970
|
+
roleContractCredential,
|
|
2971
|
+
createProtocolEnvelope(`delete-refusal-${index}-shape-probe`, {
|
|
2972
|
+
cube_id: roleContractCube.id,
|
|
2973
|
+
role_id: role.id,
|
|
2974
|
+
session_credential: shapeSession,
|
|
2975
|
+
}),
|
|
2976
|
+
);
|
|
2977
|
+
expectStatus(shapeProbe, 200, `Retained ${label} shape probe`);
|
|
2978
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
2979
|
+
invariant(
|
|
2980
|
+
shapePayload.role.id === role.id &&
|
|
2981
|
+
shapePayload.role.role_class === roleClass &&
|
|
2982
|
+
shapePayload.role.is_human_seat === isHumanSeat,
|
|
2983
|
+
`Retained ${label} shape probe returned changed role semantics.`,
|
|
2984
|
+
);
|
|
2985
|
+
expectStatus(
|
|
2986
|
+
await environment.operations.evictDrone(
|
|
2987
|
+
roleContractCredential,
|
|
2988
|
+
roleContractCube,
|
|
2989
|
+
{ id: shapePayload.drone.id },
|
|
2990
|
+
createProtocolEnvelope(`delete-refusal-${index}-shape-cleanup`, {}),
|
|
2991
|
+
),
|
|
2992
|
+
200,
|
|
2993
|
+
`Retained ${label} shape cleanup`,
|
|
2994
|
+
);
|
|
2917
2995
|
}
|
|
2918
2996
|
|
|
2919
2997
|
const unknownRole = { id: '00000000-0000-4000-8000-000000000398' };
|
|
@@ -2933,8 +3011,8 @@ export async function runAdapterConformance(
|
|
|
2933
3011
|
roleClass: 'worker',
|
|
2934
3012
|
isHumanSeat: false,
|
|
2935
3013
|
name: 'Disposable Worker',
|
|
3014
|
+
detailedDescription: refusalRationale,
|
|
2936
3015
|
});
|
|
2937
|
-
const beforeReadDenial = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
2938
3016
|
expectError(
|
|
2939
3017
|
await environment.operations.deleteRole(
|
|
2940
3018
|
roleContractReadCredential,
|
|
@@ -2946,12 +3024,50 @@ export async function runAdapterConformance(
|
|
|
2946
3024
|
ErrorCode.ACCESS_DENIED,
|
|
2947
3025
|
'Delete role with read authority',
|
|
2948
3026
|
);
|
|
3027
|
+
const retainedDeletable = await environment.operations.roleRationale(
|
|
3028
|
+
roleContractReadCredential,
|
|
3029
|
+
roleContractCube,
|
|
3030
|
+
createProtocolEnvelope('delete-role-read-denied-readback', {
|
|
3031
|
+
role: deletableRole.id,
|
|
3032
|
+
section: 'Workflow rationale',
|
|
3033
|
+
}),
|
|
3034
|
+
);
|
|
3035
|
+
expectStatus(retainedDeletable, 200, 'Read-denied retained role rationale lookup');
|
|
3036
|
+
const retainedDeletablePayload = decodeRoleRationaleResultEnvelope(retainedDeletable.body).payload;
|
|
2949
3037
|
invariant(
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
3038
|
+
retainedDeletablePayload.role_id === deletableRole.id &&
|
|
3039
|
+
retainedDeletablePayload.role_name === 'Disposable Worker' &&
|
|
3040
|
+
same(retainedDeletablePayload.section, {
|
|
3041
|
+
heading: 'Workflow rationale',
|
|
3042
|
+
body: refusalRationale,
|
|
3043
|
+
}),
|
|
3044
|
+
'Read-authority deletion denial changed the target role.',
|
|
3045
|
+
);
|
|
3046
|
+
const shapeProbe = await environment.operations.attach(
|
|
3047
|
+
roleContractCredential,
|
|
3048
|
+
createProtocolEnvelope('delete-role-read-denied-shape-probe', {
|
|
3049
|
+
cube_id: roleContractCube.id,
|
|
3050
|
+
role_id: deletableRole.id,
|
|
3051
|
+
session_credential: 'S'.repeat(43),
|
|
3052
|
+
}),
|
|
3053
|
+
);
|
|
3054
|
+
expectStatus(shapeProbe, 200, 'Read-denied retained role shape probe');
|
|
3055
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
3056
|
+
invariant(
|
|
3057
|
+
shapePayload.role.id === deletableRole.id &&
|
|
3058
|
+
shapePayload.role.role_class === 'worker' &&
|
|
3059
|
+
shapePayload.role.is_human_seat === false,
|
|
3060
|
+
'Read-authority deletion denial changed the target role shape.',
|
|
3061
|
+
);
|
|
3062
|
+
expectStatus(
|
|
3063
|
+
await environment.operations.evictDrone(
|
|
3064
|
+
roleContractCredential,
|
|
3065
|
+
roleContractCube,
|
|
3066
|
+
{ id: shapePayload.drone.id },
|
|
3067
|
+
createProtocolEnvelope('delete-role-read-denied-shape-cleanup', {}),
|
|
2953
3068
|
),
|
|
2954
|
-
|
|
3069
|
+
200,
|
|
3070
|
+
'Read-denied retained role shape cleanup',
|
|
2955
3071
|
);
|
|
2956
3072
|
const deleted = await environment.operations.deleteRole(
|
|
2957
3073
|
roleContractCredential,
|
|
@@ -3002,10 +3118,6 @@ export async function runAdapterConformance(
|
|
|
3002
3118
|
evictedRole,
|
|
3003
3119
|
createProtocolEnvelope('delete-evicted-role', {}),
|
|
3004
3120
|
), 200, 'Delete role held only by an evicted drone');
|
|
3005
|
-
invariant(
|
|
3006
|
-
(await environment.admin.inspectManagedDrone(evictedDrone)).role_id === defaultRole.id,
|
|
3007
|
-
'Role deletion did not retarget the evicted drone to the surviving default role.',
|
|
3008
|
-
);
|
|
3009
3121
|
const read = await environment.operations.read(
|
|
3010
3122
|
roleContractCredential,
|
|
3011
3123
|
roleContractCube,
|
|
@@ -3028,11 +3140,10 @@ export async function runAdapterConformance(
|
|
|
3028
3140
|
ErrorCode.ROLE_REQUIRED,
|
|
3029
3141
|
],
|
|
3030
3142
|
in_use_message_actionable: true,
|
|
3031
|
-
|
|
3143
|
+
refusals_non_mutating: true,
|
|
3032
3144
|
activity_log_attribution_preserved: true,
|
|
3033
3145
|
};
|
|
3034
3146
|
});
|
|
3035
|
-
|
|
3036
3147
|
await record('roles.rationale-contract', async () => {
|
|
3037
3148
|
const detailedDescription = [
|
|
3038
3149
|
'Implements assigned work.',
|
|
@@ -3173,14 +3284,20 @@ export async function runAdapterConformance(
|
|
|
3173
3284
|
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
3174
3285
|
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
3175
3286
|
};
|
|
3287
|
+
try {
|
|
3288
|
+
metadataDroneA = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
3289
|
+
metadataSessionA = await environment.admin.issueManagedDroneSession(metadataDroneA);
|
|
3290
|
+
} catch (error) {
|
|
3291
|
+
prerequisiteFailures.set('shared.metadata-baseline', error instanceof Error ? error.message : String(error));
|
|
3292
|
+
}
|
|
3176
3293
|
await record('metadata.attach-report', async () => {
|
|
3177
|
-
|
|
3294
|
+
const attachSession = 'M'.repeat(43);
|
|
3178
3295
|
const created = await environment.operations.attach(
|
|
3179
3296
|
credentialA,
|
|
3180
3297
|
createProtocolEnvelope('metadata-attach-created', {
|
|
3181
3298
|
cube_id: cubeA.id,
|
|
3182
3299
|
role_id: workerRoleA.id,
|
|
3183
|
-
session_credential:
|
|
3300
|
+
session_credential: attachSession,
|
|
3184
3301
|
runtime_metadata: {
|
|
3185
3302
|
...knownMetadata,
|
|
3186
3303
|
working_repo_origin: 'git@github.com:Byte-Ventures/borg-mcp.git',
|
|
@@ -3192,7 +3309,7 @@ export async function runAdapterConformance(
|
|
|
3192
3309
|
invariant(createdPayload.result === 'created', 'First metadata attach did not create a seat.');
|
|
3193
3310
|
invariant(same(createdPayload.drone.runtime_metadata, knownMetadata), 'Attach did not echo canonical metadata.');
|
|
3194
3311
|
invariant(createdPayload.drone.runtime_metadata_reported, 'Present attach report was not marked reported.');
|
|
3195
|
-
|
|
3312
|
+
const attachedDrone = { id: createdPayload.drone.id };
|
|
3196
3313
|
|
|
3197
3314
|
const unavailableSession = 'N'.repeat(43);
|
|
3198
3315
|
const unavailable = await environment.operations.attach(
|
|
@@ -3239,15 +3356,15 @@ export async function runAdapterConformance(
|
|
|
3239
3356
|
createProtocolEnvelope('metadata-attach-reused', {
|
|
3240
3357
|
cube_id: cubeA.id,
|
|
3241
3358
|
role_id: workerRoleA.id,
|
|
3242
|
-
prior_drone_id:
|
|
3243
|
-
session_credential:
|
|
3359
|
+
prior_drone_id: attachedDrone.id,
|
|
3360
|
+
session_credential: attachSession,
|
|
3244
3361
|
runtime_metadata: knownMetadata,
|
|
3245
3362
|
}),
|
|
3246
3363
|
);
|
|
3247
3364
|
expectStatus(reused, 200, 'Metadata attach reuse');
|
|
3248
3365
|
invariant(decodeAttachResponseEnvelope(reused.body).payload.result === 'reused', 'Prior seat was not reused.');
|
|
3249
3366
|
const cleared = await environment.operations.selfMetadataUpdate(
|
|
3250
|
-
|
|
3367
|
+
attachSession,
|
|
3251
3368
|
cubeA,
|
|
3252
3369
|
createProtocolEnvelope('metadata-clear-all', {
|
|
3253
3370
|
agent_kind: null,
|
|
@@ -3265,7 +3382,7 @@ export async function runAdapterConformance(
|
|
|
3265
3382
|
);
|
|
3266
3383
|
expectStatus(
|
|
3267
3384
|
await environment.operations.selfMetadataUpdate(
|
|
3268
|
-
|
|
3385
|
+
attachSession,
|
|
3269
3386
|
cubeA,
|
|
3270
3387
|
createProtocolEnvelope('metadata-restore-known', knownMetadata),
|
|
3271
3388
|
),
|
|
@@ -3283,7 +3400,6 @@ export async function runAdapterConformance(
|
|
|
3283
3400
|
canonical_response: true,
|
|
3284
3401
|
};
|
|
3285
3402
|
});
|
|
3286
|
-
|
|
3287
3403
|
await record('metadata.self-heal-patch', async () => {
|
|
3288
3404
|
const updates = [
|
|
3289
3405
|
{ request_id: 'metadata-known-new', patch: { reported_model: 'openai/gpt-5.6-terra' } },
|
|
@@ -3291,6 +3407,7 @@ export async function runAdapterConformance(
|
|
|
3291
3407
|
{ request_id: 'metadata-null-repeat', patch: { reported_model: null } },
|
|
3292
3408
|
{ request_id: 'metadata-same-value', patch: { agent_kind: 'opencode' as const } },
|
|
3293
3409
|
];
|
|
3410
|
+
let state;
|
|
3294
3411
|
for (const update of updates) {
|
|
3295
3412
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3296
3413
|
metadataSessionA,
|
|
@@ -3298,15 +3415,30 @@ export async function runAdapterConformance(
|
|
|
3298
3415
|
createProtocolEnvelope(update.request_id, update.patch),
|
|
3299
3416
|
);
|
|
3300
3417
|
expectStatus(response, 200, update.request_id);
|
|
3301
|
-
decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body);
|
|
3418
|
+
state = decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body).payload;
|
|
3302
3419
|
}
|
|
3303
|
-
|
|
3304
|
-
invariant(state.
|
|
3305
|
-
invariant(state.metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
3420
|
+
invariant(state?.runtime_metadata.reported_model === null, 'Explicit null did not clear model metadata.');
|
|
3421
|
+
invariant(state.runtime_metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
3306
3422
|
return { known_replace: true, omitted_unchanged: true, null_clear: true, repeat_safe: true };
|
|
3307
3423
|
});
|
|
3308
3424
|
|
|
3309
3425
|
await record('security.metadata-invalid-atomic', async () => {
|
|
3426
|
+
const baselineResponse = await environment.operations.selfMetadataUpdate(
|
|
3427
|
+
metadataSessionA,
|
|
3428
|
+
cubeA,
|
|
3429
|
+
createProtocolEnvelope('metadata-invalid-baseline', {
|
|
3430
|
+
agent_kind: 'opencode',
|
|
3431
|
+
reported_model: null,
|
|
3432
|
+
working_repo_name: 'Byte-Ventures/borg-mcp-shared',
|
|
3433
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp-shared',
|
|
3434
|
+
}),
|
|
3435
|
+
);
|
|
3436
|
+
expectStatus(baselineResponse, 200, 'Invalid metadata baseline');
|
|
3437
|
+
decodeUpdateDroneRuntimeMetadataResponseEnvelope(baselineResponse.body);
|
|
3438
|
+
const baselineRoster = await environment.operations.listDrones(credentialA, cubeA);
|
|
3439
|
+
expectStatus(baselineRoster, 200, 'Invalid metadata baseline roster');
|
|
3440
|
+
const baselineDrone = listedDrone(baselineRoster, metadataDroneA.id);
|
|
3441
|
+
invariant(baselineDrone, 'Invalid metadata baseline roster omitted the metadata seat.');
|
|
3310
3442
|
const invalidPatches: unknown[] = [
|
|
3311
3443
|
{},
|
|
3312
3444
|
{ agent_kind: 'OpenCode' },
|
|
@@ -3324,15 +3456,16 @@ export async function runAdapterConformance(
|
|
|
3324
3456
|
{ last_seen: '2026-07-14T10:00:00.000Z' },
|
|
3325
3457
|
];
|
|
3326
3458
|
for (const [index, patch] of invalidPatches.entries()) {
|
|
3327
|
-
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
3328
3459
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3329
3460
|
metadataSessionA,
|
|
3330
3461
|
cubeA,
|
|
3331
3462
|
createProtocolEnvelope(`metadata-invalid-${index}`, patch),
|
|
3332
3463
|
);
|
|
3333
3464
|
expectError(response, 400, ErrorCode.INVALID_INPUT, `Invalid metadata patch ${index}`);
|
|
3465
|
+
const readbackResponse = await environment.operations.listDrones(credentialA, cubeA);
|
|
3466
|
+
expectStatus(readbackResponse, 200, `Invalid metadata readback ${index}`);
|
|
3334
3467
|
invariant(
|
|
3335
|
-
same(
|
|
3468
|
+
same(listedDrone(readbackResponse, metadataDroneA.id), baselineDrone),
|
|
3336
3469
|
`Invalid metadata patch ${index} partially mutated state.`,
|
|
3337
3470
|
);
|
|
3338
3471
|
}
|
|
@@ -3341,25 +3474,30 @@ export async function runAdapterConformance(
|
|
|
3341
3474
|
|
|
3342
3475
|
await record('security.metadata-own-seat', async () => {
|
|
3343
3476
|
const peer = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
const
|
|
3477
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3478
|
+
expectStatus(rosterBefore, 200, 'Pre-update metadata roster');
|
|
3479
|
+
const peerBefore = listedDrone(rosterBefore, peer.id);
|
|
3480
|
+
const managedBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
3481
|
+
const ownBefore = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3482
|
+
invariant(peerBefore && managedBefore && ownBefore, 'Pre-update metadata roster omitted a fixture drone.');
|
|
3347
3483
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3348
3484
|
metadataSessionA,
|
|
3349
3485
|
cubeA,
|
|
3350
3486
|
createProtocolEnvelope('metadata-own-seat', { agent_kind: 'claude' }),
|
|
3351
3487
|
);
|
|
3352
3488
|
expectStatus(response, 200, 'Own-seat metadata update');
|
|
3489
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3490
|
+
expectStatus(rosterAfter, 200, 'Post-update metadata roster');
|
|
3353
3491
|
invariant(
|
|
3354
|
-
same(
|
|
3492
|
+
same(listedDrone(rosterAfter, peer.id), peerBefore),
|
|
3355
3493
|
'Own-seat update mutated a peer seat.',
|
|
3356
3494
|
);
|
|
3357
3495
|
invariant(
|
|
3358
|
-
same(
|
|
3496
|
+
same(listedDrone(rosterAfter, managedWorker.id), managedBefore),
|
|
3359
3497
|
'Own-seat update mutated another existing seat.',
|
|
3360
3498
|
);
|
|
3361
3499
|
invariant(
|
|
3362
|
-
(
|
|
3500
|
+
listedDrone(rosterAfter, metadataDroneA.id)?.role_id === ownBefore.role_id,
|
|
3363
3501
|
'Metadata update changed the current seat role.',
|
|
3364
3502
|
);
|
|
3365
3503
|
expectError(
|
|
@@ -3401,7 +3539,12 @@ export async function runAdapterConformance(
|
|
|
3401
3539
|
);
|
|
3402
3540
|
rejectedStates.push(['evicted', evicted, evictedSession, 410, ErrorCode.DRONE_EVICTED]);
|
|
3403
3541
|
for (const [label, drone, session, status, code] of rejectedStates) {
|
|
3404
|
-
const
|
|
3542
|
+
const rosterBefore = label === 'revoked'
|
|
3543
|
+
? await environment.operations.listDrones(credentialA, cubeA)
|
|
3544
|
+
: null;
|
|
3545
|
+
if (rosterBefore) expectStatus(rosterBefore, 200, 'Pre-denial revoked metadata roster');
|
|
3546
|
+
const droneBefore = rosterBefore ? listedDrone(rosterBefore, drone.id) : null;
|
|
3547
|
+
if (rosterBefore) invariant(droneBefore, 'Pre-denial roster omitted the revoked metadata seat.');
|
|
3405
3548
|
expectError(
|
|
3406
3549
|
await environment.operations.selfMetadataUpdate(
|
|
3407
3550
|
session,
|
|
@@ -3412,10 +3555,14 @@ export async function runAdapterConformance(
|
|
|
3412
3555
|
code,
|
|
3413
3556
|
`${label} metadata session`,
|
|
3414
3557
|
);
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3558
|
+
if (droneBefore) {
|
|
3559
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3560
|
+
expectStatus(rosterAfter, 200, 'Post-denial revoked metadata roster');
|
|
3561
|
+
invariant(
|
|
3562
|
+
same(listedDrone(rosterAfter, drone.id), droneBefore),
|
|
3563
|
+
'Revoked metadata denial mutated the protocol-visible seat.',
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3419
3566
|
}
|
|
3420
3567
|
return {
|
|
3421
3568
|
own_seat_only: true,
|
|
@@ -3446,7 +3593,10 @@ export async function runAdapterConformance(
|
|
|
3446
3593
|
);
|
|
3447
3594
|
expectStatus(attachedB, 200, 'Cube B metadata attach');
|
|
3448
3595
|
metadataDroneB = { id: decodeAttachResponseEnvelope(attachedB.body).payload.drone.id };
|
|
3449
|
-
const
|
|
3596
|
+
const rosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
3597
|
+
expectStatus(rosterBefore, 200, 'Pre-probe cube B roster');
|
|
3598
|
+
const beforeB = listedDrone(rosterBefore, metadataDroneB.id);
|
|
3599
|
+
invariant(beforeB, 'Pre-probe cube B roster omitted its metadata seat.');
|
|
3450
3600
|
const foreign = await environment.operations.selfMetadataUpdate(
|
|
3451
3601
|
metadataSessionA,
|
|
3452
3602
|
cubeB,
|
|
@@ -3460,17 +3610,19 @@ export async function runAdapterConformance(
|
|
|
3460
3610
|
);
|
|
3461
3611
|
expectError(unknown, 404, ErrorCode.NOT_FOUND, 'Unknown-cube metadata update');
|
|
3462
3612
|
invariant(same(foreign.body, unknown.body), 'Foreign and unknown metadata probes were distinguishable.');
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
);
|
|
3613
|
+
const rosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
3614
|
+
expectStatus(rosterAfter, 200, 'Post-probe cube B roster');
|
|
3615
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneB.id), beforeB), 'Cross-cube metadata update mutated the foreign seat.');
|
|
3467
3616
|
const listedA = await environment.operations.listDrones(credentialA, cubeA);
|
|
3468
3617
|
invariant(!listedDroneIds(listedA).includes(metadataDroneB.id), 'Cube A roster disclosed cube B metadata seat.');
|
|
3469
3618
|
return { update_status: 404, foreign_unknown_indistinguishable: true, foreign_unchanged: true, roster_isolated: true };
|
|
3470
3619
|
});
|
|
3471
3620
|
|
|
3472
3621
|
await record('security.metadata-noninterference', async () => {
|
|
3473
|
-
const
|
|
3622
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3623
|
+
expectStatus(rosterBefore, 200, 'Pre-update noninterference roster');
|
|
3624
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3625
|
+
invariant(before, 'Pre-update noninterference roster omitted the metadata seat.');
|
|
3474
3626
|
for (const [requestId, patch] of [
|
|
3475
3627
|
['metadata-noninterference-known', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
3476
3628
|
['metadata-noninterference-repeat', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
@@ -3506,22 +3658,23 @@ export async function runAdapterConformance(
|
|
|
3506
3658
|
}),
|
|
3507
3659
|
);
|
|
3508
3660
|
expectStatus(reattached, 200, 'Metadata non-interference reattach');
|
|
3509
|
-
const
|
|
3510
|
-
|
|
3511
|
-
const
|
|
3512
|
-
invariant(
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
model_turns_unchanged: true,
|
|
3519
|
-
};
|
|
3661
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3662
|
+
expectStatus(rosterAfter, 200, 'Post-update noninterference roster');
|
|
3663
|
+
const after = listedDrone(rosterAfter, metadataDroneA.id);
|
|
3664
|
+
invariant(after, 'Post-update noninterference roster omitted the metadata seat.');
|
|
3665
|
+
invariant(
|
|
3666
|
+
same(withoutRuntimeMetadata(before), withoutRuntimeMetadata(after)),
|
|
3667
|
+
'Metadata update changed non-metadata roster state.',
|
|
3668
|
+
);
|
|
3669
|
+
return { non_metadata_roster_state_unchanged: true };
|
|
3520
3670
|
});
|
|
3521
3671
|
|
|
3522
3672
|
await record('security.metadata-secret-non-echo', async () => {
|
|
3523
3673
|
const marker = 'SECRET-METADATA-MARKER';
|
|
3524
|
-
const
|
|
3674
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3675
|
+
expectStatus(rosterBefore, 200, 'Pre-secret metadata roster');
|
|
3676
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3677
|
+
invariant(before, 'Pre-secret metadata roster omitted the metadata seat.');
|
|
3525
3678
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3526
3679
|
metadataSessionA,
|
|
3527
3680
|
cubeA,
|
|
@@ -3532,10 +3685,9 @@ export async function runAdapterConformance(
|
|
|
3532
3685
|
);
|
|
3533
3686
|
expectError(response, 400, ErrorCode.INVALID_INPUT, 'Secret-bearing metadata update');
|
|
3534
3687
|
invariant(!JSON.stringify(response.body).includes(marker), 'Metadata error echoed hostile secret input.');
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
);
|
|
3688
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3689
|
+
expectStatus(rosterAfter, 200, 'Post-secret metadata roster');
|
|
3690
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneA.id), before), 'Secret-bearing metadata input was persisted.');
|
|
3539
3691
|
const hostileKeys = [
|
|
3540
3692
|
'SECRET-METADATA-KEY-MARKER',
|
|
3541
3693
|
`control\u001b[2J`,
|
|
@@ -3570,8 +3722,23 @@ export async function runAdapterConformance(
|
|
|
3570
3722
|
});
|
|
3571
3723
|
|
|
3572
3724
|
await record('security.active-stream-revocation', async () => {
|
|
3573
|
-
|
|
3574
|
-
const
|
|
3725
|
+
const principal = await environment.admin.createPrincipal('stream-revocation');
|
|
3726
|
+
const cube = await environment.admin.createCube('stream-revocation');
|
|
3727
|
+
await environment.admin.grantCube(principal, cube);
|
|
3728
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
3729
|
+
const appended = await environment.operations.append(
|
|
3730
|
+
credential,
|
|
3731
|
+
cube,
|
|
3732
|
+
createProtocolEnvelope('stream-revocation-entry', {
|
|
3733
|
+
post_id: '00000000-0000-4000-8000-000000000315',
|
|
3734
|
+
to: 'broadcast',
|
|
3735
|
+
message: 'revocation cursor baseline',
|
|
3736
|
+
}),
|
|
3737
|
+
);
|
|
3738
|
+
expectStatus(appended, 201, 'Revocation fixture append');
|
|
3739
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
3740
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
3741
|
+
const opened = await environment.operations.openStream(credential, cube, cursor);
|
|
3575
3742
|
expectStatus(opened, 200, 'Revocation stream open');
|
|
3576
3743
|
invariant(opened.stream, 'Successful revocation stream omitted its AsyncIterable.');
|
|
3577
3744
|
const reader = new SseEventReader(opened.stream);
|
|
@@ -3580,7 +3747,7 @@ export async function runAdapterConformance(
|
|
|
3580
3747
|
invariant(bookmark.type === 'bookmark' && bookmark.replay_complete, 'Fresh live stream did not complete replay.');
|
|
3581
3748
|
const pending = reader.next();
|
|
3582
3749
|
await provePending(pending, 'Idle live stream', pendingProbeMs);
|
|
3583
|
-
await environment.admin.revokePrincipal(
|
|
3750
|
+
await environment.admin.revokePrincipal(principal);
|
|
3584
3751
|
let terminated = false;
|
|
3585
3752
|
try {
|
|
3586
3753
|
await within(pending, 'Revoked stream termination', streamDeadlineMs);
|
|
@@ -3593,9 +3760,9 @@ export async function runAdapterConformance(
|
|
|
3593
3760
|
await reader.close();
|
|
3594
3761
|
}
|
|
3595
3762
|
const rejected = await environment.operations.read(
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
createProtocolEnvelope('read-revoked', { cursor
|
|
3763
|
+
credential,
|
|
3764
|
+
cube,
|
|
3765
|
+
createProtocolEnvelope('read-revoked', { cursor, limit: 10 }),
|
|
3599
3766
|
);
|
|
3600
3767
|
expectError(rejected, 401, ErrorCode.SESSION_REVOKED, 'Post-revocation request');
|
|
3601
3768
|
return { stream_terminated: true, subsequent_status: 401, subsequent_code: ErrorCode.SESSION_REVOKED };
|
|
@@ -3627,15 +3794,16 @@ export async function runAdapterConformance(
|
|
|
3627
3794
|
const writer = await enrollParent('delete-writer', 'W', '704');
|
|
3628
3795
|
const outsider = await enrollParent('delete-outsider', 'O', '705');
|
|
3629
3796
|
await environment.admin.grantCreateCubeCapability(creator.principal);
|
|
3797
|
+
const deleteCubeRequest = {
|
|
3798
|
+
retry_key: '00000000-0000-4000-8000-000000000706',
|
|
3799
|
+
name: 'Disposable Cube',
|
|
3800
|
+
working_repo_name: 'disposable-cube',
|
|
3801
|
+
repository: { kind: 'local' as const, value: '00000000-0000-4000-8000-000000000707' },
|
|
3802
|
+
template: 'software-dev' as const,
|
|
3803
|
+
};
|
|
3630
3804
|
const createResponse = await environment.operations.createCube(
|
|
3631
3805
|
creator.credential,
|
|
3632
|
-
createProtocolEnvelope('delete-cube-create',
|
|
3633
|
-
retry_key: '00000000-0000-4000-8000-000000000706',
|
|
3634
|
-
name: 'Disposable Cube',
|
|
3635
|
-
working_repo_name: 'disposable-cube',
|
|
3636
|
-
repository: { kind: 'local', value: '00000000-0000-4000-8000-000000000707' },
|
|
3637
|
-
template: 'default',
|
|
3638
|
-
}),
|
|
3806
|
+
createProtocolEnvelope('delete-cube-create', deleteCubeRequest),
|
|
3639
3807
|
);
|
|
3640
3808
|
expectStatus(createResponse, 201, 'Disposable cube creation');
|
|
3641
3809
|
const created = decodeCreateCubeResponseEnvelope(createResponse.body).payload;
|
|
@@ -3646,6 +3814,17 @@ export async function runAdapterConformance(
|
|
|
3646
3814
|
await environment.admin.grantCube(writer.principal, cube, 'write');
|
|
3647
3815
|
const drone = await environment.admin.createDrone(creator.principal, cube, role);
|
|
3648
3816
|
const droneCredential = await environment.admin.issueManagedDroneSession(drone);
|
|
3817
|
+
const roleProbeSession = 'T'.repeat(43);
|
|
3818
|
+
const initialRoleProbe = await environment.operations.attach(
|
|
3819
|
+
manager.credential,
|
|
3820
|
+
createProtocolEnvelope('delete-role-shape-baseline', {
|
|
3821
|
+
cube_id: cube.id,
|
|
3822
|
+
role_id: role.id,
|
|
3823
|
+
session_credential: roleProbeSession,
|
|
3824
|
+
}),
|
|
3825
|
+
);
|
|
3826
|
+
expectStatus(initialRoleProbe, 200, 'Deletion default-role shape baseline');
|
|
3827
|
+
const roleProbeDrone = decodeAttachResponseEnvelope(initialRoleProbe.body).payload.drone;
|
|
3649
3828
|
const append = await environment.operations.append(
|
|
3650
3829
|
creator.credential,
|
|
3651
3830
|
cube,
|
|
@@ -3667,8 +3846,50 @@ export async function runAdapterConformance(
|
|
|
3667
3846
|
cube,
|
|
3668
3847
|
createProtocolEnvelope('delete-decision', { topic: 'cleanup', decision: 'delete' }),
|
|
3669
3848
|
), 201, 'Deletion fixture decision');
|
|
3849
|
+
const readDenialState = async (label: string): Promise<unknown> => {
|
|
3850
|
+
const decisions = await environment.operations.listDecisions(
|
|
3851
|
+
manager.credential,
|
|
3852
|
+
cube,
|
|
3853
|
+
createProtocolEnvelope(`delete-${label}-decision-readback`, {}),
|
|
3854
|
+
);
|
|
3855
|
+
expectStatus(decisions, 200, `${label} deletion decision readback`);
|
|
3856
|
+
const active = decodeDecisionsResultEnvelope(decisions.body).payload.decisions;
|
|
3857
|
+
const log = await environment.operations.read(
|
|
3858
|
+
manager.credential,
|
|
3859
|
+
cube,
|
|
3860
|
+
createProtocolEnvelope(`delete-${label}-log-readback`, { cursor: null, limit: 500 }),
|
|
3861
|
+
);
|
|
3862
|
+
expectStatus(log, 200, `${label} deletion log readback`);
|
|
3863
|
+
const logPayload = decodeReadLogResultEnvelope(log.body).payload;
|
|
3864
|
+
const roster = await environment.operations.listDrones(manager.credential, cube);
|
|
3865
|
+
expectStatus(roster, 200, `${label} deletion roster readback`);
|
|
3866
|
+
const roleReadback = await environment.operations.attach(
|
|
3867
|
+
manager.credential,
|
|
3868
|
+
createProtocolEnvelope(`delete-${label}-role-readback`, {
|
|
3869
|
+
cube_id: cube.id,
|
|
3870
|
+
role_id: role.id,
|
|
3871
|
+
prior_drone_id: roleProbeDrone.id,
|
|
3872
|
+
session_credential: roleProbeSession,
|
|
3873
|
+
}),
|
|
3874
|
+
);
|
|
3875
|
+
expectStatus(roleReadback, 200, `${label} deletion role readback`);
|
|
3876
|
+
const rolePayload = decodeAttachResponseEnvelope(roleReadback.body).payload.role;
|
|
3877
|
+
return {
|
|
3878
|
+
decisions: active,
|
|
3879
|
+
entries: logPayload.entries,
|
|
3880
|
+
claims: logPayload.claims,
|
|
3881
|
+
drone: listedDrone(roster, drone.id),
|
|
3882
|
+
role: rolePayload,
|
|
3883
|
+
};
|
|
3884
|
+
};
|
|
3885
|
+
const denialBaseline = await readDenialState('baseline');
|
|
3886
|
+
const assertDenialState = async (label: string): Promise<void> => {
|
|
3887
|
+
invariant(
|
|
3888
|
+
same(await readDenialState(label), denialBaseline),
|
|
3889
|
+
`${label} cube deletion denial mutated protocol-visible cube state.`,
|
|
3890
|
+
);
|
|
3891
|
+
};
|
|
3670
3892
|
|
|
3671
|
-
const beforeDenied = await environment.admin.inspectCubeManagementState(cube);
|
|
3672
3893
|
for (const [kind, credential] of [
|
|
3673
3894
|
['read', reader.credential],
|
|
3674
3895
|
['write', writer.credential],
|
|
@@ -3684,10 +3905,7 @@ export async function runAdapterConformance(
|
|
|
3684
3905
|
ErrorCode.ACCESS_DENIED,
|
|
3685
3906
|
`${kind} cube deletion`,
|
|
3686
3907
|
);
|
|
3687
|
-
|
|
3688
|
-
same(await environment.admin.inspectCubeManagementState(cube), beforeDenied),
|
|
3689
|
-
`${kind} cube deletion denial mutated the cube.`,
|
|
3690
|
-
);
|
|
3908
|
+
await assertDenialState(kind);
|
|
3691
3909
|
}
|
|
3692
3910
|
expectError(
|
|
3693
3911
|
await environment.operations.deleteCube(
|
|
@@ -3699,6 +3917,7 @@ export async function runAdapterConformance(
|
|
|
3699
3917
|
ErrorCode.NOT_FOUND,
|
|
3700
3918
|
'Never-authorized cube deletion',
|
|
3701
3919
|
);
|
|
3920
|
+
await assertDenialState('outsider');
|
|
3702
3921
|
|
|
3703
3922
|
const parentStream = await environment.operations.openStream(manager.credential, cube, null);
|
|
3704
3923
|
const droneStream = await environment.operations.openStream(droneCredential, cube, null);
|
|
@@ -3751,22 +3970,6 @@ export async function runAdapterConformance(
|
|
|
3751
3970
|
await streamReader.close();
|
|
3752
3971
|
}
|
|
3753
3972
|
|
|
3754
|
-
invariant(
|
|
3755
|
-
same(await environment.admin.inspectDeletedCube(cube), {
|
|
3756
|
-
cube_exists: false,
|
|
3757
|
-
role_count: 0,
|
|
3758
|
-
drone_count: 0,
|
|
3759
|
-
log_count: 0,
|
|
3760
|
-
claim_count: 0,
|
|
3761
|
-
decision_count: 0,
|
|
3762
|
-
grant_count: 0,
|
|
3763
|
-
cube_create_binding_count: 0,
|
|
3764
|
-
repository_association_count: 0,
|
|
3765
|
-
active_stream_count: 0,
|
|
3766
|
-
terminal_credential_count: 5,
|
|
3767
|
-
}),
|
|
3768
|
-
'Cube deletion did not atomically remove cube-owned state and preserve only terminal credentials.',
|
|
3769
|
-
);
|
|
3770
3973
|
for (const [kind, credential] of [
|
|
3771
3974
|
['creator', creator.credential],
|
|
3772
3975
|
['manager', manager.credential],
|
|
@@ -3815,66 +4018,28 @@ export async function runAdapterConformance(
|
|
|
3815
4018
|
ErrorCode.NOT_FOUND,
|
|
3816
4019
|
'Never-authorized post-delete request',
|
|
3817
4020
|
);
|
|
3818
|
-
await environment.
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
['manager', manager.credential],
|
|
3822
|
-
['reader', reader.credential],
|
|
3823
|
-
['writer', writer.credential],
|
|
3824
|
-
['drone', droneCredential],
|
|
3825
|
-
] as const) {
|
|
3826
|
-
expectError(
|
|
3827
|
-
await environment.operations.read(
|
|
3828
|
-
credential,
|
|
3829
|
-
cube,
|
|
3830
|
-
createProtocolEnvelope(`delete-${kind}-after-restart`, { cursor: null, limit: 1 }),
|
|
3831
|
-
),
|
|
3832
|
-
PROTOCOL_HTTP_CONTRACT.cube_deleted_status,
|
|
3833
|
-
ErrorCode.CUBE_DELETED,
|
|
3834
|
-
`${kind} post-restart deleted-cube request`,
|
|
3835
|
-
);
|
|
3836
|
-
expectError(
|
|
3837
|
-
await environment.operations.deleteCube(
|
|
3838
|
-
credential,
|
|
3839
|
-
cube,
|
|
3840
|
-
createProtocolEnvelope(`delete-${kind}-repeat-after-restart`, {}),
|
|
3841
|
-
),
|
|
3842
|
-
PROTOCOL_HTTP_CONTRACT.cube_deleted_status,
|
|
3843
|
-
ErrorCode.CUBE_DELETED,
|
|
3844
|
-
`${kind} post-restart DELETE`,
|
|
3845
|
-
);
|
|
3846
|
-
}
|
|
3847
|
-
expectError(
|
|
3848
|
-
await environment.operations.deleteCube(
|
|
3849
|
-
outsider.credential,
|
|
3850
|
-
cube,
|
|
3851
|
-
createProtocolEnvelope('delete-outsider-hidden-repeat-after-restart', {}),
|
|
3852
|
-
),
|
|
3853
|
-
404,
|
|
3854
|
-
ErrorCode.NOT_FOUND,
|
|
3855
|
-
'Never-authorized post-restart DELETE',
|
|
4021
|
+
const recreatedResponse = await environment.operations.createCube(
|
|
4022
|
+
creator.credential,
|
|
4023
|
+
createProtocolEnvelope('delete-cube-recreate', deleteCubeRequest),
|
|
3856
4024
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
),
|
|
3863
|
-
404,
|
|
3864
|
-
ErrorCode.NOT_FOUND,
|
|
3865
|
-
'Never-authorized post-restart request',
|
|
4025
|
+
expectStatus(recreatedResponse, 201, 'Post-delete cube recreation');
|
|
4026
|
+
const recreated = decodeCreateCubeResponseEnvelope(recreatedResponse.body).payload;
|
|
4027
|
+
invariant(
|
|
4028
|
+
recreated.result === 'created' && recreated.cube_id !== cube.id,
|
|
4029
|
+
'Post-delete recreation resolved retained cube-owned creation state.',
|
|
3866
4030
|
);
|
|
3867
4031
|
return {
|
|
3868
4032
|
deletion_status: 200,
|
|
3869
|
-
cascade_complete: true,
|
|
3870
4033
|
terminal_stream_error: ErrorCode.CUBE_DELETED,
|
|
3871
4034
|
terminal_http_status: 410,
|
|
3872
|
-
terminal_state_durable_after_restart: true,
|
|
3873
4035
|
unknown_hidden_status: 404,
|
|
3874
4036
|
non_member_manager: true,
|
|
4037
|
+
recreation_created_new_cube: true,
|
|
3875
4038
|
};
|
|
3876
4039
|
});
|
|
3877
4040
|
|
|
3878
|
-
const normalizedTranscript = results
|
|
4041
|
+
const normalizedTranscript = results
|
|
4042
|
+
.filter((result) => !result.skipped)
|
|
4043
|
+
.map(({ id, observations }) => ({ id, observations }));
|
|
3879
4044
|
return { ok: results.every((result) => result.ok), results, normalizedTranscript };
|
|
3880
4045
|
}
|