borgmcp-shared 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/dist/conformance/adapter.d.ts +27 -1
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +184 -17
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +139 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +193 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +62 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +177 -5
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/errors.d.ts +2 -0
- package/dist/protocol/errors.d.ts.map +1 -1
- package/dist/protocol/errors.js +2 -0
- package/dist/protocol/errors.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/dist/templates.d.ts +12 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +16 -3
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +18 -2
- package/docs/enrollment.md +97 -25
- package/docs/releasing.md +24 -22
- package/package.json +1 -1
- package/src/conformance/adapter.ts +398 -22
- package/src/conformance/index.ts +271 -1
- package/src/protocol/contract.ts +254 -11
- package/src/protocol/errors.ts +2 -0
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +20 -3
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
ErrorCode,
|
|
3
3
|
compareLogCursor,
|
|
4
4
|
createProtocolEnvelope,
|
|
5
|
+
decodeAssociateRepositoryCubeResponseEnvelope,
|
|
5
6
|
decodeCreateCubeResponseEnvelope,
|
|
6
7
|
decodeAppendLogResultEnvelope,
|
|
7
8
|
decodeDecisionResultEnvelope,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
decodeProtocolErrorEnvelope,
|
|
15
16
|
decodeProtocolTagPreflight,
|
|
16
17
|
decodeReadLogResultEnvelope,
|
|
18
|
+
decodeResolveRepositoryCubeResponseEnvelope,
|
|
17
19
|
decodeReassignDroneResultEnvelope,
|
|
18
20
|
decodeUpdateDroneRuntimeMetadataResponseEnvelope,
|
|
19
21
|
decodeSseFrames,
|
|
@@ -22,11 +24,18 @@ import {
|
|
|
22
24
|
PROTOCOL_VERSION,
|
|
23
25
|
utf8ByteLength,
|
|
24
26
|
type CreateCubeResponse,
|
|
27
|
+
type CreateCubeRepository,
|
|
28
|
+
type CubeTemplate,
|
|
29
|
+
type ResolvedRepositoryCube,
|
|
25
30
|
type LogCursor,
|
|
26
31
|
type StreamEvent,
|
|
27
32
|
type DroneRuntimeMetadata,
|
|
28
33
|
} from '../protocol/index.js';
|
|
29
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
CREATE_CUBE_ASSOCIATION_CONFORMANCE,
|
|
36
|
+
CREATE_CUBE_RETRY_CONFORMANCE,
|
|
37
|
+
ENROLLMENT_RETRY_CONFORMANCE,
|
|
38
|
+
} from './index.js';
|
|
30
39
|
|
|
31
40
|
export interface ConformanceHttpResponse {
|
|
32
41
|
status: number;
|
|
@@ -102,17 +111,34 @@ export interface ConformanceAuthorityState {
|
|
|
102
111
|
grants: number;
|
|
103
112
|
server_capabilities: number;
|
|
104
113
|
cube_create_bindings: number;
|
|
114
|
+
repository_associations: number;
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
export interface ConformanceCreatedCubeState {
|
|
108
118
|
cube_exists: boolean;
|
|
109
119
|
creator_has_grant: boolean;
|
|
120
|
+
creator_access: 'manage' | null;
|
|
110
121
|
grant_count: number;
|
|
111
122
|
role_count: number;
|
|
123
|
+
name: string;
|
|
124
|
+
working_repo_name: string;
|
|
125
|
+
repository: CreateCubeRepository;
|
|
126
|
+
template: CubeTemplate;
|
|
127
|
+
human_seat_role_id: string;
|
|
128
|
+
default_worker_role_id: string;
|
|
112
129
|
human_seat_role_matches: boolean;
|
|
113
130
|
default_worker_role_matches: boolean;
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
export interface ConformanceRepositoryCubeFixture {
|
|
134
|
+
cube_id: string;
|
|
135
|
+
name: string;
|
|
136
|
+
template: CubeTemplate;
|
|
137
|
+
human_seat_role_id: string;
|
|
138
|
+
default_worker_role_id: string;
|
|
139
|
+
access: 'manage';
|
|
140
|
+
}
|
|
141
|
+
|
|
116
142
|
export interface ConformanceEnrollmentPrincipalState {
|
|
117
143
|
response_client_matches: boolean;
|
|
118
144
|
active_credential_bindings: number;
|
|
@@ -150,6 +176,7 @@ export interface ConformanceAdmin {
|
|
|
150
176
|
cube: ConformanceCube,
|
|
151
177
|
access?: ConformanceCubeAccess,
|
|
152
178
|
): Promise<void>;
|
|
179
|
+
revokeCubeGrant(principal: ConformancePrincipal, cube: ConformanceCube): Promise<void>;
|
|
153
180
|
createRole(cube: ConformanceCube, input: {
|
|
154
181
|
readonly roleClass: 'queen' | 'worker';
|
|
155
182
|
readonly isHumanSeat: boolean;
|
|
@@ -178,6 +205,13 @@ export interface ConformanceAdmin {
|
|
|
178
205
|
creator: ConformancePrincipal,
|
|
179
206
|
response: CreateCubeResponse,
|
|
180
207
|
): Promise<ConformanceCreatedCubeState>;
|
|
208
|
+
prepareRepositoryCube(
|
|
209
|
+
cube: ConformanceCube,
|
|
210
|
+
input: {
|
|
211
|
+
name: string;
|
|
212
|
+
template: CubeTemplate;
|
|
213
|
+
},
|
|
214
|
+
): Promise<ConformanceRepositoryCubeFixture>;
|
|
181
215
|
inspectEnrollmentPrincipal(
|
|
182
216
|
principal: ConformancePrincipal,
|
|
183
217
|
responseClientId: string,
|
|
@@ -193,6 +227,8 @@ export interface ConformanceOperations {
|
|
|
193
227
|
protocol(credential: string | null): Promise<ConformanceHttpResponse>;
|
|
194
228
|
enroll(request: unknown): Promise<ConformanceHttpResponse>;
|
|
195
229
|
createCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
230
|
+
resolveRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
231
|
+
associateRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
196
232
|
attach(credential: string, request: unknown): Promise<ConformanceHttpResponse>;
|
|
197
233
|
selfMetadataUpdate(
|
|
198
234
|
credential: string,
|
|
@@ -277,6 +313,7 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
277
313
|
{ id: 'http.unauthenticated-liveness', area: 'http' },
|
|
278
314
|
{ id: 'protocol.credential-free-preflight', area: 'protocol' },
|
|
279
315
|
{ id: 'enrollment.retry-authority', area: 'enrollment' },
|
|
316
|
+
{ id: 'repository.explicit-association', area: 'repository' },
|
|
280
317
|
{ id: 'security.adapter-boundary-injection', area: 'security' },
|
|
281
318
|
{ id: 'security.oversize-request', area: 'security' },
|
|
282
319
|
{ id: 'security.cross-cube-isolation', area: 'security' },
|
|
@@ -686,7 +723,12 @@ export async function runAdapterConformance(
|
|
|
686
723
|
|
|
687
724
|
const cubeRequest = {
|
|
688
725
|
retry_key: '00000000-0000-4000-8000-000000000213',
|
|
689
|
-
name: '
|
|
726
|
+
name: 'Repository One',
|
|
727
|
+
working_repo_name: 'repository-one',
|
|
728
|
+
repository: {
|
|
729
|
+
kind: 'origin' as const,
|
|
730
|
+
value: 'https://github.com/Byte-Ventures/repository-one',
|
|
731
|
+
},
|
|
690
732
|
template: 'default',
|
|
691
733
|
};
|
|
692
734
|
const droneCredential = await environment.admin.issueDroneSession(ownerPrincipal);
|
|
@@ -724,13 +766,25 @@ export async function runAdapterConformance(
|
|
|
724
766
|
const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
|
|
725
767
|
expectStatus(createdResponse, 201, 'Owner cube create');
|
|
726
768
|
const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
|
|
727
|
-
assertStateDelta(
|
|
769
|
+
assertStateDelta(
|
|
770
|
+
beforeCreate,
|
|
771
|
+
await environment.admin.observeAuthorityState(),
|
|
772
|
+
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 },
|
|
773
|
+
'Owner cube create',
|
|
774
|
+
);
|
|
728
775
|
invariant(
|
|
729
776
|
same(await environment.admin.inspectCreatedCube(ownerPrincipal, created), {
|
|
730
777
|
cube_exists: true,
|
|
731
778
|
creator_has_grant: true,
|
|
779
|
+
creator_access: 'manage',
|
|
732
780
|
grant_count: 1,
|
|
733
781
|
role_count: 2,
|
|
782
|
+
name: cubeRequest.name,
|
|
783
|
+
working_repo_name: cubeRequest.working_repo_name,
|
|
784
|
+
repository: cubeRequest.repository,
|
|
785
|
+
template: cubeRequest.template,
|
|
786
|
+
human_seat_role_id: created.human_seat_role_id,
|
|
787
|
+
default_worker_role_id: created.default_worker_role_id,
|
|
734
788
|
human_seat_role_matches: true,
|
|
735
789
|
default_worker_role_matches: true,
|
|
736
790
|
}),
|
|
@@ -739,19 +793,92 @@ export async function runAdapterConformance(
|
|
|
739
793
|
const beforeCreateRetry = await environment.admin.observeAuthorityState();
|
|
740
794
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
741
795
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
742
|
-
|
|
796
|
+
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
797
|
+
invariant(retriedCreate.result === 'resolved', 'Exact cube-create retry did not report resolved readback.');
|
|
798
|
+
invariant(
|
|
799
|
+
same({ ...retriedCreate, result: 'created' }, created),
|
|
800
|
+
'Exact cube-create retry returned different authoritative fields.',
|
|
801
|
+
);
|
|
743
802
|
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
803
|
+
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find(
|
|
804
|
+
(vector) => vector.expected.outcome === 'resolved_response' &&
|
|
805
|
+
vector.retry.working_repo_name !== vector.initial.working_repo_name,
|
|
806
|
+
);
|
|
807
|
+
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
808
|
+
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
809
|
+
const displayRetryResponse = await environment.operations.createCube(
|
|
810
|
+
ownerCredential,
|
|
811
|
+
createProtocolEnvelope('cube-display-retry', {
|
|
812
|
+
...displayRetryVector.retry,
|
|
813
|
+
retry_key: cubeRequest.retry_key,
|
|
814
|
+
}),
|
|
815
|
+
);
|
|
816
|
+
expectStatus(displayRetryResponse, 201, 'Changed repository display retry');
|
|
817
|
+
invariant(
|
|
818
|
+
same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate),
|
|
819
|
+
'Changed repository display retry did not return stored authoritative display.',
|
|
820
|
+
);
|
|
821
|
+
assertStateDelta(
|
|
822
|
+
beforeDisplayRetry,
|
|
823
|
+
await environment.admin.observeAuthorityState(),
|
|
824
|
+
{},
|
|
825
|
+
'Changed repository display retry',
|
|
826
|
+
);
|
|
827
|
+
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
828
|
+
if (vector.expected.outcome !== 'retry_tuple_mismatch') continue;
|
|
829
|
+
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
830
|
+
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
831
|
+
expectSecretFreeError(
|
|
832
|
+
await environment.operations.createCube(
|
|
833
|
+
ownerCredential,
|
|
834
|
+
createProtocolEnvelope(`cube-mismatch-${index}`, mismatch),
|
|
835
|
+
),
|
|
836
|
+
409,
|
|
837
|
+
ErrorCode.INVALID_INPUT,
|
|
838
|
+
`Cube-create ${vector.name}`,
|
|
839
|
+
[cubeRequest.retry_key],
|
|
840
|
+
);
|
|
841
|
+
assertStateDelta(
|
|
842
|
+
beforeCreateMismatch,
|
|
843
|
+
await environment.admin.observeAuthorityState(),
|
|
844
|
+
{},
|
|
845
|
+
`Cube-create ${vector.name}`,
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
849
|
+
(vector) => vector.expected.outcome === 'resolved',
|
|
850
|
+
);
|
|
851
|
+
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
852
|
+
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
853
|
+
const associationResolveResponse = await environment.operations.createCube(
|
|
854
|
+
ownerCredential,
|
|
855
|
+
createProtocolEnvelope('cube-association-resolve', {
|
|
856
|
+
...sameAssociationVector.request,
|
|
857
|
+
repository: cubeRequest.repository,
|
|
858
|
+
}),
|
|
859
|
+
);
|
|
860
|
+
expectStatus(associationResolveResponse, 201, 'Fresh retry for associated repository');
|
|
861
|
+
const associationResolved = decodeCreateCubeResponseEnvelope(associationResolveResponse.body).payload;
|
|
862
|
+
invariant(associationResolved.result === 'resolved', 'Associated repository did not resolve.');
|
|
863
|
+
invariant(
|
|
864
|
+
same({ ...associationResolved, result: 'created' }, created),
|
|
865
|
+
'Associated repository did not return stored authoritative fields.',
|
|
866
|
+
);
|
|
867
|
+
assertStateDelta(
|
|
868
|
+
beforeAssociationResolve,
|
|
869
|
+
await environment.admin.observeAuthorityState(),
|
|
870
|
+
sameAssociationVector.expected.authority_state_delta,
|
|
871
|
+
'Fresh retry for associated repository',
|
|
751
872
|
);
|
|
752
|
-
assertStateDelta(beforeCreateMismatch, await environment.admin.observeAuthorityState(), {}, 'Cube-create retry mismatch');
|
|
753
873
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
754
|
-
const crossClientRequest = {
|
|
874
|
+
const crossClientRequest = {
|
|
875
|
+
...cubeRequest,
|
|
876
|
+
working_repo_name: 'ordinary-repository',
|
|
877
|
+
repository: {
|
|
878
|
+
kind: 'local' as const,
|
|
879
|
+
value: '00000000-0000-4000-8000-000000000216',
|
|
880
|
+
},
|
|
881
|
+
};
|
|
755
882
|
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
756
883
|
const crossClientResponse = await environment.operations.createCube(
|
|
757
884
|
ordinaryCredential,
|
|
@@ -763,7 +890,7 @@ export async function runAdapterConformance(
|
|
|
763
890
|
assertStateDelta(
|
|
764
891
|
beforeCrossClientCreate,
|
|
765
892
|
await environment.admin.observeAuthorityState(),
|
|
766
|
-
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 },
|
|
893
|
+
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 },
|
|
767
894
|
'Cross-client cube create',
|
|
768
895
|
);
|
|
769
896
|
invariant(
|
|
@@ -776,24 +903,35 @@ export async function runAdapterConformance(
|
|
|
776
903
|
createProtocolEnvelope('cube-cross-client-retry', crossClientRequest),
|
|
777
904
|
);
|
|
778
905
|
expectStatus(crossClientRetry, 201, 'Exact cross-client cube-create retry');
|
|
906
|
+
const crossClientResolved = decodeCreateCubeResponseEnvelope(crossClientRetry.body).payload;
|
|
779
907
|
invariant(
|
|
780
|
-
|
|
781
|
-
'Exact cross-client cube-create retry
|
|
908
|
+
crossClientResolved.result === 'resolved',
|
|
909
|
+
'Exact cross-client cube-create retry did not report resolved readback.',
|
|
910
|
+
);
|
|
911
|
+
invariant(
|
|
912
|
+
same(
|
|
913
|
+
{ ...crossClientResolved, result: 'created' },
|
|
914
|
+
crossClientCreated,
|
|
915
|
+
),
|
|
916
|
+
'Exact cross-client cube-create retry returned different authoritative fields.',
|
|
782
917
|
);
|
|
783
918
|
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
919
|
+
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
920
|
+
(vector) => vector.expected.outcome === 'created',
|
|
921
|
+
);
|
|
922
|
+
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
784
923
|
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
785
|
-
const secondCreatedResponse = await environment.operations.createCube(
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}));
|
|
924
|
+
const secondCreatedResponse = await environment.operations.createCube(
|
|
925
|
+
ownerCredential,
|
|
926
|
+
createProtocolEnvelope('cube-create-second', differentAssociationVector.request),
|
|
927
|
+
);
|
|
790
928
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
791
929
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
792
930
|
invariant(secondCreated.cube_id !== created.cube_id, 'Fresh cube-create retry key reused an existing cube.');
|
|
793
931
|
assertStateDelta(
|
|
794
932
|
beforeSecondCreate,
|
|
795
933
|
await environment.admin.observeAuthorityState(),
|
|
796
|
-
|
|
934
|
+
differentAssociationVector.expected.authority_state_delta,
|
|
797
935
|
'Second cube create',
|
|
798
936
|
);
|
|
799
937
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
|
@@ -866,6 +1004,244 @@ export async function runAdapterConformance(
|
|
|
866
1004
|
};
|
|
867
1005
|
});
|
|
868
1006
|
|
|
1007
|
+
await record('repository.explicit-association', async () => {
|
|
1008
|
+
const repository = {
|
|
1009
|
+
kind: 'origin' as const,
|
|
1010
|
+
value: 'https://github.com/Byte-Ventures/legacy-repository',
|
|
1011
|
+
};
|
|
1012
|
+
const prepared = await environment.admin.prepareRepositoryCube(cubeA, {
|
|
1013
|
+
name: 'Legacy Repository Cube',
|
|
1014
|
+
template: 'software-dev',
|
|
1015
|
+
});
|
|
1016
|
+
const resolveRequest = {
|
|
1017
|
+
working_repo_name: 'legacy-repository',
|
|
1018
|
+
repository,
|
|
1019
|
+
};
|
|
1020
|
+
const beforeNone = await environment.admin.observeAuthorityState();
|
|
1021
|
+
const noneResponse = await environment.operations.resolveRepositoryCube(
|
|
1022
|
+
credentialA,
|
|
1023
|
+
createProtocolEnvelope('repository-resolve-none', resolveRequest),
|
|
1024
|
+
);
|
|
1025
|
+
expectStatus(noneResponse, 200, 'Unassociated repository resolution');
|
|
1026
|
+
invariant(
|
|
1027
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }),
|
|
1028
|
+
'Unassociated repository did not return explicit none.',
|
|
1029
|
+
);
|
|
1030
|
+
assertStateDelta(beforeNone, await environment.admin.observeAuthorityState(), {}, 'Repository resolution');
|
|
1031
|
+
|
|
1032
|
+
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
1033
|
+
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
1034
|
+
expectSecretFreeError(
|
|
1035
|
+
await environment.operations.associateRepositoryCube(
|
|
1036
|
+
credentialB,
|
|
1037
|
+
createProtocolEnvelope('repository-associate-denied', associationRequest),
|
|
1038
|
+
),
|
|
1039
|
+
403,
|
|
1040
|
+
ErrorCode.ACCESS_DENIED,
|
|
1041
|
+
'Inaccessible repository cube association',
|
|
1042
|
+
[associationRequest.cube_id, repository.value],
|
|
1043
|
+
);
|
|
1044
|
+
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
1045
|
+
|
|
1046
|
+
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
1047
|
+
const associatedResponse = await environment.operations.associateRepositoryCube(
|
|
1048
|
+
credentialA,
|
|
1049
|
+
createProtocolEnvelope('repository-associate', associationRequest),
|
|
1050
|
+
);
|
|
1051
|
+
expectStatus(associatedResponse, 200, 'Explicit repository cube association');
|
|
1052
|
+
const associated = decodeAssociateRepositoryCubeResponseEnvelope(associatedResponse.body).payload;
|
|
1053
|
+
const authoritative = {
|
|
1054
|
+
result: 'resolved' as const,
|
|
1055
|
+
cube_id: prepared.cube_id,
|
|
1056
|
+
name: prepared.name,
|
|
1057
|
+
working_repo_name: resolveRequest.working_repo_name,
|
|
1058
|
+
repository,
|
|
1059
|
+
template: prepared.template,
|
|
1060
|
+
human_seat_role_id: prepared.human_seat_role_id,
|
|
1061
|
+
default_worker_role_id: prepared.default_worker_role_id,
|
|
1062
|
+
access: prepared.access,
|
|
1063
|
+
};
|
|
1064
|
+
invariant(same(associated, authoritative), 'Repository association did not return authoritative stored fields.');
|
|
1065
|
+
assertStateDelta(
|
|
1066
|
+
beforeAssociation,
|
|
1067
|
+
await environment.admin.observeAuthorityState(),
|
|
1068
|
+
{ repository_associations: 1 },
|
|
1069
|
+
'Explicit repository association',
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
const beforeResolve = await environment.admin.observeAuthorityState();
|
|
1073
|
+
const resolvedResponse = await environment.operations.resolveRepositoryCube(
|
|
1074
|
+
credentialA,
|
|
1075
|
+
createProtocolEnvelope('repository-resolve', {
|
|
1076
|
+
...resolveRequest,
|
|
1077
|
+
working_repo_name: 'ignored-new-display',
|
|
1078
|
+
}),
|
|
1079
|
+
);
|
|
1080
|
+
expectStatus(resolvedResponse, 200, 'Associated repository resolution');
|
|
1081
|
+
invariant(
|
|
1082
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative),
|
|
1083
|
+
'Associated repository did not return stored authoritative display.',
|
|
1084
|
+
);
|
|
1085
|
+
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
1086
|
+
|
|
1087
|
+
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
1088
|
+
const retryResponse = await environment.operations.associateRepositoryCube(
|
|
1089
|
+
credentialA,
|
|
1090
|
+
createProtocolEnvelope('repository-associate-retry', associationRequest),
|
|
1091
|
+
);
|
|
1092
|
+
expectStatus(retryResponse, 200, 'Exact repository association retry');
|
|
1093
|
+
invariant(
|
|
1094
|
+
same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative),
|
|
1095
|
+
'Exact repository association retry changed authoritative fields.',
|
|
1096
|
+
);
|
|
1097
|
+
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
1098
|
+
|
|
1099
|
+
const cubeC = await environment.admin.createCube('cube-c');
|
|
1100
|
+
await environment.admin.grantCube(principalA, cubeC);
|
|
1101
|
+
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
1102
|
+
name: 'Other Legacy Cube',
|
|
1103
|
+
template: 'starter',
|
|
1104
|
+
});
|
|
1105
|
+
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
1106
|
+
expectSecretFreeError(
|
|
1107
|
+
await environment.operations.associateRepositoryCube(
|
|
1108
|
+
credentialA,
|
|
1109
|
+
createProtocolEnvelope('repository-associate-repository-conflict', {
|
|
1110
|
+
...associationRequest,
|
|
1111
|
+
cube_id: cubeC.id,
|
|
1112
|
+
}),
|
|
1113
|
+
),
|
|
1114
|
+
409,
|
|
1115
|
+
ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
1116
|
+
'Repository-to-other-cube conflict',
|
|
1117
|
+
[cubeA.id, cubeC.id, repository.value],
|
|
1118
|
+
);
|
|
1119
|
+
assertStateDelta(
|
|
1120
|
+
beforeRepositoryConflict,
|
|
1121
|
+
await environment.admin.observeAuthorityState(),
|
|
1122
|
+
{},
|
|
1123
|
+
'Repository-to-other-cube conflict',
|
|
1124
|
+
);
|
|
1125
|
+
|
|
1126
|
+
const beforeCubeConflict = await environment.admin.observeAuthorityState();
|
|
1127
|
+
expectSecretFreeError(
|
|
1128
|
+
await environment.operations.associateRepositoryCube(
|
|
1129
|
+
credentialA,
|
|
1130
|
+
createProtocolEnvelope('repository-associate-cube-conflict', {
|
|
1131
|
+
cube_id: cubeA.id,
|
|
1132
|
+
working_repo_name: 'other-repository',
|
|
1133
|
+
repository: {
|
|
1134
|
+
kind: 'origin',
|
|
1135
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
1136
|
+
},
|
|
1137
|
+
}),
|
|
1138
|
+
),
|
|
1139
|
+
409,
|
|
1140
|
+
ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
1141
|
+
'Cube-to-other-repository conflict',
|
|
1142
|
+
[cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository'],
|
|
1143
|
+
);
|
|
1144
|
+
assertStateDelta(
|
|
1145
|
+
beforeCubeConflict,
|
|
1146
|
+
await environment.admin.observeAuthorityState(),
|
|
1147
|
+
{},
|
|
1148
|
+
'Cube-to-other-repository conflict',
|
|
1149
|
+
);
|
|
1150
|
+
|
|
1151
|
+
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
1152
|
+
name: 'Other Client Legacy Cube',
|
|
1153
|
+
template: 'starter',
|
|
1154
|
+
});
|
|
1155
|
+
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
1156
|
+
const crossClientResolve = await environment.operations.resolveRepositoryCube(
|
|
1157
|
+
credentialB,
|
|
1158
|
+
createProtocolEnvelope('repository-resolve-cross-client', resolveRequest),
|
|
1159
|
+
);
|
|
1160
|
+
expectStatus(crossClientResolve, 200, 'Cross-client repository resolution');
|
|
1161
|
+
invariant(
|
|
1162
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }),
|
|
1163
|
+
'Another client repository binding was exposed by resolution.',
|
|
1164
|
+
);
|
|
1165
|
+
assertStateDelta(
|
|
1166
|
+
beforeCrossClientResolve,
|
|
1167
|
+
await environment.admin.observeAuthorityState(),
|
|
1168
|
+
{},
|
|
1169
|
+
'Cross-client repository resolution',
|
|
1170
|
+
);
|
|
1171
|
+
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
1172
|
+
const crossClientAssociation = await environment.operations.associateRepositoryCube(
|
|
1173
|
+
credentialB,
|
|
1174
|
+
createProtocolEnvelope('repository-associate-cross-client', {
|
|
1175
|
+
...associationRequest,
|
|
1176
|
+
cube_id: cubeB.id,
|
|
1177
|
+
}),
|
|
1178
|
+
);
|
|
1179
|
+
expectStatus(crossClientAssociation, 200, 'Cross-client repository association');
|
|
1180
|
+
const crossClientAssociated = decodeAssociateRepositoryCubeResponseEnvelope(
|
|
1181
|
+
crossClientAssociation.body,
|
|
1182
|
+
).payload;
|
|
1183
|
+
invariant(
|
|
1184
|
+
crossClientAssociated.cube_id === preparedB.cube_id &&
|
|
1185
|
+
crossClientAssociated.repository.value === repository.value,
|
|
1186
|
+
'Another client binding changed cross-client association identity.',
|
|
1187
|
+
);
|
|
1188
|
+
assertStateDelta(
|
|
1189
|
+
beforeCrossClientAssociation,
|
|
1190
|
+
await environment.admin.observeAuthorityState(),
|
|
1191
|
+
{ repository_associations: 1 },
|
|
1192
|
+
'Cross-client repository association',
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
1196
|
+
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
1197
|
+
const hiddenResolve = await environment.operations.resolveRepositoryCube(
|
|
1198
|
+
credentialA,
|
|
1199
|
+
createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest),
|
|
1200
|
+
);
|
|
1201
|
+
expectStatus(hiddenResolve, 200, 'Inaccessible repository binding resolution');
|
|
1202
|
+
invariant(
|
|
1203
|
+
same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }),
|
|
1204
|
+
'Inaccessible repository binding was exposed by read-only resolution.',
|
|
1205
|
+
);
|
|
1206
|
+
assertStateDelta(
|
|
1207
|
+
beforeHiddenResolve,
|
|
1208
|
+
await environment.admin.observeAuthorityState(),
|
|
1209
|
+
{},
|
|
1210
|
+
'Inaccessible repository binding resolution',
|
|
1211
|
+
);
|
|
1212
|
+
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
1213
|
+
expectSecretFreeError(
|
|
1214
|
+
await environment.operations.associateRepositoryCube(
|
|
1215
|
+
credentialA,
|
|
1216
|
+
createProtocolEnvelope('repository-associate-inaccessible-binding', {
|
|
1217
|
+
...associationRequest,
|
|
1218
|
+
cube_id: cubeC.id,
|
|
1219
|
+
}),
|
|
1220
|
+
),
|
|
1221
|
+
403,
|
|
1222
|
+
ErrorCode.ACCESS_DENIED,
|
|
1223
|
+
'Inaccessible existing repository binding',
|
|
1224
|
+
[cubeA.id, cubeC.id, repository.value],
|
|
1225
|
+
);
|
|
1226
|
+
assertStateDelta(
|
|
1227
|
+
beforeHiddenAssociation,
|
|
1228
|
+
await environment.admin.observeAuthorityState(),
|
|
1229
|
+
{},
|
|
1230
|
+
'Inaccessible existing repository binding',
|
|
1231
|
+
);
|
|
1232
|
+
await environment.admin.grantCube(principalA, cubeA);
|
|
1233
|
+
return {
|
|
1234
|
+
none: true,
|
|
1235
|
+
resolved: true,
|
|
1236
|
+
idempotent: true,
|
|
1237
|
+
repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
1238
|
+
cube_conflict: ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
1239
|
+
inaccessible: ErrorCode.ACCESS_DENIED,
|
|
1240
|
+
inaccessible_binding_hidden: true,
|
|
1241
|
+
cross_client_binding_hidden: true,
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
|
|
869
1245
|
await record('security.adapter-boundary-injection', async () => {
|
|
870
1246
|
const injectedMessage = "'); DROP TABLE log_entries; --\r\ndata: forged-sse-frame";
|
|
871
1247
|
const injectedBody = JSON.stringify(
|