borgmcp-shared 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +4 -1
- package/README.md +37 -8
- package/RELEASES.md +2 -3
- package/dist/conformance/adapter.d.ts +6 -101
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +546 -281
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +5 -42
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +16 -52
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +2 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +2 -2
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/errors.d.ts +0 -14
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +0 -8
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +0 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +1 -2
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +11 -0
- package/docs/enrollment.md +6 -9
- package/docs/releases/1.1.0.md +19 -0
- package/docs/releases/1.2.0.md +20 -0
- package/docs/releasing.md +13 -23
- package/package.json +1 -2
- package/src/conformance/adapter.ts +822 -640
- package/src/conformance/index.ts +24 -75
- package/src/protocol/contract.ts +2 -2
- package/src/protocol/errors.ts +0 -17
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +1 -3
- package/docs/release-records.json +0 -261
|
@@ -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,56 @@ 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
|
+
expectError(
|
|
818
|
+
await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-case-variant-duplicate', {
|
|
819
|
+
...cubeRequest,
|
|
820
|
+
retry_key: '00000000-0000-4000-8000-000000000214',
|
|
821
|
+
name: 'repository one',
|
|
822
|
+
repository: {
|
|
823
|
+
kind: 'origin' as const,
|
|
824
|
+
value: 'https://github.com/Byte-Ventures/repository-one-case-variant',
|
|
825
|
+
},
|
|
826
|
+
})),
|
|
827
|
+
409,
|
|
828
|
+
ErrorCode.INVALID_INPUT,
|
|
829
|
+
'Same-owner case-variant cube name',
|
|
830
|
+
);
|
|
831
|
+
const createdRoleProbes = new Map<string, {
|
|
832
|
+
role: unknown;
|
|
833
|
+
droneId: string;
|
|
834
|
+
sessionCredential: string;
|
|
835
|
+
}>();
|
|
836
|
+
for (const [kind, roleId, roleClass, isHumanSeat, marker] of [
|
|
837
|
+
['human-seat', created.human_seat_role_id, 'queen', true, 'H'],
|
|
838
|
+
['default-worker', created.default_worker_role_id, 'worker', false, 'D'],
|
|
839
|
+
] as const) {
|
|
840
|
+
const sessionCredential = marker.repeat(43);
|
|
841
|
+
const attached = await environment.operations.attach(
|
|
842
|
+
ownerCredential,
|
|
843
|
+
createProtocolEnvelope(`cube-create-${kind}-probe`, {
|
|
844
|
+
cube_id: created.cube_id,
|
|
845
|
+
role_id: roleId,
|
|
846
|
+
session_credential: sessionCredential,
|
|
847
|
+
}),
|
|
848
|
+
);
|
|
849
|
+
expectStatus(attached, 200, `Created ${kind} role probe`);
|
|
850
|
+
const attachedPayload = decodeAttachResponseEnvelope(attached.body).payload;
|
|
851
|
+
const attachedRole = attachedPayload.role;
|
|
852
|
+
invariant(
|
|
853
|
+
attachedRole.id === roleId &&
|
|
854
|
+
attachedRole.role_class === roleClass &&
|
|
855
|
+
attachedRole.is_human_seat === isHumanSeat,
|
|
856
|
+
`Created ${kind} role identity did not match its protocol role.`,
|
|
857
|
+
);
|
|
858
|
+
createdRoleProbes.set(kind, {
|
|
859
|
+
role: attachedRole,
|
|
860
|
+
droneId: attachedPayload.drone.id,
|
|
861
|
+
sessionCredential,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
904
864
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
905
865
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
906
866
|
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
@@ -909,13 +869,11 @@ export async function runAdapterConformance(
|
|
|
909
869
|
same({ ...retriedCreate, result: 'created' }, created),
|
|
910
870
|
'Exact cube-create retry returned different authoritative fields.',
|
|
911
871
|
);
|
|
912
|
-
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
913
872
|
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find(
|
|
914
873
|
(vector) => vector.expected.outcome === 'resolved_response' &&
|
|
915
874
|
vector.retry.working_repo_name !== vector.initial.working_repo_name,
|
|
916
875
|
);
|
|
917
876
|
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
918
|
-
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
919
877
|
const displayRetryResponse = await environment.operations.createCube(
|
|
920
878
|
ownerCredential,
|
|
921
879
|
createProtocolEnvelope('cube-display-retry', {
|
|
@@ -928,16 +886,9 @@ export async function runAdapterConformance(
|
|
|
928
886
|
same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate),
|
|
929
887
|
'Changed repository display retry did not return stored authoritative display.',
|
|
930
888
|
);
|
|
931
|
-
assertStateDelta(
|
|
932
|
-
beforeDisplayRetry,
|
|
933
|
-
await environment.admin.observeAuthorityState(),
|
|
934
|
-
{},
|
|
935
|
-
'Changed repository display retry',
|
|
936
|
-
);
|
|
937
889
|
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
938
890
|
if (vector.expected.outcome !== 'retry_tuple_mismatch') continue;
|
|
939
891
|
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
940
|
-
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
941
892
|
expectSecretFreeError(
|
|
942
893
|
await environment.operations.createCube(
|
|
943
894
|
ownerCredential,
|
|
@@ -948,18 +899,32 @@ export async function runAdapterConformance(
|
|
|
948
899
|
`Cube-create ${vector.name}`,
|
|
949
900
|
[cubeRequest.retry_key],
|
|
950
901
|
);
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
902
|
+
}
|
|
903
|
+
for (const [kind, roleId] of [
|
|
904
|
+
['human-seat', created.human_seat_role_id],
|
|
905
|
+
['default-worker', created.default_worker_role_id],
|
|
906
|
+
] as const) {
|
|
907
|
+
const initialProbe = createdRoleProbes.get(kind);
|
|
908
|
+
invariant(initialProbe !== undefined, `Missing initial ${kind} role probe.`);
|
|
909
|
+
const attached = await environment.operations.attach(
|
|
910
|
+
ownerCredential,
|
|
911
|
+
createProtocolEnvelope(`cube-create-${kind}-post-refusal-probe`, {
|
|
912
|
+
cube_id: created.cube_id,
|
|
913
|
+
role_id: roleId,
|
|
914
|
+
session_credential: initialProbe.sessionCredential,
|
|
915
|
+
prior_drone_id: initialProbe.droneId,
|
|
916
|
+
}),
|
|
917
|
+
);
|
|
918
|
+
expectStatus(attached, 200, `Post-refusal ${kind} role probe`);
|
|
919
|
+
invariant(
|
|
920
|
+
same(decodeAttachResponseEnvelope(attached.body).payload.role, initialProbe.role),
|
|
921
|
+
`Cube-create retry refusal changed the full ${kind} role shape.`,
|
|
956
922
|
);
|
|
957
923
|
}
|
|
958
924
|
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
959
925
|
(vector) => vector.expected.outcome === 'resolved',
|
|
960
926
|
);
|
|
961
927
|
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
962
|
-
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
963
928
|
const associationResolveResponse = await environment.operations.createCube(
|
|
964
929
|
ownerCredential,
|
|
965
930
|
createProtocolEnvelope('cube-association-resolve', {
|
|
@@ -974,12 +939,6 @@ export async function runAdapterConformance(
|
|
|
974
939
|
same({ ...associationResolved, result: 'created' }, created),
|
|
975
940
|
'Associated repository did not return stored authoritative fields.',
|
|
976
941
|
);
|
|
977
|
-
assertStateDelta(
|
|
978
|
-
beforeAssociationResolve,
|
|
979
|
-
await environment.admin.observeAuthorityState(),
|
|
980
|
-
sameAssociationVector.expected.authority_state_delta,
|
|
981
|
-
'Fresh retry for associated repository',
|
|
982
|
-
);
|
|
983
942
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
984
943
|
const crossClientRequest = {
|
|
985
944
|
...cubeRequest,
|
|
@@ -989,7 +948,6 @@ export async function runAdapterConformance(
|
|
|
989
948
|
value: '00000000-0000-4000-8000-000000000216',
|
|
990
949
|
},
|
|
991
950
|
};
|
|
992
|
-
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
993
951
|
const crossClientResponse = await environment.operations.createCube(
|
|
994
952
|
ordinaryCredential,
|
|
995
953
|
createProtocolEnvelope('cube-cross-client', crossClientRequest),
|
|
@@ -997,17 +955,27 @@ export async function runAdapterConformance(
|
|
|
997
955
|
expectStatus(crossClientResponse, 201, 'Cross-client cube create with reused retry key');
|
|
998
956
|
const crossClientCreated = decodeCreateCubeResponseEnvelope(crossClientResponse.body).payload;
|
|
999
957
|
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
|
-
|
|
958
|
+
for (const [kind, roleId, originalRoleId, marker] of [
|
|
959
|
+
['human-seat', crossClientCreated.human_seat_role_id, created.human_seat_role_id, 'V'],
|
|
960
|
+
['default-worker', crossClientCreated.default_worker_role_id, created.default_worker_role_id, 'W'],
|
|
961
|
+
] as const) {
|
|
962
|
+
const originalProbe = createdRoleProbes.get(kind);
|
|
963
|
+
invariant(originalProbe !== undefined, `Missing original ${kind} role probe.`);
|
|
964
|
+
const attached = await environment.operations.attach(
|
|
965
|
+
ordinaryCredential,
|
|
966
|
+
createProtocolEnvelope(`cube-cross-client-${kind}-probe`, {
|
|
967
|
+
cube_id: crossClientCreated.cube_id,
|
|
968
|
+
role_id: roleId,
|
|
969
|
+
session_credential: marker.repeat(43),
|
|
970
|
+
}),
|
|
971
|
+
);
|
|
972
|
+
expectStatus(attached, 200, `Cross-client ${kind} role probe`);
|
|
973
|
+
const attachedRole = decodeAttachResponseEnvelope(attached.body).payload.role;
|
|
974
|
+
invariant(
|
|
975
|
+
same({ ...attachedRole, id: originalRoleId }, originalProbe.role),
|
|
976
|
+
`Cross-client ${kind} role did not match the full template role shape.`,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
1011
979
|
const crossClientRetry = await environment.operations.createCube(
|
|
1012
980
|
ordinaryCredential,
|
|
1013
981
|
createProtocolEnvelope('cube-cross-client-retry', crossClientRequest),
|
|
@@ -1025,12 +993,10 @@ export async function runAdapterConformance(
|
|
|
1025
993
|
),
|
|
1026
994
|
'Exact cross-client cube-create retry returned different authoritative fields.',
|
|
1027
995
|
);
|
|
1028
|
-
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
1029
996
|
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
1030
997
|
(vector) => vector.expected.outcome === 'created',
|
|
1031
998
|
);
|
|
1032
999
|
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
1033
|
-
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
1034
1000
|
const secondCreatedResponse = await environment.operations.createCube(
|
|
1035
1001
|
ownerCredential,
|
|
1036
1002
|
createProtocolEnvelope('cube-create-second', differentAssociationVector.request),
|
|
@@ -1038,14 +1004,7 @@ export async function runAdapterConformance(
|
|
|
1038
1004
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
1039
1005
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
1040
1006
|
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
1007
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
|
1048
|
-
const beforeRevokedCreate = await environment.admin.observeAuthorityState();
|
|
1049
1008
|
expectSecretFreeError(
|
|
1050
1009
|
await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-revoked', {
|
|
1051
1010
|
...cubeRequest,
|
|
@@ -1056,8 +1015,16 @@ export async function runAdapterConformance(
|
|
|
1056
1015
|
'Revoked owner cube create',
|
|
1057
1016
|
['00000000-0000-4000-8000-000000000215'],
|
|
1058
1017
|
);
|
|
1059
|
-
assertStateDelta(beforeRevokedCreate, await environment.admin.observeAuthorityState(), {}, 'Revoked owner cube create');
|
|
1060
1018
|
|
|
1019
|
+
return {
|
|
1020
|
+
enrollment_status: 201,
|
|
1021
|
+
exact_retry_status: 201,
|
|
1022
|
+
mismatched_retry: ErrorCode.AUTH_INVALID,
|
|
1023
|
+
response_secret_free: true,
|
|
1024
|
+
};
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
try {
|
|
1061
1028
|
await environment.admin.reset();
|
|
1062
1029
|
principalA = await environment.admin.createPrincipal('principal-a');
|
|
1063
1030
|
principalB = await environment.admin.createPrincipal('principal-b');
|
|
@@ -1069,50 +1036,23 @@ export async function runAdapterConformance(
|
|
|
1069
1036
|
const invitationB = await environment.admin.issueSingleUseInvitation(principalB, 'client');
|
|
1070
1037
|
credentialA = 'A'.repeat(43);
|
|
1071
1038
|
credentialB = 'E'.repeat(43);
|
|
1072
|
-
const
|
|
1039
|
+
const enrolledAResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-a1', {
|
|
1073
1040
|
invitation: invitationA,
|
|
1074
1041
|
retry_key: '00000000-0000-4000-8000-000000000201',
|
|
1075
1042
|
client_credential: credentialA,
|
|
1076
1043
|
client_name: 'conformance-a',
|
|
1077
|
-
});
|
|
1078
|
-
const
|
|
1044
|
+
}));
|
|
1045
|
+
const enrolledBResponse = await environment.operations.enroll(createProtocolEnvelope('enroll-b1', {
|
|
1079
1046
|
invitation: invitationB,
|
|
1080
1047
|
retry_key: '00000000-0000-4000-8000-000000000202',
|
|
1081
1048
|
client_credential: credentialB,
|
|
1082
1049
|
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
|
-
});
|
|
1050
|
+
}));
|
|
1051
|
+
expectStatus(enrolledAResponse, 201, 'Shared principal A enrollment');
|
|
1052
|
+
expectStatus(enrolledBResponse, 201, 'Shared principal B enrollment');
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
prerequisiteFailures.set('shared.enrolled-principals', error instanceof Error ? error.message : String(error));
|
|
1055
|
+
}
|
|
1116
1056
|
|
|
1117
1057
|
await record('repository.explicit-association', async () => {
|
|
1118
1058
|
const repository = {
|
|
@@ -1127,7 +1067,21 @@ export async function runAdapterConformance(
|
|
|
1127
1067
|
working_repo_name: 'legacy-repository',
|
|
1128
1068
|
repository,
|
|
1129
1069
|
};
|
|
1130
|
-
const
|
|
1070
|
+
const alternateResolveRequest = {
|
|
1071
|
+
working_repo_name: 'other-repository',
|
|
1072
|
+
repository: {
|
|
1073
|
+
kind: 'origin' as const,
|
|
1074
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
const resolve = async (requestId: string, request: typeof resolveRequest) => {
|
|
1078
|
+
const response = await environment.operations.resolveRepositoryCube(
|
|
1079
|
+
credentialA,
|
|
1080
|
+
createProtocolEnvelope(requestId, request),
|
|
1081
|
+
);
|
|
1082
|
+
expectStatus(response, 200, requestId);
|
|
1083
|
+
return decodeResolveRepositoryCubeResponseEnvelope(response.body).payload;
|
|
1084
|
+
};
|
|
1131
1085
|
const noneResponse = await environment.operations.resolveRepositoryCube(
|
|
1132
1086
|
credentialA,
|
|
1133
1087
|
createProtocolEnvelope('repository-resolve-none', resolveRequest),
|
|
@@ -1137,10 +1091,11 @@ export async function runAdapterConformance(
|
|
|
1137
1091
|
same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }),
|
|
1138
1092
|
'Unassociated repository did not return explicit none.',
|
|
1139
1093
|
);
|
|
1140
|
-
|
|
1141
|
-
|
|
1094
|
+
invariant(
|
|
1095
|
+
same(await resolve('repository-resolve-none-readback', resolveRequest), { result: 'none' }),
|
|
1096
|
+
'Read-only repository resolution created an association.',
|
|
1097
|
+
);
|
|
1142
1098
|
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
1143
|
-
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
1144
1099
|
expectSecretFreeError(
|
|
1145
1100
|
await environment.operations.associateRepositoryCube(
|
|
1146
1101
|
credentialB,
|
|
@@ -1151,9 +1106,6 @@ export async function runAdapterConformance(
|
|
|
1151
1106
|
'Inaccessible repository cube association',
|
|
1152
1107
|
[associationRequest.cube_id, repository.value],
|
|
1153
1108
|
);
|
|
1154
|
-
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
1155
|
-
|
|
1156
|
-
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
1157
1109
|
const associatedResponse = await environment.operations.associateRepositoryCube(
|
|
1158
1110
|
credentialA,
|
|
1159
1111
|
createProtocolEnvelope('repository-associate', associationRequest),
|
|
@@ -1172,14 +1124,6 @@ export async function runAdapterConformance(
|
|
|
1172
1124
|
access: prepared.access,
|
|
1173
1125
|
};
|
|
1174
1126
|
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
1127
|
const resolvedResponse = await environment.operations.resolveRepositoryCube(
|
|
1184
1128
|
credentialA,
|
|
1185
1129
|
createProtocolEnvelope('repository-resolve', {
|
|
@@ -1192,9 +1136,6 @@ export async function runAdapterConformance(
|
|
|
1192
1136
|
same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative),
|
|
1193
1137
|
'Associated repository did not return stored authoritative display.',
|
|
1194
1138
|
);
|
|
1195
|
-
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
1196
|
-
|
|
1197
|
-
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
1198
1139
|
const retryResponse = await environment.operations.associateRepositoryCube(
|
|
1199
1140
|
credentialA,
|
|
1200
1141
|
createProtocolEnvelope('repository-associate-retry', associationRequest),
|
|
@@ -1204,15 +1145,12 @@ export async function runAdapterConformance(
|
|
|
1204
1145
|
same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative),
|
|
1205
1146
|
'Exact repository association retry changed authoritative fields.',
|
|
1206
1147
|
);
|
|
1207
|
-
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
1208
|
-
|
|
1209
1148
|
const cubeC = await environment.admin.createCube('cube-c');
|
|
1210
1149
|
await environment.admin.grantCube(principalA, cubeC);
|
|
1211
1150
|
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
1212
1151
|
name: 'Other Legacy Cube',
|
|
1213
1152
|
template: 'starter',
|
|
1214
1153
|
});
|
|
1215
|
-
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
1216
1154
|
expectSecretFreeError(
|
|
1217
1155
|
await environment.operations.associateRepositoryCube(
|
|
1218
1156
|
credentialA,
|
|
@@ -1226,43 +1164,40 @@ export async function runAdapterConformance(
|
|
|
1226
1164
|
'Repository-to-other-cube conflict',
|
|
1227
1165
|
[cubeA.id, cubeC.id, repository.value],
|
|
1228
1166
|
);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
{},
|
|
1233
|
-
'Repository-to-other-cube conflict',
|
|
1167
|
+
invariant(
|
|
1168
|
+
same(await resolve('repository-resolve-after-repository-conflict', resolveRequest), authoritative),
|
|
1169
|
+
'Repository conflict changed the authoritative original binding.',
|
|
1234
1170
|
);
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
await environment.operations.associateRepositoryCube(
|
|
1239
|
-
credentialA,
|
|
1240
|
-
createProtocolEnvelope('repository-associate-cube-conflict', {
|
|
1241
|
-
cube_id: cubeA.id,
|
|
1242
|
-
working_repo_name: 'other-repository',
|
|
1243
|
-
repository: {
|
|
1244
|
-
kind: 'origin',
|
|
1245
|
-
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1246
|
-
},
|
|
1247
|
-
}),
|
|
1248
|
-
),
|
|
1249
|
-
409,
|
|
1250
|
-
ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
1251
|
-
'Cube-to-other-repository conflict',
|
|
1252
|
-
[cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository'],
|
|
1171
|
+
invariant(
|
|
1172
|
+
same(await resolve('repository-resolve-alternate-after-repository-conflict', alternateResolveRequest), { result: 'none' }),
|
|
1173
|
+
'Repository conflict created an alternate binding.',
|
|
1253
1174
|
);
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1175
|
+
const alternateAssociationResponse = await environment.operations.associateRepositoryCube(
|
|
1176
|
+
credentialA,
|
|
1177
|
+
createProtocolEnvelope('repository-associate-second-repository', {
|
|
1178
|
+
cube_id: cubeA.id,
|
|
1179
|
+
...alternateResolveRequest,
|
|
1180
|
+
}),
|
|
1181
|
+
);
|
|
1182
|
+
expectStatus(alternateAssociationResponse, 200, 'Second repository association');
|
|
1183
|
+
const alternateAuthoritative = decodeAssociateRepositoryCubeResponseEnvelope(
|
|
1184
|
+
alternateAssociationResponse.body,
|
|
1185
|
+
).payload;
|
|
1186
|
+
invariant(
|
|
1187
|
+
same(await resolve('repository-resolve-after-second-association', resolveRequest), authoritative),
|
|
1188
|
+
'Second repository association changed the authoritative original binding.',
|
|
1189
|
+
);
|
|
1190
|
+
invariant(
|
|
1191
|
+
same(
|
|
1192
|
+
await resolve('repository-resolve-alternate-after-second-association', alternateResolveRequest),
|
|
1193
|
+
alternateAuthoritative,
|
|
1194
|
+
),
|
|
1195
|
+
'Second repository association was not observable.',
|
|
1259
1196
|
);
|
|
1260
|
-
|
|
1261
1197
|
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
1262
1198
|
name: 'Other Client Legacy Cube',
|
|
1263
1199
|
template: 'starter',
|
|
1264
1200
|
});
|
|
1265
|
-
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
1266
1201
|
const crossClientResolve = await environment.operations.resolveRepositoryCube(
|
|
1267
1202
|
credentialB,
|
|
1268
1203
|
createProtocolEnvelope('repository-resolve-cross-client', resolveRequest),
|
|
@@ -1272,13 +1207,6 @@ export async function runAdapterConformance(
|
|
|
1272
1207
|
same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }),
|
|
1273
1208
|
'Another client repository binding was exposed by resolution.',
|
|
1274
1209
|
);
|
|
1275
|
-
assertStateDelta(
|
|
1276
|
-
beforeCrossClientResolve,
|
|
1277
|
-
await environment.admin.observeAuthorityState(),
|
|
1278
|
-
{},
|
|
1279
|
-
'Cross-client repository resolution',
|
|
1280
|
-
);
|
|
1281
|
-
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
1282
1210
|
const crossClientAssociation = await environment.operations.associateRepositoryCube(
|
|
1283
1211
|
credentialB,
|
|
1284
1212
|
createProtocolEnvelope('repository-associate-cross-client', {
|
|
@@ -1295,15 +1223,7 @@ export async function runAdapterConformance(
|
|
|
1295
1223
|
crossClientAssociated.repository.value === repository.value,
|
|
1296
1224
|
'Another client binding changed cross-client association identity.',
|
|
1297
1225
|
);
|
|
1298
|
-
assertStateDelta(
|
|
1299
|
-
beforeCrossClientAssociation,
|
|
1300
|
-
await environment.admin.observeAuthorityState(),
|
|
1301
|
-
{ repository_associations: 1 },
|
|
1302
|
-
'Cross-client repository association',
|
|
1303
|
-
);
|
|
1304
|
-
|
|
1305
1226
|
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
1306
|
-
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
1307
1227
|
const hiddenResolve = await environment.operations.resolveRepositoryCube(
|
|
1308
1228
|
credentialA,
|
|
1309
1229
|
createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest),
|
|
@@ -1313,13 +1233,6 @@ export async function runAdapterConformance(
|
|
|
1313
1233
|
same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }),
|
|
1314
1234
|
'Inaccessible repository binding was exposed by read-only resolution.',
|
|
1315
1235
|
);
|
|
1316
|
-
assertStateDelta(
|
|
1317
|
-
beforeHiddenResolve,
|
|
1318
|
-
await environment.admin.observeAuthorityState(),
|
|
1319
|
-
{},
|
|
1320
|
-
'Inaccessible repository binding resolution',
|
|
1321
|
-
);
|
|
1322
|
-
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
1323
1236
|
expectSecretFreeError(
|
|
1324
1237
|
await environment.operations.associateRepositoryCube(
|
|
1325
1238
|
credentialA,
|
|
@@ -1333,19 +1246,30 @@ export async function runAdapterConformance(
|
|
|
1333
1246
|
'Inaccessible existing repository binding',
|
|
1334
1247
|
[cubeA.id, cubeC.id, repository.value],
|
|
1335
1248
|
);
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
'Inaccessible existing repository binding',
|
|
1249
|
+
const hiddenAfterRefusal = await resolve('repository-resolve-after-inaccessible-refusal', resolveRequest);
|
|
1250
|
+
const alternateAfterRefusal = await resolve(
|
|
1251
|
+
'repository-resolve-alternate-after-inaccessible-refusal',
|
|
1252
|
+
alternateResolveRequest,
|
|
1341
1253
|
);
|
|
1342
1254
|
await environment.admin.grantCube(principalA, cubeA);
|
|
1255
|
+
invariant(
|
|
1256
|
+
same(hiddenAfterRefusal, { result: 'none' }),
|
|
1257
|
+
'Inaccessible-binding refusal changed the hidden authoritative binding.',
|
|
1258
|
+
);
|
|
1259
|
+
invariant(
|
|
1260
|
+
same(alternateAfterRefusal, { result: 'none' }),
|
|
1261
|
+
'Inaccessible-binding refusal created an alternate binding.',
|
|
1262
|
+
);
|
|
1263
|
+
invariant(
|
|
1264
|
+
same(await resolve('repository-resolve-restored-after-inaccessible-refusal', resolveRequest), authoritative),
|
|
1265
|
+
'Inaccessible-binding refusal did not preserve the original binding.',
|
|
1266
|
+
);
|
|
1343
1267
|
return {
|
|
1344
1268
|
none: true,
|
|
1345
1269
|
resolved: true,
|
|
1346
1270
|
idempotent: true,
|
|
1347
1271
|
repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
1348
|
-
|
|
1272
|
+
multiple_repositories_per_cube: true,
|
|
1349
1273
|
inaccessible: ErrorCode.ACCESS_DENIED,
|
|
1350
1274
|
inaccessible_binding_hidden: true,
|
|
1351
1275
|
cross_client_binding_hidden: true,
|
|
@@ -1887,7 +1811,6 @@ export async function runAdapterConformance(
|
|
|
1887
1811
|
});
|
|
1888
1812
|
const recipient = await environment.admin.createDrone(principalA, cube, role);
|
|
1889
1813
|
const message = 'START NOW exact slice';
|
|
1890
|
-
const before = await environment.admin.observeAuthorityState();
|
|
1891
1814
|
expectError(
|
|
1892
1815
|
await environment.operations.append(
|
|
1893
1816
|
credentialA,
|
|
@@ -1901,8 +1824,6 @@ export async function runAdapterConformance(
|
|
|
1901
1824
|
ErrorCode.INVALID_INPUT,
|
|
1902
1825
|
'Omitted mandatory addressing',
|
|
1903
1826
|
);
|
|
1904
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Mandatory-addressing refusal');
|
|
1905
|
-
|
|
1906
1827
|
const broadcast = await environment.operations.append(
|
|
1907
1828
|
credentialA,
|
|
1908
1829
|
cube,
|
|
@@ -1940,6 +1861,17 @@ export async function runAdapterConformance(
|
|
|
1940
1861
|
directedResult.entry.recipient_drone_ids?.length === 1,
|
|
1941
1862
|
'Classification changed or fell open the explicit recipient set.',
|
|
1942
1863
|
);
|
|
1864
|
+
const readback = await environment.operations.read(
|
|
1865
|
+
credentialA,
|
|
1866
|
+
cube,
|
|
1867
|
+
createProtocolEnvelope('mandatory-addressing-readback', { cursor: null, limit: 10 }),
|
|
1868
|
+
);
|
|
1869
|
+
expectStatus(readback, 200, 'Mandatory-addressing persistence readback');
|
|
1870
|
+
const persistedIds = decodeReadLogResultEnvelope(readback.body).payload.entries.map((entry) => entry.id);
|
|
1871
|
+
invariant(
|
|
1872
|
+
same(persistedIds, [broadcastResult.entry.id, directedResult.entry.id]),
|
|
1873
|
+
'Rejected mandatory-addressing append was persisted.',
|
|
1874
|
+
);
|
|
1943
1875
|
return { refused_status: 400, code: ErrorCode.INVALID_INPUT, persisted: false, broadcast: true, union: true, no_fall_open: true };
|
|
1944
1876
|
});
|
|
1945
1877
|
|
|
@@ -1961,6 +1893,7 @@ export async function runAdapterConformance(
|
|
|
1961
1893
|
'22222222-0000-4000-8000-000000000002',
|
|
1962
1894
|
];
|
|
1963
1895
|
const entries: EnrichedStreamEntry[] = [];
|
|
1896
|
+
const idSeeds: Array<{ current_id: string; query_id: string }> = [];
|
|
1964
1897
|
for (const [index, id] of ids.entries()) {
|
|
1965
1898
|
const response = await environment.operations.append(
|
|
1966
1899
|
credentialA,
|
|
@@ -1973,9 +1906,10 @@ export async function runAdapterConformance(
|
|
|
1973
1906
|
);
|
|
1974
1907
|
expectStatus(response, 201, `Entry-query fixture append ${index}`);
|
|
1975
1908
|
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
1976
|
-
|
|
1909
|
+
idSeeds.push({ current_id: entry.id, query_id: id });
|
|
1977
1910
|
entries.push({ ...entry, id });
|
|
1978
1911
|
}
|
|
1912
|
+
await environment.admin.seedEntryQueryIds(cube, idSeeds);
|
|
1979
1913
|
|
|
1980
1914
|
expectStatus(
|
|
1981
1915
|
await environment.operations.ack(
|
|
@@ -1996,7 +1930,6 @@ export async function runAdapterConformance(
|
|
|
1996
1930
|
'Entry-query claim seed',
|
|
1997
1931
|
);
|
|
1998
1932
|
|
|
1999
|
-
const before = await environment.admin.observeAuthorityState();
|
|
2000
1933
|
for (const [requestId, selector, expectedId] of [
|
|
2001
1934
|
['entry-query-full', ids[1], ids[1]],
|
|
2002
1935
|
['entry-query-prefix', '11111111', ids[0]],
|
|
@@ -2030,7 +1963,6 @@ export async function runAdapterConformance(
|
|
|
2030
1963
|
ErrorCode.LOG_ENTRY_PREFIX_AMBIGUOUS,
|
|
2031
1964
|
'Ambiguous entry query',
|
|
2032
1965
|
);
|
|
2033
|
-
assertStateDelta(before, await environment.admin.observeAuthorityState(), {}, 'Entry query');
|
|
2034
1966
|
const acknowledgementStatus = decodeAckStatusResultEnvelope((await environment.operations.ackStatus(
|
|
2035
1967
|
credentialA,
|
|
2036
1968
|
cube,
|
|
@@ -2041,15 +1973,22 @@ export async function runAdapterConformance(
|
|
|
2041
1973
|
cube,
|
|
2042
1974
|
createProtocolEnvelope('entry-query-claim-status', { entry_id: ids[1] }),
|
|
2043
1975
|
)).body).payload;
|
|
1976
|
+
const acknowledgedRecipients = acknowledgementStatus.recipients.filter(
|
|
1977
|
+
(recipient) => recipient.acknowledged_at !== null,
|
|
1978
|
+
);
|
|
2044
1979
|
invariant(
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
1980
|
+
acknowledgedRecipients.length === 1 &&
|
|
1981
|
+
acknowledgedRecipients[0].drone_id === acknowledgedRecipient.id &&
|
|
1982
|
+
acknowledgementStatus.recipients.find(
|
|
1983
|
+
(recipient) => recipient.drone_id === claimingRecipient.id,
|
|
1984
|
+
)?.acknowledged_at === null,
|
|
1985
|
+
'Entry query changed the seeded acknowledgement set.',
|
|
2049
1986
|
);
|
|
2050
1987
|
invariant(
|
|
2051
|
-
claimStatus.claims.
|
|
2052
|
-
|
|
1988
|
+
claimStatus.claims.length === 1 &&
|
|
1989
|
+
claimStatus.claims[0].drone_id === claimingRecipient.id &&
|
|
1990
|
+
claimStatus.recipients.every((recipient) => recipient.acknowledged_at === null),
|
|
1991
|
+
'Entry query changed the seeded claim or acknowledgement set.',
|
|
2053
1992
|
);
|
|
2054
1993
|
const unreadResponse = await environment.operations.read(
|
|
2055
1994
|
credentialA,
|
|
@@ -2059,8 +1998,8 @@ export async function runAdapterConformance(
|
|
|
2059
1998
|
expectStatus(unreadResponse, 200, 'Entry-query unread read');
|
|
2060
1999
|
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2061
2000
|
invariant(
|
|
2062
|
-
unread.entries
|
|
2063
|
-
'Entry query consumed or advanced unread activity.',
|
|
2001
|
+
same(unread.entries, entries),
|
|
2002
|
+
'Entry query mutated, consumed, or advanced unread activity.',
|
|
2064
2003
|
);
|
|
2065
2004
|
return {
|
|
2066
2005
|
full_uuid: true,
|
|
@@ -2074,9 +2013,8 @@ export async function runAdapterConformance(
|
|
|
2074
2013
|
};
|
|
2075
2014
|
});
|
|
2076
2015
|
|
|
2077
|
-
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
2078
|
-
let readCursor: LogCursor | null = null;
|
|
2079
2016
|
await record('log.read-cursor-tuple', async () => {
|
|
2017
|
+
const entries: Array<{ id: string; created_at: string; message: string }> = [];
|
|
2080
2018
|
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
2081
2019
|
const response = await environment.operations.append(
|
|
2082
2020
|
credentialA,
|
|
@@ -2105,22 +2043,39 @@ export async function runAdapterConformance(
|
|
|
2105
2043
|
page.cursor.id === entries[1].id && page.cursor.created_at === entries[1].created_at,
|
|
2106
2044
|
'Read cursor does not equal the final delivered entry tuple.',
|
|
2107
2045
|
);
|
|
2108
|
-
readCursor = page.cursor;
|
|
2109
2046
|
return { messages: ['alpha', 'beta'], has_more: true, behind_by: 1, cursor_matches_last_entry: true };
|
|
2110
2047
|
});
|
|
2111
|
-
|
|
2112
|
-
let liveCursor: LogCursor | null = null;
|
|
2113
2048
|
await record('sse.replay-live-transition', async () => {
|
|
2114
|
-
|
|
2049
|
+
const principal = await environment.admin.createPrincipal('stream-transition');
|
|
2050
|
+
const cube = await environment.admin.createCube('stream-transition');
|
|
2051
|
+
await environment.admin.grantCube(principal, cube);
|
|
2052
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
2053
|
+
let readCursor!: LogCursor;
|
|
2054
|
+
for (const [index, message] of ['alpha', 'beta', 'gamma'].entries()) {
|
|
2055
|
+
const response = await environment.operations.append(
|
|
2056
|
+
credential,
|
|
2057
|
+
cube,
|
|
2058
|
+
createProtocolEnvelope(`stream-transition-${message}`, {
|
|
2059
|
+
post_id: `00000000-0000-4000-8000-${String(316 + index).padStart(12, '0')}`,
|
|
2060
|
+
to: 'broadcast',
|
|
2061
|
+
message,
|
|
2062
|
+
}),
|
|
2063
|
+
);
|
|
2064
|
+
expectStatus(response, 201, `Stream transition append ${message}`);
|
|
2065
|
+
if (message === 'beta') {
|
|
2066
|
+
const entry = decodeAppendLogResultEnvelope(response.body).payload.entry;
|
|
2067
|
+
readCursor = { id: entry.id, created_at: entry.created_at };
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2115
2070
|
const barrier = environment.admin.armReplayTransition();
|
|
2116
|
-
const openPromise = environment.operations.openStream(
|
|
2071
|
+
const openPromise = environment.operations.openStream(credential, cube, readCursor);
|
|
2117
2072
|
await within(barrier.reached, 'Replay transition boundary', streamDeadlineMs);
|
|
2118
2073
|
try {
|
|
2119
2074
|
const appendDelta = environment.operations.append(
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
createProtocolEnvelope('
|
|
2123
|
-
post_id: '00000000-0000-4000-8000-
|
|
2075
|
+
credential,
|
|
2076
|
+
cube,
|
|
2077
|
+
createProtocolEnvelope('stream-transition-delta', {
|
|
2078
|
+
post_id: '00000000-0000-4000-8000-000000000319',
|
|
2124
2079
|
to: 'broadcast',
|
|
2125
2080
|
message: 'delta',
|
|
2126
2081
|
}),
|
|
@@ -2145,10 +2100,10 @@ export async function runAdapterConformance(
|
|
|
2145
2100
|
const noDuplicate = reader.next();
|
|
2146
2101
|
await provePending(noDuplicate, 'Live stream after delta', pendingProbeMs);
|
|
2147
2102
|
const epsilonResponse = await environment.operations.append(
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
createProtocolEnvelope('
|
|
2151
|
-
post_id: '00000000-0000-4000-8000-
|
|
2103
|
+
credential,
|
|
2104
|
+
cube,
|
|
2105
|
+
createProtocolEnvelope('stream-transition-epsilon', {
|
|
2106
|
+
post_id: '00000000-0000-4000-8000-000000000320',
|
|
2152
2107
|
to: 'broadcast',
|
|
2153
2108
|
message: 'epsilon',
|
|
2154
2109
|
}),
|
|
@@ -2158,33 +2113,56 @@ export async function runAdapterConformance(
|
|
|
2158
2113
|
invariant(epsilon.entry.message === 'epsilon', 'Live stream duplicated or reordered an event.');
|
|
2159
2114
|
invariant(compareLogCursor(delta.cursor, epsilon.cursor) < 0, 'Live stream cursors are not ordered.');
|
|
2160
2115
|
invariant(new Set([replay.entry.id, delta.entry.id, epsilon.entry.id]).size === 3, 'Stream delivered a duplicate entry.');
|
|
2161
|
-
liveCursor = epsilon.cursor;
|
|
2162
2116
|
return { replay: ['gamma'], transition: 'bookmark', live: ['delta', 'epsilon'], duplicates: 0 };
|
|
2163
2117
|
} finally {
|
|
2164
2118
|
await reader.close();
|
|
2165
2119
|
}
|
|
2166
2120
|
});
|
|
2167
|
-
|
|
2168
2121
|
await record('cursor.explicit-expiry', async () => {
|
|
2169
|
-
|
|
2170
|
-
await environment.admin.
|
|
2122
|
+
const cube = await environment.admin.createCube('cursor-expiry');
|
|
2123
|
+
await environment.admin.grantCube(principalA, cube);
|
|
2124
|
+
const appended = await environment.operations.append(
|
|
2125
|
+
credentialA,
|
|
2126
|
+
cube,
|
|
2127
|
+
createProtocolEnvelope('cursor-expiry-entry', {
|
|
2128
|
+
post_id: '00000000-0000-4000-8000-000000000321',
|
|
2129
|
+
to: 'broadcast',
|
|
2130
|
+
message: 'expire this cursor',
|
|
2131
|
+
}),
|
|
2132
|
+
);
|
|
2133
|
+
expectStatus(appended, 201, 'Cursor expiry fixture append');
|
|
2134
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2135
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
2136
|
+
await environment.admin.expireCursor(cube, cursor);
|
|
2171
2137
|
const response = await environment.operations.read(
|
|
2172
2138
|
credentialA,
|
|
2173
|
-
|
|
2174
|
-
createProtocolEnvelope('read-expired', { cursor
|
|
2139
|
+
cube,
|
|
2140
|
+
createProtocolEnvelope('read-expired', { cursor, limit: 10 }),
|
|
2175
2141
|
);
|
|
2176
2142
|
expectError(response, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor read');
|
|
2177
|
-
const stream = await environment.operations.openStream(credentialA,
|
|
2143
|
+
const stream = await environment.operations.openStream(credentialA, cube, cursor);
|
|
2178
2144
|
expectError(stream, 410, ErrorCode.CURSOR_EXPIRED, 'Expired cursor stream');
|
|
2179
2145
|
invariant(stream.stream === null, 'Expired cursor stream exposed a body stream.');
|
|
2180
2146
|
return { read_status: 410, stream_status: 410, code: ErrorCode.CURSOR_EXPIRED };
|
|
2181
2147
|
});
|
|
2182
2148
|
|
|
2183
2149
|
await record('acks.idempotent', async () => {
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2150
|
+
const cube = await environment.admin.createCube('ack-idempotency');
|
|
2151
|
+
await environment.admin.grantCube(principalA, cube);
|
|
2152
|
+
const appended = await environment.operations.append(
|
|
2153
|
+
credentialA,
|
|
2154
|
+
cube,
|
|
2155
|
+
createProtocolEnvelope('ack-idempotency-entry', {
|
|
2156
|
+
post_id: '00000000-0000-4000-8000-000000000322',
|
|
2157
|
+
to: 'broadcast',
|
|
2158
|
+
message: 'acknowledge this entry',
|
|
2159
|
+
}),
|
|
2160
|
+
);
|
|
2161
|
+
expectStatus(appended, 201, 'Acknowledgement fixture append');
|
|
2162
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2163
|
+
const request = createProtocolEnvelope('ack-entry1', { entry_id: entry.id, kind: 'ack' });
|
|
2164
|
+
const first = await environment.operations.ack(credentialA, cube, request);
|
|
2165
|
+
const second = await environment.operations.ack(credentialA, cube, request);
|
|
2188
2166
|
expectStatus(first, 204, 'First acknowledgement');
|
|
2189
2167
|
expectStatus(second, 204, 'Repeated acknowledgement');
|
|
2190
2168
|
invariant((first.body === '' || first.body === undefined) && (second.body === '' || second.body === undefined), 'Acknowledgement responses must be bodyless.');
|
|
@@ -2215,7 +2193,6 @@ export async function runAdapterConformance(
|
|
|
2215
2193
|
expectStatus(append, 201, 'Acknowledgement-status fixture append');
|
|
2216
2194
|
const entry = decodeAppendLogResultEnvelope(append.body).payload.entry;
|
|
2217
2195
|
|
|
2218
|
-
const beforeMissing = await environment.admin.observeAuthorityState();
|
|
2219
2196
|
const missingResponse = await environment.operations.ackStatus(
|
|
2220
2197
|
credentialA,
|
|
2221
2198
|
cube,
|
|
@@ -2230,13 +2207,6 @@ export async function runAdapterConformance(
|
|
|
2230
2207
|
missing.claims.length === 0,
|
|
2231
2208
|
'Missing acknowledgement was not represented as nullable per-recipient state.',
|
|
2232
2209
|
);
|
|
2233
|
-
assertStateDelta(
|
|
2234
|
-
beforeMissing,
|
|
2235
|
-
await environment.admin.observeAuthorityState(),
|
|
2236
|
-
{},
|
|
2237
|
-
'Missing acknowledgement status query',
|
|
2238
|
-
);
|
|
2239
|
-
|
|
2240
2210
|
expectStatus(
|
|
2241
2211
|
await environment.operations.ack(
|
|
2242
2212
|
acknowledgedCredential,
|
|
@@ -2255,7 +2225,6 @@ export async function runAdapterConformance(
|
|
|
2255
2225
|
204,
|
|
2256
2226
|
'Acknowledgement-status claim',
|
|
2257
2227
|
);
|
|
2258
|
-
const beforeDistinct = await environment.admin.observeAuthorityState();
|
|
2259
2228
|
const distinctResponse = await environment.operations.ackStatus(
|
|
2260
2229
|
credentialA,
|
|
2261
2230
|
cube,
|
|
@@ -2272,26 +2241,6 @@ export async function runAdapterConformance(
|
|
|
2272
2241
|
distinct.recipients.find((recipient) => recipient.drone_id === claimingRecipient.id)?.acknowledged_at === null,
|
|
2273
2242
|
'Acknowledgement status conflated acknowledgement and claim records.',
|
|
2274
2243
|
);
|
|
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
2244
|
expectError(
|
|
2296
2245
|
await environment.operations.ackStatus(
|
|
2297
2246
|
credentialA,
|
|
@@ -2304,11 +2253,16 @@ export async function runAdapterConformance(
|
|
|
2304
2253
|
ErrorCode.NOT_FOUND,
|
|
2305
2254
|
'Unknown acknowledgement-status entry',
|
|
2306
2255
|
);
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
{},
|
|
2311
|
-
|
|
2256
|
+
const unreadResponse = await environment.operations.read(
|
|
2257
|
+
credentialA,
|
|
2258
|
+
cube,
|
|
2259
|
+
createProtocolEnvelope('ack-status-unread', { cursor: null, limit: 10 }),
|
|
2260
|
+
);
|
|
2261
|
+
expectStatus(unreadResponse, 200, 'Post-query unread read');
|
|
2262
|
+
const unread = decodeReadLogResultEnvelope(unreadResponse.body).payload;
|
|
2263
|
+
invariant(
|
|
2264
|
+
same(unread.entries, [entry]),
|
|
2265
|
+
'Acknowledgement-status query mutated or advanced unread delivery state.',
|
|
2312
2266
|
);
|
|
2313
2267
|
return {
|
|
2314
2268
|
missing_acknowledgement: null,
|
|
@@ -2321,23 +2275,38 @@ export async function runAdapterConformance(
|
|
|
2321
2275
|
});
|
|
2322
2276
|
|
|
2323
2277
|
await record('claims.durable-noncursor', async () => {
|
|
2324
|
-
|
|
2278
|
+
const principal = await environment.admin.createPrincipal('claim-noncursor');
|
|
2279
|
+
const cube = await environment.admin.createCube('claim-noncursor');
|
|
2280
|
+
await environment.admin.grantCube(principal, cube);
|
|
2281
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
2282
|
+
const appended = await environment.operations.append(
|
|
2283
|
+
credential,
|
|
2284
|
+
cube,
|
|
2285
|
+
createProtocolEnvelope('claim-fixture-entry', {
|
|
2286
|
+
post_id: '00000000-0000-4000-8000-000000000314',
|
|
2287
|
+
to: 'broadcast',
|
|
2288
|
+
message: 'claim cursor baseline',
|
|
2289
|
+
}),
|
|
2290
|
+
);
|
|
2291
|
+
expectStatus(appended, 201, 'Claim fixture append');
|
|
2292
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
2293
|
+
const baselineCursor = { id: entry.id, created_at: entry.created_at };
|
|
2325
2294
|
const claim = await environment.operations.ack(
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
createProtocolEnvelope('claim-
|
|
2295
|
+
credential,
|
|
2296
|
+
cube,
|
|
2297
|
+
createProtocolEnvelope('claim-fixture-claim', { entry_id: entry.id, kind: 'claim' }),
|
|
2329
2298
|
);
|
|
2330
2299
|
expectStatus(claim, 204, 'Claim');
|
|
2331
2300
|
const read = await environment.operations.read(
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
createProtocolEnvelope('
|
|
2301
|
+
credential,
|
|
2302
|
+
cube,
|
|
2303
|
+
createProtocolEnvelope('claim-fixture-readback', { cursor: baselineCursor, limit: 10 }),
|
|
2335
2304
|
);
|
|
2336
2305
|
expectStatus(read, 200, 'Claim-state read');
|
|
2337
2306
|
const page = decodeReadLogResultEnvelope(read.body).payload;
|
|
2338
2307
|
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 ===
|
|
2308
|
+
invariant(page.cursor !== null && compareLogCursor(page.cursor, baselineCursor) === 0, 'Claim advanced the log cursor.');
|
|
2309
|
+
invariant(page.claims.some((item) => item.log_entry_id === entry.id), 'Claim was not durable in a later read.');
|
|
2341
2310
|
return { durable_claims: 1, entries: 0, cursor_advanced: false };
|
|
2342
2311
|
});
|
|
2343
2312
|
|
|
@@ -2378,6 +2347,7 @@ export async function runAdapterConformance(
|
|
|
2378
2347
|
let metadataDroneB!: ConformanceDrone;
|
|
2379
2348
|
let metadataSessionA = '';
|
|
2380
2349
|
await record('security.drone-management-authorization', async () => {
|
|
2350
|
+
const observationCredential = await environment.admin.issueDroneSession(principalA);
|
|
2381
2351
|
workerRoleA = await environment.admin.createRole(cubeA, {
|
|
2382
2352
|
roleClass: 'worker', isHumanSeat: false,
|
|
2383
2353
|
});
|
|
@@ -2404,6 +2374,10 @@ export async function runAdapterConformance(
|
|
|
2404
2374
|
deniedCredentials.push({ access: access as 'read' | 'write', credential });
|
|
2405
2375
|
}
|
|
2406
2376
|
for (const { access, credential } of deniedCredentials) {
|
|
2377
|
+
const rosterBefore = await environment.operations.listDrones(observationCredential, cubeA);
|
|
2378
|
+
expectStatus(rosterBefore, 200, `${access} principal pre-denial roster`);
|
|
2379
|
+
const workerBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
2380
|
+
invariant(workerBefore, `${access} principal pre-denial roster omitted the managed worker.`);
|
|
2407
2381
|
expectError(
|
|
2408
2382
|
await environment.operations.reassignDrone(
|
|
2409
2383
|
credential,
|
|
@@ -2426,10 +2400,15 @@ export async function runAdapterConformance(
|
|
|
2426
2400
|
ErrorCode.ACCESS_DENIED,
|
|
2427
2401
|
`${access} principal eviction`,
|
|
2428
2402
|
);
|
|
2403
|
+
const rosterAfter = await environment.operations.listDrones(observationCredential, cubeA);
|
|
2404
|
+
expectStatus(rosterAfter, 200, `${access} principal post-denial roster`);
|
|
2405
|
+
invariant(
|
|
2406
|
+
same(listedDrone(rosterAfter, managedWorker.id), workerBefore),
|
|
2407
|
+
`${access} principal management denial mutated the managed worker.`,
|
|
2408
|
+
);
|
|
2429
2409
|
}
|
|
2430
2410
|
return { read_status: 403, write_status: 403, manage_required: true };
|
|
2431
2411
|
});
|
|
2432
|
-
|
|
2433
2412
|
await record('security.manage-access-matrix', async () => {
|
|
2434
2413
|
const noGrantPrincipal = await environment.admin.createPrincipal('Coordinator role without cube grant');
|
|
2435
2414
|
const noGrantInvitation = await environment.admin.issueSingleUseInvitation(noGrantPrincipal, 'client');
|
|
@@ -2449,12 +2428,8 @@ export async function runAdapterConformance(
|
|
|
2449
2428
|
roleClass: 'worker', isHumanSeat: false,
|
|
2450
2429
|
});
|
|
2451
2430
|
const evictionTarget = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
2452
|
-
await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
2431
|
+
const evictionTargetCredential = await environment.admin.issueManagedDroneSession(evictionTarget);
|
|
2453
2432
|
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
2433
|
const operations: ReadonlyArray<{
|
|
2459
2434
|
name: string;
|
|
2460
2435
|
successStatus: number;
|
|
@@ -2519,6 +2494,48 @@ export async function runAdapterConformance(
|
|
|
2519
2494
|
),
|
|
2520
2495
|
},
|
|
2521
2496
|
];
|
|
2497
|
+
const observableState = async (operationName: string): Promise<unknown | null> => {
|
|
2498
|
+
if (operationName === 'decision-record') {
|
|
2499
|
+
const response = await environment.operations.listDecisions(
|
|
2500
|
+
credentialA,
|
|
2501
|
+
cubeA,
|
|
2502
|
+
createProtocolEnvelope('matrix-observe-decisions', {}),
|
|
2503
|
+
);
|
|
2504
|
+
expectStatus(response, 200, 'Matrix decision observation');
|
|
2505
|
+
return decodeDecisionsResultEnvelope(response.body).payload;
|
|
2506
|
+
}
|
|
2507
|
+
if (operationName === 'drone-reassign' || operationName === 'drone-evict') {
|
|
2508
|
+
const response = await environment.operations.listDrones(credentialA, cubeA);
|
|
2509
|
+
expectStatus(response, 200, 'Matrix drone observation');
|
|
2510
|
+
return listedDrone(
|
|
2511
|
+
response,
|
|
2512
|
+
operationName === 'drone-reassign' ? managedWorker.id : evictionTarget.id,
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
return null;
|
|
2516
|
+
};
|
|
2517
|
+
const assertObservableDenial = async (operationName: string, before: unknown | null): Promise<void> => {
|
|
2518
|
+
if (operationName === 'role-create') {
|
|
2519
|
+
expectError(
|
|
2520
|
+
await environment.operations.roleRationale(
|
|
2521
|
+
credentialA,
|
|
2522
|
+
cubeA,
|
|
2523
|
+
createProtocolEnvelope('matrix-observe-role', {
|
|
2524
|
+
role: 'Matrix Role',
|
|
2525
|
+
section: 'Workflow rationale',
|
|
2526
|
+
}),
|
|
2527
|
+
),
|
|
2528
|
+
404,
|
|
2529
|
+
ErrorCode.ROLE_NOT_FOUND,
|
|
2530
|
+
'Denied matrix role observation',
|
|
2531
|
+
);
|
|
2532
|
+
} else if (before !== null) {
|
|
2533
|
+
invariant(
|
|
2534
|
+
same(await observableState(operationName), before),
|
|
2535
|
+
`${operationName} changed protocol-visible state after denial.`,
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
};
|
|
2522
2539
|
|
|
2523
2540
|
for (const operation of operations) {
|
|
2524
2541
|
for (const [kind, credential] of [
|
|
@@ -2526,14 +2543,25 @@ export async function runAdapterConformance(
|
|
|
2526
2543
|
['write', writeCredential],
|
|
2527
2544
|
['drone-session', droneCredential],
|
|
2528
2545
|
] as const) {
|
|
2529
|
-
const before = await
|
|
2546
|
+
const before = await observableState(operation.name);
|
|
2530
2547
|
expectError(
|
|
2531
2548
|
await operation.invoke(credential, cubeA),
|
|
2532
2549
|
403,
|
|
2533
2550
|
ErrorCode.ACCESS_DENIED,
|
|
2534
2551
|
`${operation.name} by ${kind} principal`,
|
|
2535
2552
|
);
|
|
2536
|
-
|
|
2553
|
+
await assertObservableDenial(operation.name, before);
|
|
2554
|
+
if (operation.name === 'drone-evict') {
|
|
2555
|
+
expectStatus(
|
|
2556
|
+
await environment.operations.read(
|
|
2557
|
+
evictionTargetCredential,
|
|
2558
|
+
cubeA,
|
|
2559
|
+
createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 }),
|
|
2560
|
+
),
|
|
2561
|
+
200,
|
|
2562
|
+
`${kind} denied eviction target probe`,
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2537
2565
|
}
|
|
2538
2566
|
for (const [kind, credential, cube] of [
|
|
2539
2567
|
['no-grant', noGrantCredential, cubeA],
|
|
@@ -2541,22 +2569,28 @@ export async function runAdapterConformance(
|
|
|
2541
2569
|
['foreign-cube', credentialA, cubeB],
|
|
2542
2570
|
['unknown-cube', credentialA, unknownCube],
|
|
2543
2571
|
] as const) {
|
|
2544
|
-
const before = await
|
|
2572
|
+
const before = await observableState(operation.name);
|
|
2545
2573
|
expectError(
|
|
2546
2574
|
await operation.invoke(credential, cube),
|
|
2547
2575
|
404,
|
|
2548
2576
|
ErrorCode.NOT_FOUND,
|
|
2549
2577
|
`${operation.name} against ${kind}`,
|
|
2550
2578
|
);
|
|
2551
|
-
|
|
2579
|
+
await assertObservableDenial(operation.name, before);
|
|
2580
|
+
if (operation.name === 'drone-evict') {
|
|
2581
|
+
expectStatus(
|
|
2582
|
+
await environment.operations.read(
|
|
2583
|
+
evictionTargetCredential,
|
|
2584
|
+
cubeA,
|
|
2585
|
+
createProtocolEnvelope(`matrix-${kind}-eviction-target-probe`, { cursor: null, limit: 1 }),
|
|
2586
|
+
),
|
|
2587
|
+
200,
|
|
2588
|
+
`${kind} denied eviction target probe`,
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2552
2591
|
}
|
|
2553
|
-
const beforeSuccess = await environment.admin.inspectCubeManagementState(cubeA);
|
|
2554
2592
|
const success = await operation.invoke(credentialA, cubeA);
|
|
2555
2593
|
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
2594
|
}
|
|
2561
2595
|
return {
|
|
2562
2596
|
operation_count: operations.length,
|
|
@@ -2564,7 +2598,8 @@ export async function runAdapterConformance(
|
|
|
2564
2598
|
read_write_status: 403,
|
|
2565
2599
|
drone_session_status: 403,
|
|
2566
2600
|
hidden_status: 404,
|
|
2567
|
-
|
|
2601
|
+
observable_non_mutation_operations: 4,
|
|
2602
|
+
write_only_operations: 2,
|
|
2568
2603
|
role_labels_authoritative: false,
|
|
2569
2604
|
};
|
|
2570
2605
|
});
|
|
@@ -2651,12 +2686,18 @@ export async function runAdapterConformance(
|
|
|
2651
2686
|
occupied_human_seat_denied: true,
|
|
2652
2687
|
};
|
|
2653
2688
|
});
|
|
2654
|
-
|
|
2655
2689
|
await record('security.cross-cube-drone-management', async () => {
|
|
2656
2690
|
const foreignRole = await environment.admin.createRole(cubeB, {
|
|
2657
2691
|
roleClass: 'worker', isHumanSeat: false,
|
|
2658
2692
|
});
|
|
2659
2693
|
const foreignDrone = await environment.admin.createDrone(principalB, cubeB, foreignRole);
|
|
2694
|
+
const foreignRosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
2695
|
+
const localRosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
2696
|
+
expectStatus(foreignRosterBefore, 200, 'Pre-denial foreign roster');
|
|
2697
|
+
expectStatus(localRosterBefore, 200, 'Pre-denial local roster');
|
|
2698
|
+
const foreignDroneBefore = listedDrone(foreignRosterBefore, foreignDrone.id);
|
|
2699
|
+
const managedWorkerBefore = listedDrone(localRosterBefore, managedWorker.id);
|
|
2700
|
+
invariant(foreignDroneBefore && managedWorkerBefore, 'Pre-denial roster omitted a drone-management target.');
|
|
2660
2701
|
expectError(
|
|
2661
2702
|
await environment.operations.reassignDrone(
|
|
2662
2703
|
credentialA,
|
|
@@ -2713,7 +2754,6 @@ export async function runAdapterConformance(
|
|
|
2713
2754
|
'Foreign role reassignment through authorized cube route',
|
|
2714
2755
|
);
|
|
2715
2756
|
await environment.admin.grantCube(principalA, cubeB, 'read');
|
|
2716
|
-
const foreignDroneBefore = await environment.admin.inspectManagedDrone(foreignDrone);
|
|
2717
2757
|
expectError(
|
|
2718
2758
|
await environment.operations.reassignDrone(
|
|
2719
2759
|
workerSession,
|
|
@@ -2725,10 +2765,6 @@ export async function runAdapterConformance(
|
|
|
2725
2765
|
ErrorCode.NOT_FOUND,
|
|
2726
2766
|
'Bound drone cross-cube reassignment',
|
|
2727
2767
|
);
|
|
2728
|
-
invariant(
|
|
2729
|
-
same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore),
|
|
2730
|
-
'Bound drone cross-cube reassignment mutated the foreign target.',
|
|
2731
|
-
);
|
|
2732
2768
|
expectError(
|
|
2733
2769
|
await environment.operations.evictDrone(
|
|
2734
2770
|
workerSession,
|
|
@@ -2740,15 +2776,24 @@ export async function runAdapterConformance(
|
|
|
2740
2776
|
ErrorCode.NOT_FOUND,
|
|
2741
2777
|
'Bound drone cross-cube eviction',
|
|
2742
2778
|
);
|
|
2779
|
+
const foreignRosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
2780
|
+
const localRosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
2781
|
+
expectStatus(foreignRosterAfter, 200, 'Post-denial foreign roster');
|
|
2782
|
+
expectStatus(localRosterAfter, 200, 'Post-denial local roster');
|
|
2743
2783
|
invariant(
|
|
2744
|
-
same(
|
|
2745
|
-
'
|
|
2784
|
+
same(listedDrone(foreignRosterAfter, foreignDrone.id), foreignDroneBefore),
|
|
2785
|
+
'Denied cross-cube management mutated the foreign drone.',
|
|
2786
|
+
);
|
|
2787
|
+
invariant(
|
|
2788
|
+
same(listedDrone(localRosterAfter, managedWorker.id), managedWorkerBefore),
|
|
2789
|
+
'Denied foreign-role management mutated the local drone.',
|
|
2746
2790
|
);
|
|
2747
2791
|
return {
|
|
2748
2792
|
unauthorized_cube_status: 404,
|
|
2749
2793
|
foreign_drone_status: 404,
|
|
2750
2794
|
foreign_role_status: 404,
|
|
2751
2795
|
bound_drone_cross_cube_status: 404,
|
|
2796
|
+
targets_unchanged: true,
|
|
2752
2797
|
code: ErrorCode.NOT_FOUND,
|
|
2753
2798
|
};
|
|
2754
2799
|
});
|
|
@@ -2783,14 +2828,6 @@ export async function runAdapterConformance(
|
|
|
2783
2828
|
}),
|
|
2784
2829
|
'Eviction response did not identify the terminal seat.',
|
|
2785
2830
|
);
|
|
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
2831
|
const after = await environment.operations.listDrones(credentialA, cubeA);
|
|
2795
2832
|
expectStatus(after, 200, 'Post-eviction roster');
|
|
2796
2833
|
invariant(!listedDroneIds(after).includes(evictedDrone.id), 'Evicted drone remained in roster.');
|
|
@@ -2864,38 +2901,46 @@ export async function runAdapterConformance(
|
|
|
2864
2901
|
},
|
|
2865
2902
|
)), 201, 'Role-contract reader enrollment');
|
|
2866
2903
|
|
|
2904
|
+
const refusalRationale = 'Workflow rationale:\nRefusal role remains.';
|
|
2867
2905
|
const defaultRole = await environment.admin.createRole(roleContractCube, {
|
|
2868
2906
|
roleClass: 'worker',
|
|
2869
2907
|
isHumanSeat: false,
|
|
2870
2908
|
name: 'Default Worker',
|
|
2909
|
+
detailedDescription: refusalRationale,
|
|
2871
2910
|
isDefault: true,
|
|
2872
2911
|
});
|
|
2873
2912
|
const mandatoryRole = await environment.admin.createRole(roleContractCube, {
|
|
2874
2913
|
roleClass: 'worker',
|
|
2875
2914
|
isHumanSeat: false,
|
|
2876
2915
|
name: 'Mandatory Worker',
|
|
2916
|
+
detailedDescription: refusalRationale,
|
|
2877
2917
|
isMandatory: true,
|
|
2878
2918
|
});
|
|
2879
2919
|
const humanRole = await environment.admin.createRole(roleContractCube, {
|
|
2880
2920
|
roleClass: 'queen',
|
|
2881
2921
|
isHumanSeat: true,
|
|
2882
2922
|
name: 'Human Seat',
|
|
2923
|
+
detailedDescription: refusalRationale,
|
|
2883
2924
|
});
|
|
2884
2925
|
const activeRole = await environment.admin.createRole(roleContractCube, {
|
|
2885
2926
|
roleClass: 'worker',
|
|
2886
2927
|
isHumanSeat: false,
|
|
2887
2928
|
name: 'Active Worker',
|
|
2929
|
+
detailedDescription: refusalRationale,
|
|
2888
2930
|
});
|
|
2889
|
-
await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
2931
|
+
const activeDrone = await environment.admin.createDrone(roleContractPrincipal, roleContractCube, activeRole);
|
|
2932
|
+
const activeRosterBefore = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
2933
|
+
expectStatus(activeRosterBefore, 200, 'Pre-refusal active-role roster');
|
|
2934
|
+
const activeDroneBefore = listedDrone(activeRosterBefore, activeDrone.id);
|
|
2935
|
+
invariant(activeDroneBefore, 'Pre-refusal roster omitted the active-role drone.');
|
|
2890
2936
|
|
|
2891
2937
|
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'],
|
|
2938
|
+
[defaultRole, ErrorCode.DEFAULT_ROLE_REQUIRED, 'default role', 'Default Worker', 'worker', false],
|
|
2939
|
+
[mandatoryRole, ErrorCode.ROLE_REQUIRED, 'mandatory role', 'Mandatory Worker', 'worker', false],
|
|
2940
|
+
[humanRole, ErrorCode.ROLE_REQUIRED, 'human-seat role', 'Human Seat', 'queen', true],
|
|
2941
|
+
[activeRole, ErrorCode.ROLE_IN_USE, 'active-drone role', 'Active Worker', 'worker', false],
|
|
2896
2942
|
] as const;
|
|
2897
|
-
for (const [index, [role, code, label]] of refusals.entries()) {
|
|
2898
|
-
const before = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
2943
|
+
for (const [index, [role, code, label, roleName, roleClass, isHumanSeat]] of refusals.entries()) {
|
|
2899
2944
|
const response = await environment.operations.deleteRole(
|
|
2900
2945
|
roleContractCredential,
|
|
2901
2946
|
roleContractCube,
|
|
@@ -2903,17 +2948,65 @@ export async function runAdapterConformance(
|
|
|
2903
2948
|
createProtocolEnvelope(`delete-refusal-${index}`, {}),
|
|
2904
2949
|
);
|
|
2905
2950
|
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
2951
|
if (code === ErrorCode.ROLE_IN_USE) {
|
|
2911
2952
|
const message = protocolErrorMessage(response) ?? '';
|
|
2912
2953
|
invariant(
|
|
2913
2954
|
message === ROLE_IN_USE_DELETE_MESSAGE,
|
|
2914
2955
|
'ROLE_IN_USE did not direct the caller to reassign or evict the drones first.',
|
|
2915
2956
|
);
|
|
2957
|
+
const roster = await environment.operations.listDrones(roleContractCredential, roleContractCube);
|
|
2958
|
+
expectStatus(roster, 200, 'Active-role refusal roster readback');
|
|
2959
|
+
invariant(
|
|
2960
|
+
same(listedDrone(roster, activeDrone.id), activeDroneBefore),
|
|
2961
|
+
'Active-role deletion refusal changed the active drone.',
|
|
2962
|
+
);
|
|
2916
2963
|
}
|
|
2964
|
+
const retained = await environment.operations.roleRationale(
|
|
2965
|
+
roleContractReadCredential,
|
|
2966
|
+
roleContractCube,
|
|
2967
|
+
createProtocolEnvelope(`delete-refusal-${index}-readback`, {
|
|
2968
|
+
role: role.id,
|
|
2969
|
+
section: 'Workflow rationale',
|
|
2970
|
+
}),
|
|
2971
|
+
);
|
|
2972
|
+
expectStatus(retained, 200, `Retained ${label} rationale lookup`);
|
|
2973
|
+
const retainedPayload = decodeRoleRationaleResultEnvelope(retained.body).payload;
|
|
2974
|
+
invariant(
|
|
2975
|
+
retainedPayload.role_id === role.id &&
|
|
2976
|
+
retainedPayload.role_name === roleName &&
|
|
2977
|
+
same(retainedPayload.section, {
|
|
2978
|
+
heading: 'Workflow rationale',
|
|
2979
|
+
body: refusalRationale,
|
|
2980
|
+
}),
|
|
2981
|
+
`Retained ${label} rationale lookup returned a different role or section.`,
|
|
2982
|
+
);
|
|
2983
|
+
const shapeSession = `${String(index).repeat(42)}S`;
|
|
2984
|
+
const shapeProbe = await environment.operations.attach(
|
|
2985
|
+
roleContractCredential,
|
|
2986
|
+
createProtocolEnvelope(`delete-refusal-${index}-shape-probe`, {
|
|
2987
|
+
cube_id: roleContractCube.id,
|
|
2988
|
+
role_id: role.id,
|
|
2989
|
+
session_credential: shapeSession,
|
|
2990
|
+
}),
|
|
2991
|
+
);
|
|
2992
|
+
expectStatus(shapeProbe, 200, `Retained ${label} shape probe`);
|
|
2993
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
2994
|
+
invariant(
|
|
2995
|
+
shapePayload.role.id === role.id &&
|
|
2996
|
+
shapePayload.role.role_class === roleClass &&
|
|
2997
|
+
shapePayload.role.is_human_seat === isHumanSeat,
|
|
2998
|
+
`Retained ${label} shape probe returned changed role semantics.`,
|
|
2999
|
+
);
|
|
3000
|
+
expectStatus(
|
|
3001
|
+
await environment.operations.evictDrone(
|
|
3002
|
+
roleContractCredential,
|
|
3003
|
+
roleContractCube,
|
|
3004
|
+
{ id: shapePayload.drone.id },
|
|
3005
|
+
createProtocolEnvelope(`delete-refusal-${index}-shape-cleanup`, {}),
|
|
3006
|
+
),
|
|
3007
|
+
200,
|
|
3008
|
+
`Retained ${label} shape cleanup`,
|
|
3009
|
+
);
|
|
2917
3010
|
}
|
|
2918
3011
|
|
|
2919
3012
|
const unknownRole = { id: '00000000-0000-4000-8000-000000000398' };
|
|
@@ -2933,8 +3026,8 @@ export async function runAdapterConformance(
|
|
|
2933
3026
|
roleClass: 'worker',
|
|
2934
3027
|
isHumanSeat: false,
|
|
2935
3028
|
name: 'Disposable Worker',
|
|
3029
|
+
detailedDescription: refusalRationale,
|
|
2936
3030
|
});
|
|
2937
|
-
const beforeReadDenial = await environment.admin.inspectCubeManagementState(roleContractCube);
|
|
2938
3031
|
expectError(
|
|
2939
3032
|
await environment.operations.deleteRole(
|
|
2940
3033
|
roleContractReadCredential,
|
|
@@ -2946,12 +3039,50 @@ export async function runAdapterConformance(
|
|
|
2946
3039
|
ErrorCode.ACCESS_DENIED,
|
|
2947
3040
|
'Delete role with read authority',
|
|
2948
3041
|
);
|
|
3042
|
+
const retainedDeletable = await environment.operations.roleRationale(
|
|
3043
|
+
roleContractReadCredential,
|
|
3044
|
+
roleContractCube,
|
|
3045
|
+
createProtocolEnvelope('delete-role-read-denied-readback', {
|
|
3046
|
+
role: deletableRole.id,
|
|
3047
|
+
section: 'Workflow rationale',
|
|
3048
|
+
}),
|
|
3049
|
+
);
|
|
3050
|
+
expectStatus(retainedDeletable, 200, 'Read-denied retained role rationale lookup');
|
|
3051
|
+
const retainedDeletablePayload = decodeRoleRationaleResultEnvelope(retainedDeletable.body).payload;
|
|
2949
3052
|
invariant(
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
3053
|
+
retainedDeletablePayload.role_id === deletableRole.id &&
|
|
3054
|
+
retainedDeletablePayload.role_name === 'Disposable Worker' &&
|
|
3055
|
+
same(retainedDeletablePayload.section, {
|
|
3056
|
+
heading: 'Workflow rationale',
|
|
3057
|
+
body: refusalRationale,
|
|
3058
|
+
}),
|
|
3059
|
+
'Read-authority deletion denial changed the target role.',
|
|
3060
|
+
);
|
|
3061
|
+
const shapeProbe = await environment.operations.attach(
|
|
3062
|
+
roleContractCredential,
|
|
3063
|
+
createProtocolEnvelope('delete-role-read-denied-shape-probe', {
|
|
3064
|
+
cube_id: roleContractCube.id,
|
|
3065
|
+
role_id: deletableRole.id,
|
|
3066
|
+
session_credential: 'S'.repeat(43),
|
|
3067
|
+
}),
|
|
3068
|
+
);
|
|
3069
|
+
expectStatus(shapeProbe, 200, 'Read-denied retained role shape probe');
|
|
3070
|
+
const shapePayload = decodeAttachResponseEnvelope(shapeProbe.body).payload;
|
|
3071
|
+
invariant(
|
|
3072
|
+
shapePayload.role.id === deletableRole.id &&
|
|
3073
|
+
shapePayload.role.role_class === 'worker' &&
|
|
3074
|
+
shapePayload.role.is_human_seat === false,
|
|
3075
|
+
'Read-authority deletion denial changed the target role shape.',
|
|
3076
|
+
);
|
|
3077
|
+
expectStatus(
|
|
3078
|
+
await environment.operations.evictDrone(
|
|
3079
|
+
roleContractCredential,
|
|
3080
|
+
roleContractCube,
|
|
3081
|
+
{ id: shapePayload.drone.id },
|
|
3082
|
+
createProtocolEnvelope('delete-role-read-denied-shape-cleanup', {}),
|
|
2953
3083
|
),
|
|
2954
|
-
|
|
3084
|
+
200,
|
|
3085
|
+
'Read-denied retained role shape cleanup',
|
|
2955
3086
|
);
|
|
2956
3087
|
const deleted = await environment.operations.deleteRole(
|
|
2957
3088
|
roleContractCredential,
|
|
@@ -3002,10 +3133,6 @@ export async function runAdapterConformance(
|
|
|
3002
3133
|
evictedRole,
|
|
3003
3134
|
createProtocolEnvelope('delete-evicted-role', {}),
|
|
3004
3135
|
), 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
3136
|
const read = await environment.operations.read(
|
|
3010
3137
|
roleContractCredential,
|
|
3011
3138
|
roleContractCube,
|
|
@@ -3028,11 +3155,10 @@ export async function runAdapterConformance(
|
|
|
3028
3155
|
ErrorCode.ROLE_REQUIRED,
|
|
3029
3156
|
],
|
|
3030
3157
|
in_use_message_actionable: true,
|
|
3031
|
-
|
|
3158
|
+
refusals_non_mutating: true,
|
|
3032
3159
|
activity_log_attribution_preserved: true,
|
|
3033
3160
|
};
|
|
3034
3161
|
});
|
|
3035
|
-
|
|
3036
3162
|
await record('roles.rationale-contract', async () => {
|
|
3037
3163
|
const detailedDescription = [
|
|
3038
3164
|
'Implements assigned work.',
|
|
@@ -3122,24 +3248,25 @@ export async function runAdapterConformance(
|
|
|
3122
3248
|
name: 'Ambiguous Role',
|
|
3123
3249
|
detailedDescription,
|
|
3124
3250
|
});
|
|
3125
|
-
await environment.admin.createRole(roleContractCube, {
|
|
3126
|
-
roleClass: 'worker',
|
|
3127
|
-
isHumanSeat: false,
|
|
3128
|
-
name: 'AMBIGUOUS ROLE',
|
|
3129
|
-
detailedDescription,
|
|
3130
|
-
});
|
|
3131
3251
|
expectError(
|
|
3132
|
-
await environment.operations.
|
|
3133
|
-
|
|
3252
|
+
await environment.operations.createRole(
|
|
3253
|
+
roleContractCredential,
|
|
3134
3254
|
roleContractCube,
|
|
3135
|
-
createProtocolEnvelope('
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3255
|
+
createProtocolEnvelope('role-case-variant-duplicate', { name: 'AMBIGUOUS ROLE' }),
|
|
3256
|
+
),
|
|
3257
|
+
409,
|
|
3258
|
+
ErrorCode.INVALID_INPUT,
|
|
3259
|
+
'Case-variant duplicate role name',
|
|
3260
|
+
);
|
|
3261
|
+
expectError(
|
|
3262
|
+
await environment.operations.createRole(
|
|
3263
|
+
roleContractCredential,
|
|
3264
|
+
roleContractCube,
|
|
3265
|
+
createProtocolEnvelope('role-invalid-character', { name: 'Invalid/Role' }),
|
|
3139
3266
|
),
|
|
3140
3267
|
400,
|
|
3141
3268
|
ErrorCode.INVALID_INPUT,
|
|
3142
|
-
'
|
|
3269
|
+
'Invalid role name character',
|
|
3143
3270
|
);
|
|
3144
3271
|
expectError(
|
|
3145
3272
|
await environment.operations.roleRationale(
|
|
@@ -3162,7 +3289,8 @@ export async function runAdapterConformance(
|
|
|
3162
3289
|
unknown_role_code: ErrorCode.ROLE_NOT_FOUND,
|
|
3163
3290
|
inaccessible_role_code: ErrorCode.ROLE_NOT_FOUND,
|
|
3164
3291
|
unknown_section_code: ErrorCode.ROLE_SECTION_NOT_FOUND,
|
|
3165
|
-
|
|
3292
|
+
case_variant_duplicate_status: 409,
|
|
3293
|
+
invalid_name_code: ErrorCode.INVALID_INPUT,
|
|
3166
3294
|
invalid_selector_code: ErrorCode.INVALID_INPUT,
|
|
3167
3295
|
};
|
|
3168
3296
|
});
|
|
@@ -3173,14 +3301,20 @@ export async function runAdapterConformance(
|
|
|
3173
3301
|
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
3174
3302
|
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
3175
3303
|
};
|
|
3304
|
+
try {
|
|
3305
|
+
metadataDroneA = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
3306
|
+
metadataSessionA = await environment.admin.issueManagedDroneSession(metadataDroneA);
|
|
3307
|
+
} catch (error) {
|
|
3308
|
+
prerequisiteFailures.set('shared.metadata-baseline', error instanceof Error ? error.message : String(error));
|
|
3309
|
+
}
|
|
3176
3310
|
await record('metadata.attach-report', async () => {
|
|
3177
|
-
|
|
3311
|
+
const attachSession = 'M'.repeat(43);
|
|
3178
3312
|
const created = await environment.operations.attach(
|
|
3179
3313
|
credentialA,
|
|
3180
3314
|
createProtocolEnvelope('metadata-attach-created', {
|
|
3181
3315
|
cube_id: cubeA.id,
|
|
3182
3316
|
role_id: workerRoleA.id,
|
|
3183
|
-
session_credential:
|
|
3317
|
+
session_credential: attachSession,
|
|
3184
3318
|
runtime_metadata: {
|
|
3185
3319
|
...knownMetadata,
|
|
3186
3320
|
working_repo_origin: 'git@github.com:Byte-Ventures/borg-mcp.git',
|
|
@@ -3192,7 +3326,7 @@ export async function runAdapterConformance(
|
|
|
3192
3326
|
invariant(createdPayload.result === 'created', 'First metadata attach did not create a seat.');
|
|
3193
3327
|
invariant(same(createdPayload.drone.runtime_metadata, knownMetadata), 'Attach did not echo canonical metadata.');
|
|
3194
3328
|
invariant(createdPayload.drone.runtime_metadata_reported, 'Present attach report was not marked reported.');
|
|
3195
|
-
|
|
3329
|
+
const attachedDrone = { id: createdPayload.drone.id };
|
|
3196
3330
|
|
|
3197
3331
|
const unavailableSession = 'N'.repeat(43);
|
|
3198
3332
|
const unavailable = await environment.operations.attach(
|
|
@@ -3239,15 +3373,15 @@ export async function runAdapterConformance(
|
|
|
3239
3373
|
createProtocolEnvelope('metadata-attach-reused', {
|
|
3240
3374
|
cube_id: cubeA.id,
|
|
3241
3375
|
role_id: workerRoleA.id,
|
|
3242
|
-
prior_drone_id:
|
|
3243
|
-
session_credential:
|
|
3376
|
+
prior_drone_id: attachedDrone.id,
|
|
3377
|
+
session_credential: attachSession,
|
|
3244
3378
|
runtime_metadata: knownMetadata,
|
|
3245
3379
|
}),
|
|
3246
3380
|
);
|
|
3247
3381
|
expectStatus(reused, 200, 'Metadata attach reuse');
|
|
3248
3382
|
invariant(decodeAttachResponseEnvelope(reused.body).payload.result === 'reused', 'Prior seat was not reused.');
|
|
3249
3383
|
const cleared = await environment.operations.selfMetadataUpdate(
|
|
3250
|
-
|
|
3384
|
+
attachSession,
|
|
3251
3385
|
cubeA,
|
|
3252
3386
|
createProtocolEnvelope('metadata-clear-all', {
|
|
3253
3387
|
agent_kind: null,
|
|
@@ -3265,7 +3399,7 @@ export async function runAdapterConformance(
|
|
|
3265
3399
|
);
|
|
3266
3400
|
expectStatus(
|
|
3267
3401
|
await environment.operations.selfMetadataUpdate(
|
|
3268
|
-
|
|
3402
|
+
attachSession,
|
|
3269
3403
|
cubeA,
|
|
3270
3404
|
createProtocolEnvelope('metadata-restore-known', knownMetadata),
|
|
3271
3405
|
),
|
|
@@ -3283,7 +3417,6 @@ export async function runAdapterConformance(
|
|
|
3283
3417
|
canonical_response: true,
|
|
3284
3418
|
};
|
|
3285
3419
|
});
|
|
3286
|
-
|
|
3287
3420
|
await record('metadata.self-heal-patch', async () => {
|
|
3288
3421
|
const updates = [
|
|
3289
3422
|
{ request_id: 'metadata-known-new', patch: { reported_model: 'openai/gpt-5.6-terra' } },
|
|
@@ -3291,6 +3424,7 @@ export async function runAdapterConformance(
|
|
|
3291
3424
|
{ request_id: 'metadata-null-repeat', patch: { reported_model: null } },
|
|
3292
3425
|
{ request_id: 'metadata-same-value', patch: { agent_kind: 'opencode' as const } },
|
|
3293
3426
|
];
|
|
3427
|
+
let state;
|
|
3294
3428
|
for (const update of updates) {
|
|
3295
3429
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3296
3430
|
metadataSessionA,
|
|
@@ -3298,15 +3432,30 @@ export async function runAdapterConformance(
|
|
|
3298
3432
|
createProtocolEnvelope(update.request_id, update.patch),
|
|
3299
3433
|
);
|
|
3300
3434
|
expectStatus(response, 200, update.request_id);
|
|
3301
|
-
decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body);
|
|
3435
|
+
state = decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body).payload;
|
|
3302
3436
|
}
|
|
3303
|
-
|
|
3304
|
-
invariant(state.
|
|
3305
|
-
invariant(state.metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
3437
|
+
invariant(state?.runtime_metadata.reported_model === null, 'Explicit null did not clear model metadata.');
|
|
3438
|
+
invariant(state.runtime_metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
3306
3439
|
return { known_replace: true, omitted_unchanged: true, null_clear: true, repeat_safe: true };
|
|
3307
3440
|
});
|
|
3308
3441
|
|
|
3309
3442
|
await record('security.metadata-invalid-atomic', async () => {
|
|
3443
|
+
const baselineResponse = await environment.operations.selfMetadataUpdate(
|
|
3444
|
+
metadataSessionA,
|
|
3445
|
+
cubeA,
|
|
3446
|
+
createProtocolEnvelope('metadata-invalid-baseline', {
|
|
3447
|
+
agent_kind: 'opencode',
|
|
3448
|
+
reported_model: null,
|
|
3449
|
+
working_repo_name: 'Byte-Ventures/borg-mcp-shared',
|
|
3450
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp-shared',
|
|
3451
|
+
}),
|
|
3452
|
+
);
|
|
3453
|
+
expectStatus(baselineResponse, 200, 'Invalid metadata baseline');
|
|
3454
|
+
decodeUpdateDroneRuntimeMetadataResponseEnvelope(baselineResponse.body);
|
|
3455
|
+
const baselineRoster = await environment.operations.listDrones(credentialA, cubeA);
|
|
3456
|
+
expectStatus(baselineRoster, 200, 'Invalid metadata baseline roster');
|
|
3457
|
+
const baselineDrone = listedDrone(baselineRoster, metadataDroneA.id);
|
|
3458
|
+
invariant(baselineDrone, 'Invalid metadata baseline roster omitted the metadata seat.');
|
|
3310
3459
|
const invalidPatches: unknown[] = [
|
|
3311
3460
|
{},
|
|
3312
3461
|
{ agent_kind: 'OpenCode' },
|
|
@@ -3324,15 +3473,16 @@ export async function runAdapterConformance(
|
|
|
3324
3473
|
{ last_seen: '2026-07-14T10:00:00.000Z' },
|
|
3325
3474
|
];
|
|
3326
3475
|
for (const [index, patch] of invalidPatches.entries()) {
|
|
3327
|
-
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
3328
3476
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3329
3477
|
metadataSessionA,
|
|
3330
3478
|
cubeA,
|
|
3331
3479
|
createProtocolEnvelope(`metadata-invalid-${index}`, patch),
|
|
3332
3480
|
);
|
|
3333
3481
|
expectError(response, 400, ErrorCode.INVALID_INPUT, `Invalid metadata patch ${index}`);
|
|
3482
|
+
const readbackResponse = await environment.operations.listDrones(credentialA, cubeA);
|
|
3483
|
+
expectStatus(readbackResponse, 200, `Invalid metadata readback ${index}`);
|
|
3334
3484
|
invariant(
|
|
3335
|
-
same(
|
|
3485
|
+
same(listedDrone(readbackResponse, metadataDroneA.id), baselineDrone),
|
|
3336
3486
|
`Invalid metadata patch ${index} partially mutated state.`,
|
|
3337
3487
|
);
|
|
3338
3488
|
}
|
|
@@ -3341,25 +3491,30 @@ export async function runAdapterConformance(
|
|
|
3341
3491
|
|
|
3342
3492
|
await record('security.metadata-own-seat', async () => {
|
|
3343
3493
|
const peer = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
const
|
|
3494
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3495
|
+
expectStatus(rosterBefore, 200, 'Pre-update metadata roster');
|
|
3496
|
+
const peerBefore = listedDrone(rosterBefore, peer.id);
|
|
3497
|
+
const managedBefore = listedDrone(rosterBefore, managedWorker.id);
|
|
3498
|
+
const ownBefore = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3499
|
+
invariant(peerBefore && managedBefore && ownBefore, 'Pre-update metadata roster omitted a fixture drone.');
|
|
3347
3500
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3348
3501
|
metadataSessionA,
|
|
3349
3502
|
cubeA,
|
|
3350
3503
|
createProtocolEnvelope('metadata-own-seat', { agent_kind: 'claude' }),
|
|
3351
3504
|
);
|
|
3352
3505
|
expectStatus(response, 200, 'Own-seat metadata update');
|
|
3506
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3507
|
+
expectStatus(rosterAfter, 200, 'Post-update metadata roster');
|
|
3353
3508
|
invariant(
|
|
3354
|
-
same(
|
|
3509
|
+
same(listedDrone(rosterAfter, peer.id), peerBefore),
|
|
3355
3510
|
'Own-seat update mutated a peer seat.',
|
|
3356
3511
|
);
|
|
3357
3512
|
invariant(
|
|
3358
|
-
same(
|
|
3513
|
+
same(listedDrone(rosterAfter, managedWorker.id), managedBefore),
|
|
3359
3514
|
'Own-seat update mutated another existing seat.',
|
|
3360
3515
|
);
|
|
3361
3516
|
invariant(
|
|
3362
|
-
(
|
|
3517
|
+
listedDrone(rosterAfter, metadataDroneA.id)?.role_id === ownBefore.role_id,
|
|
3363
3518
|
'Metadata update changed the current seat role.',
|
|
3364
3519
|
);
|
|
3365
3520
|
expectError(
|
|
@@ -3401,7 +3556,12 @@ export async function runAdapterConformance(
|
|
|
3401
3556
|
);
|
|
3402
3557
|
rejectedStates.push(['evicted', evicted, evictedSession, 410, ErrorCode.DRONE_EVICTED]);
|
|
3403
3558
|
for (const [label, drone, session, status, code] of rejectedStates) {
|
|
3404
|
-
const
|
|
3559
|
+
const rosterBefore = label === 'revoked'
|
|
3560
|
+
? await environment.operations.listDrones(credentialA, cubeA)
|
|
3561
|
+
: null;
|
|
3562
|
+
if (rosterBefore) expectStatus(rosterBefore, 200, 'Pre-denial revoked metadata roster');
|
|
3563
|
+
const droneBefore = rosterBefore ? listedDrone(rosterBefore, drone.id) : null;
|
|
3564
|
+
if (rosterBefore) invariant(droneBefore, 'Pre-denial roster omitted the revoked metadata seat.');
|
|
3405
3565
|
expectError(
|
|
3406
3566
|
await environment.operations.selfMetadataUpdate(
|
|
3407
3567
|
session,
|
|
@@ -3412,10 +3572,14 @@ export async function runAdapterConformance(
|
|
|
3412
3572
|
code,
|
|
3413
3573
|
`${label} metadata session`,
|
|
3414
3574
|
);
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3575
|
+
if (droneBefore) {
|
|
3576
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3577
|
+
expectStatus(rosterAfter, 200, 'Post-denial revoked metadata roster');
|
|
3578
|
+
invariant(
|
|
3579
|
+
same(listedDrone(rosterAfter, drone.id), droneBefore),
|
|
3580
|
+
'Revoked metadata denial mutated the protocol-visible seat.',
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3419
3583
|
}
|
|
3420
3584
|
return {
|
|
3421
3585
|
own_seat_only: true,
|
|
@@ -3446,7 +3610,10 @@ export async function runAdapterConformance(
|
|
|
3446
3610
|
);
|
|
3447
3611
|
expectStatus(attachedB, 200, 'Cube B metadata attach');
|
|
3448
3612
|
metadataDroneB = { id: decodeAttachResponseEnvelope(attachedB.body).payload.drone.id };
|
|
3449
|
-
const
|
|
3613
|
+
const rosterBefore = await environment.operations.listDrones(credentialB, cubeB);
|
|
3614
|
+
expectStatus(rosterBefore, 200, 'Pre-probe cube B roster');
|
|
3615
|
+
const beforeB = listedDrone(rosterBefore, metadataDroneB.id);
|
|
3616
|
+
invariant(beforeB, 'Pre-probe cube B roster omitted its metadata seat.');
|
|
3450
3617
|
const foreign = await environment.operations.selfMetadataUpdate(
|
|
3451
3618
|
metadataSessionA,
|
|
3452
3619
|
cubeB,
|
|
@@ -3460,17 +3627,19 @@ export async function runAdapterConformance(
|
|
|
3460
3627
|
);
|
|
3461
3628
|
expectError(unknown, 404, ErrorCode.NOT_FOUND, 'Unknown-cube metadata update');
|
|
3462
3629
|
invariant(same(foreign.body, unknown.body), 'Foreign and unknown metadata probes were distinguishable.');
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
);
|
|
3630
|
+
const rosterAfter = await environment.operations.listDrones(credentialB, cubeB);
|
|
3631
|
+
expectStatus(rosterAfter, 200, 'Post-probe cube B roster');
|
|
3632
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneB.id), beforeB), 'Cross-cube metadata update mutated the foreign seat.');
|
|
3467
3633
|
const listedA = await environment.operations.listDrones(credentialA, cubeA);
|
|
3468
3634
|
invariant(!listedDroneIds(listedA).includes(metadataDroneB.id), 'Cube A roster disclosed cube B metadata seat.');
|
|
3469
3635
|
return { update_status: 404, foreign_unknown_indistinguishable: true, foreign_unchanged: true, roster_isolated: true };
|
|
3470
3636
|
});
|
|
3471
3637
|
|
|
3472
3638
|
await record('security.metadata-noninterference', async () => {
|
|
3473
|
-
const
|
|
3639
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3640
|
+
expectStatus(rosterBefore, 200, 'Pre-update noninterference roster');
|
|
3641
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3642
|
+
invariant(before, 'Pre-update noninterference roster omitted the metadata seat.');
|
|
3474
3643
|
for (const [requestId, patch] of [
|
|
3475
3644
|
['metadata-noninterference-known', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
3476
3645
|
['metadata-noninterference-repeat', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
@@ -3506,22 +3675,23 @@ export async function runAdapterConformance(
|
|
|
3506
3675
|
}),
|
|
3507
3676
|
);
|
|
3508
3677
|
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
|
-
};
|
|
3678
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3679
|
+
expectStatus(rosterAfter, 200, 'Post-update noninterference roster');
|
|
3680
|
+
const after = listedDrone(rosterAfter, metadataDroneA.id);
|
|
3681
|
+
invariant(after, 'Post-update noninterference roster omitted the metadata seat.');
|
|
3682
|
+
invariant(
|
|
3683
|
+
same(withoutRuntimeMetadata(before), withoutRuntimeMetadata(after)),
|
|
3684
|
+
'Metadata update changed non-metadata roster state.',
|
|
3685
|
+
);
|
|
3686
|
+
return { non_metadata_roster_state_unchanged: true };
|
|
3520
3687
|
});
|
|
3521
3688
|
|
|
3522
3689
|
await record('security.metadata-secret-non-echo', async () => {
|
|
3523
3690
|
const marker = 'SECRET-METADATA-MARKER';
|
|
3524
|
-
const
|
|
3691
|
+
const rosterBefore = await environment.operations.listDrones(credentialA, cubeA);
|
|
3692
|
+
expectStatus(rosterBefore, 200, 'Pre-secret metadata roster');
|
|
3693
|
+
const before = listedDrone(rosterBefore, metadataDroneA.id);
|
|
3694
|
+
invariant(before, 'Pre-secret metadata roster omitted the metadata seat.');
|
|
3525
3695
|
const response = await environment.operations.selfMetadataUpdate(
|
|
3526
3696
|
metadataSessionA,
|
|
3527
3697
|
cubeA,
|
|
@@ -3532,10 +3702,9 @@ export async function runAdapterConformance(
|
|
|
3532
3702
|
);
|
|
3533
3703
|
expectError(response, 400, ErrorCode.INVALID_INPUT, 'Secret-bearing metadata update');
|
|
3534
3704
|
invariant(!JSON.stringify(response.body).includes(marker), 'Metadata error echoed hostile secret input.');
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
);
|
|
3705
|
+
const rosterAfter = await environment.operations.listDrones(credentialA, cubeA);
|
|
3706
|
+
expectStatus(rosterAfter, 200, 'Post-secret metadata roster');
|
|
3707
|
+
invariant(same(listedDrone(rosterAfter, metadataDroneA.id), before), 'Secret-bearing metadata input was persisted.');
|
|
3539
3708
|
const hostileKeys = [
|
|
3540
3709
|
'SECRET-METADATA-KEY-MARKER',
|
|
3541
3710
|
`control\u001b[2J`,
|
|
@@ -3570,8 +3739,23 @@ export async function runAdapterConformance(
|
|
|
3570
3739
|
});
|
|
3571
3740
|
|
|
3572
3741
|
await record('security.active-stream-revocation', async () => {
|
|
3573
|
-
|
|
3574
|
-
const
|
|
3742
|
+
const principal = await environment.admin.createPrincipal('stream-revocation');
|
|
3743
|
+
const cube = await environment.admin.createCube('stream-revocation');
|
|
3744
|
+
await environment.admin.grantCube(principal, cube);
|
|
3745
|
+
const credential = await environment.admin.issueDroneSession(principal);
|
|
3746
|
+
const appended = await environment.operations.append(
|
|
3747
|
+
credential,
|
|
3748
|
+
cube,
|
|
3749
|
+
createProtocolEnvelope('stream-revocation-entry', {
|
|
3750
|
+
post_id: '00000000-0000-4000-8000-000000000315',
|
|
3751
|
+
to: 'broadcast',
|
|
3752
|
+
message: 'revocation cursor baseline',
|
|
3753
|
+
}),
|
|
3754
|
+
);
|
|
3755
|
+
expectStatus(appended, 201, 'Revocation fixture append');
|
|
3756
|
+
const entry = decodeAppendLogResultEnvelope(appended.body).payload.entry;
|
|
3757
|
+
const cursor = { id: entry.id, created_at: entry.created_at };
|
|
3758
|
+
const opened = await environment.operations.openStream(credential, cube, cursor);
|
|
3575
3759
|
expectStatus(opened, 200, 'Revocation stream open');
|
|
3576
3760
|
invariant(opened.stream, 'Successful revocation stream omitted its AsyncIterable.');
|
|
3577
3761
|
const reader = new SseEventReader(opened.stream);
|
|
@@ -3580,7 +3764,7 @@ export async function runAdapterConformance(
|
|
|
3580
3764
|
invariant(bookmark.type === 'bookmark' && bookmark.replay_complete, 'Fresh live stream did not complete replay.');
|
|
3581
3765
|
const pending = reader.next();
|
|
3582
3766
|
await provePending(pending, 'Idle live stream', pendingProbeMs);
|
|
3583
|
-
await environment.admin.revokePrincipal(
|
|
3767
|
+
await environment.admin.revokePrincipal(principal);
|
|
3584
3768
|
let terminated = false;
|
|
3585
3769
|
try {
|
|
3586
3770
|
await within(pending, 'Revoked stream termination', streamDeadlineMs);
|
|
@@ -3593,9 +3777,9 @@ export async function runAdapterConformance(
|
|
|
3593
3777
|
await reader.close();
|
|
3594
3778
|
}
|
|
3595
3779
|
const rejected = await environment.operations.read(
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
createProtocolEnvelope('read-revoked', { cursor
|
|
3780
|
+
credential,
|
|
3781
|
+
cube,
|
|
3782
|
+
createProtocolEnvelope('read-revoked', { cursor, limit: 10 }),
|
|
3599
3783
|
);
|
|
3600
3784
|
expectError(rejected, 401, ErrorCode.SESSION_REVOKED, 'Post-revocation request');
|
|
3601
3785
|
return { stream_terminated: true, subsequent_status: 401, subsequent_code: ErrorCode.SESSION_REVOKED };
|
|
@@ -3627,15 +3811,16 @@ export async function runAdapterConformance(
|
|
|
3627
3811
|
const writer = await enrollParent('delete-writer', 'W', '704');
|
|
3628
3812
|
const outsider = await enrollParent('delete-outsider', 'O', '705');
|
|
3629
3813
|
await environment.admin.grantCreateCubeCapability(creator.principal);
|
|
3814
|
+
const deleteCubeRequest = {
|
|
3815
|
+
retry_key: '00000000-0000-4000-8000-000000000706',
|
|
3816
|
+
name: 'Disposable Cube',
|
|
3817
|
+
working_repo_name: 'disposable-cube',
|
|
3818
|
+
repository: { kind: 'local' as const, value: '00000000-0000-4000-8000-000000000707' },
|
|
3819
|
+
template: 'software-dev' as const,
|
|
3820
|
+
};
|
|
3630
3821
|
const createResponse = await environment.operations.createCube(
|
|
3631
3822
|
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
|
-
}),
|
|
3823
|
+
createProtocolEnvelope('delete-cube-create', deleteCubeRequest),
|
|
3639
3824
|
);
|
|
3640
3825
|
expectStatus(createResponse, 201, 'Disposable cube creation');
|
|
3641
3826
|
const created = decodeCreateCubeResponseEnvelope(createResponse.body).payload;
|
|
@@ -3646,6 +3831,17 @@ export async function runAdapterConformance(
|
|
|
3646
3831
|
await environment.admin.grantCube(writer.principal, cube, 'write');
|
|
3647
3832
|
const drone = await environment.admin.createDrone(creator.principal, cube, role);
|
|
3648
3833
|
const droneCredential = await environment.admin.issueManagedDroneSession(drone);
|
|
3834
|
+
const roleProbeSession = 'T'.repeat(43);
|
|
3835
|
+
const initialRoleProbe = await environment.operations.attach(
|
|
3836
|
+
manager.credential,
|
|
3837
|
+
createProtocolEnvelope('delete-role-shape-baseline', {
|
|
3838
|
+
cube_id: cube.id,
|
|
3839
|
+
role_id: role.id,
|
|
3840
|
+
session_credential: roleProbeSession,
|
|
3841
|
+
}),
|
|
3842
|
+
);
|
|
3843
|
+
expectStatus(initialRoleProbe, 200, 'Deletion default-role shape baseline');
|
|
3844
|
+
const roleProbeDrone = decodeAttachResponseEnvelope(initialRoleProbe.body).payload.drone;
|
|
3649
3845
|
const append = await environment.operations.append(
|
|
3650
3846
|
creator.credential,
|
|
3651
3847
|
cube,
|
|
@@ -3667,8 +3863,50 @@ export async function runAdapterConformance(
|
|
|
3667
3863
|
cube,
|
|
3668
3864
|
createProtocolEnvelope('delete-decision', { topic: 'cleanup', decision: 'delete' }),
|
|
3669
3865
|
), 201, 'Deletion fixture decision');
|
|
3866
|
+
const readDenialState = async (label: string): Promise<unknown> => {
|
|
3867
|
+
const decisions = await environment.operations.listDecisions(
|
|
3868
|
+
manager.credential,
|
|
3869
|
+
cube,
|
|
3870
|
+
createProtocolEnvelope(`delete-${label}-decision-readback`, {}),
|
|
3871
|
+
);
|
|
3872
|
+
expectStatus(decisions, 200, `${label} deletion decision readback`);
|
|
3873
|
+
const active = decodeDecisionsResultEnvelope(decisions.body).payload.decisions;
|
|
3874
|
+
const log = await environment.operations.read(
|
|
3875
|
+
manager.credential,
|
|
3876
|
+
cube,
|
|
3877
|
+
createProtocolEnvelope(`delete-${label}-log-readback`, { cursor: null, limit: 500 }),
|
|
3878
|
+
);
|
|
3879
|
+
expectStatus(log, 200, `${label} deletion log readback`);
|
|
3880
|
+
const logPayload = decodeReadLogResultEnvelope(log.body).payload;
|
|
3881
|
+
const roster = await environment.operations.listDrones(manager.credential, cube);
|
|
3882
|
+
expectStatus(roster, 200, `${label} deletion roster readback`);
|
|
3883
|
+
const roleReadback = await environment.operations.attach(
|
|
3884
|
+
manager.credential,
|
|
3885
|
+
createProtocolEnvelope(`delete-${label}-role-readback`, {
|
|
3886
|
+
cube_id: cube.id,
|
|
3887
|
+
role_id: role.id,
|
|
3888
|
+
prior_drone_id: roleProbeDrone.id,
|
|
3889
|
+
session_credential: roleProbeSession,
|
|
3890
|
+
}),
|
|
3891
|
+
);
|
|
3892
|
+
expectStatus(roleReadback, 200, `${label} deletion role readback`);
|
|
3893
|
+
const rolePayload = decodeAttachResponseEnvelope(roleReadback.body).payload.role;
|
|
3894
|
+
return {
|
|
3895
|
+
decisions: active,
|
|
3896
|
+
entries: logPayload.entries,
|
|
3897
|
+
claims: logPayload.claims,
|
|
3898
|
+
drone: listedDrone(roster, drone.id),
|
|
3899
|
+
role: rolePayload,
|
|
3900
|
+
};
|
|
3901
|
+
};
|
|
3902
|
+
const denialBaseline = await readDenialState('baseline');
|
|
3903
|
+
const assertDenialState = async (label: string): Promise<void> => {
|
|
3904
|
+
invariant(
|
|
3905
|
+
same(await readDenialState(label), denialBaseline),
|
|
3906
|
+
`${label} cube deletion denial mutated protocol-visible cube state.`,
|
|
3907
|
+
);
|
|
3908
|
+
};
|
|
3670
3909
|
|
|
3671
|
-
const beforeDenied = await environment.admin.inspectCubeManagementState(cube);
|
|
3672
3910
|
for (const [kind, credential] of [
|
|
3673
3911
|
['read', reader.credential],
|
|
3674
3912
|
['write', writer.credential],
|
|
@@ -3684,10 +3922,7 @@ export async function runAdapterConformance(
|
|
|
3684
3922
|
ErrorCode.ACCESS_DENIED,
|
|
3685
3923
|
`${kind} cube deletion`,
|
|
3686
3924
|
);
|
|
3687
|
-
|
|
3688
|
-
same(await environment.admin.inspectCubeManagementState(cube), beforeDenied),
|
|
3689
|
-
`${kind} cube deletion denial mutated the cube.`,
|
|
3690
|
-
);
|
|
3925
|
+
await assertDenialState(kind);
|
|
3691
3926
|
}
|
|
3692
3927
|
expectError(
|
|
3693
3928
|
await environment.operations.deleteCube(
|
|
@@ -3699,6 +3934,7 @@ export async function runAdapterConformance(
|
|
|
3699
3934
|
ErrorCode.NOT_FOUND,
|
|
3700
3935
|
'Never-authorized cube deletion',
|
|
3701
3936
|
);
|
|
3937
|
+
await assertDenialState('outsider');
|
|
3702
3938
|
|
|
3703
3939
|
const parentStream = await environment.operations.openStream(manager.credential, cube, null);
|
|
3704
3940
|
const droneStream = await environment.operations.openStream(droneCredential, cube, null);
|
|
@@ -3751,22 +3987,6 @@ export async function runAdapterConformance(
|
|
|
3751
3987
|
await streamReader.close();
|
|
3752
3988
|
}
|
|
3753
3989
|
|
|
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
3990
|
for (const [kind, credential] of [
|
|
3771
3991
|
['creator', creator.credential],
|
|
3772
3992
|
['manager', manager.credential],
|
|
@@ -3815,66 +4035,28 @@ export async function runAdapterConformance(
|
|
|
3815
4035
|
ErrorCode.NOT_FOUND,
|
|
3816
4036
|
'Never-authorized post-delete request',
|
|
3817
4037
|
);
|
|
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',
|
|
4038
|
+
const recreatedResponse = await environment.operations.createCube(
|
|
4039
|
+
creator.credential,
|
|
4040
|
+
createProtocolEnvelope('delete-cube-recreate', deleteCubeRequest),
|
|
3856
4041
|
);
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
),
|
|
3863
|
-
404,
|
|
3864
|
-
ErrorCode.NOT_FOUND,
|
|
3865
|
-
'Never-authorized post-restart request',
|
|
4042
|
+
expectStatus(recreatedResponse, 201, 'Post-delete cube recreation');
|
|
4043
|
+
const recreated = decodeCreateCubeResponseEnvelope(recreatedResponse.body).payload;
|
|
4044
|
+
invariant(
|
|
4045
|
+
recreated.result === 'created' && recreated.cube_id !== cube.id,
|
|
4046
|
+
'Post-delete recreation resolved retained cube-owned creation state.',
|
|
3866
4047
|
);
|
|
3867
4048
|
return {
|
|
3868
4049
|
deletion_status: 200,
|
|
3869
|
-
cascade_complete: true,
|
|
3870
4050
|
terminal_stream_error: ErrorCode.CUBE_DELETED,
|
|
3871
4051
|
terminal_http_status: 410,
|
|
3872
|
-
terminal_state_durable_after_restart: true,
|
|
3873
4052
|
unknown_hidden_status: 404,
|
|
3874
4053
|
non_member_manager: true,
|
|
4054
|
+
recreation_created_new_cube: true,
|
|
3875
4055
|
};
|
|
3876
4056
|
});
|
|
3877
4057
|
|
|
3878
|
-
const normalizedTranscript = results
|
|
4058
|
+
const normalizedTranscript = results
|
|
4059
|
+
.filter((result) => !result.skipped)
|
|
4060
|
+
.map(({ id, observations }) => ({ id, observations }));
|
|
3879
4061
|
return { ok: results.every((result) => result.ok), results, normalizedTranscript };
|
|
3880
4062
|
}
|