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
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ functions and contracts include API documentation in their TypeScript sources.
|
|
|
59
59
|
|
|
60
60
|
## Handshake
|
|
61
61
|
|
|
62
|
-
The first
|
|
62
|
+
The repository-first HTTP contract has six shared paths:
|
|
63
63
|
|
|
64
64
|
- `GET /healthz` is the only unauthenticated liveness probe. Success is `204`
|
|
65
65
|
with no body or identifying metadata.
|
|
@@ -78,6 +78,15 @@ The first-slice HTTP contract has four shared paths:
|
|
|
78
78
|
strict, idempotent request selects a server-owned template; one atomic success
|
|
79
79
|
creates a cube, two initial roles, and the creator's cube-scoped `manage`
|
|
80
80
|
grant. Exact retries return the same non-secret identities without mutation.
|
|
81
|
+
- `POST /api/repository-cubes/resolve` is an authenticated, read-only lookup by
|
|
82
|
+
canonical repository identity. It returns explicit none or the stored
|
|
83
|
+
authoritative cube, template, role IDs, and repository display without
|
|
84
|
+
inferring identity from a cube name.
|
|
85
|
+
- `PUT /api/repository-cubes/association` atomically binds an explicitly
|
|
86
|
+
confirmed cube ID and canonical repository identity for a caller with cube
|
|
87
|
+
`manage` authority. The same binding is idempotent; repository and cube
|
|
88
|
+
conflicts return distinct non-enumerating error codes, and a conflict,
|
|
89
|
+
validation failure, or permission failure performs no mutation.
|
|
81
90
|
|
|
82
91
|
`decodeProtocolTagPreflight` fails closed on any tag other than the exact
|
|
83
92
|
expected version, on any extra field, or on a non-object body — before any
|
|
@@ -114,7 +123,8 @@ readonly data rather than Vitest-specific helpers, so they work with any test
|
|
|
114
123
|
runner and in any JavaScript runtime. Cases cover HTTP and canonical errors,
|
|
115
124
|
credential misuse, isolation and revocation, SSE framing/replay/cursor ordering,
|
|
116
125
|
executable enrollment authority/retry/mismatch/redaction and cube-create
|
|
117
|
-
idempotency,
|
|
126
|
+
idempotency, explicit repository resolution/adoption and conflict atomicity,
|
|
127
|
+
acks, claims, decisions, cube-scoped drone reassignment, role-class
|
|
118
128
|
and single-seat invariants, eviction exclusion, and terminal bearer signaling.
|
|
119
129
|
The same runner covers complete attach reports, own-seat metadata self-healing,
|
|
120
130
|
canonical repository identity, invalid-patch atomicity, cross-cube isolation,
|
|
@@ -165,15 +175,19 @@ never published: annotated tag object `90a1cf686a0ce32a7aef836b0b82a930191b9030`
|
|
|
165
175
|
peels to protected-main commit `fd69b08586481a60c88099dede8e4e066f73f2f2`;
|
|
166
176
|
attempt-1 workflow run `30054936226` failed in tests before build, packaging,
|
|
167
177
|
authentication, or registry mutation and must never be rerun or moved.
|
|
168
|
-
`borgmcp-shared@0.6.1`
|
|
169
|
-
|
|
178
|
+
`borgmcp-shared@0.6.1`, `borgmcp-shared@0.6.2`, and
|
|
179
|
+
`borgmcp-shared@0.6.3` are published and immutable. This source now identifies the
|
|
180
|
+
reviewed, unpublished `0.6.4` repository-association hotfix candidate. This
|
|
170
181
|
reviewed version bump grants no tag or publish authority; creating annotated
|
|
171
|
-
`v0.6.
|
|
182
|
+
`v0.6.4` and publishing
|
|
172
183
|
its exact reviewed artifact remain separate, independently gated steps.
|
|
173
|
-
Consumers update shared, server, and client together: peers carrying protocol v2
|
|
174
|
-
and
|
|
175
|
-
belongs in this repository, package metadata, lockfiles, or a
|
|
176
|
-
|
|
184
|
+
Consumers update shared, server, and client together: peers carrying protocol v2,
|
|
185
|
+
v3, v4, and v5 reject incompatible tags during credential-free preflight. No
|
|
186
|
+
registry token belongs in this repository, package metadata, lockfiles, or a
|
|
187
|
+
committed `.npmrc`;
|
|
188
|
+
publishing uses protected external credentials and provenance. Successful
|
|
189
|
+
`npm publish` is the workflow terminal boundary; post-publication registry reads
|
|
190
|
+
are outside the immutable release gate.
|
|
177
191
|
|
|
178
192
|
The first client and server releases must consume the reviewed registry release,
|
|
179
193
|
not a Git, tag, or local-path dependency. Public release requires the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CreateCubeResponse, type LogCursor, type DroneRuntimeMetadata } from '../protocol/index.js';
|
|
1
|
+
import { type CreateCubeResponse, type CreateCubeRepository, type CubeTemplate, type LogCursor, type DroneRuntimeMetadata } from '../protocol/index.js';
|
|
2
2
|
export interface ConformanceHttpResponse {
|
|
3
3
|
status: number;
|
|
4
4
|
body: unknown;
|
|
@@ -65,15 +65,31 @@ export interface ConformanceAuthorityState {
|
|
|
65
65
|
grants: number;
|
|
66
66
|
server_capabilities: number;
|
|
67
67
|
cube_create_bindings: number;
|
|
68
|
+
repository_associations: number;
|
|
68
69
|
}
|
|
69
70
|
export interface ConformanceCreatedCubeState {
|
|
70
71
|
cube_exists: boolean;
|
|
71
72
|
creator_has_grant: boolean;
|
|
73
|
+
creator_access: 'manage' | null;
|
|
72
74
|
grant_count: number;
|
|
73
75
|
role_count: number;
|
|
76
|
+
name: string;
|
|
77
|
+
working_repo_name: string;
|
|
78
|
+
repository: CreateCubeRepository;
|
|
79
|
+
template: CubeTemplate;
|
|
80
|
+
human_seat_role_id: string;
|
|
81
|
+
default_worker_role_id: string;
|
|
74
82
|
human_seat_role_matches: boolean;
|
|
75
83
|
default_worker_role_matches: boolean;
|
|
76
84
|
}
|
|
85
|
+
export interface ConformanceRepositoryCubeFixture {
|
|
86
|
+
cube_id: string;
|
|
87
|
+
name: string;
|
|
88
|
+
template: CubeTemplate;
|
|
89
|
+
human_seat_role_id: string;
|
|
90
|
+
default_worker_role_id: string;
|
|
91
|
+
access: 'manage';
|
|
92
|
+
}
|
|
77
93
|
export interface ConformanceEnrollmentPrincipalState {
|
|
78
94
|
response_client_matches: boolean;
|
|
79
95
|
active_credential_bindings: number;
|
|
@@ -91,6 +107,7 @@ export interface ConformanceAdmin {
|
|
|
91
107
|
createPrincipal(name: string): Promise<ConformancePrincipal>;
|
|
92
108
|
createCube(name: string): Promise<ConformanceCube>;
|
|
93
109
|
grantCube(principal: ConformancePrincipal, cube: ConformanceCube, access?: ConformanceCubeAccess): Promise<void>;
|
|
110
|
+
revokeCubeGrant(principal: ConformancePrincipal, cube: ConformanceCube): Promise<void>;
|
|
94
111
|
createRole(cube: ConformanceCube, input: {
|
|
95
112
|
readonly roleClass: 'queen' | 'worker';
|
|
96
113
|
readonly isHumanSeat: boolean;
|
|
@@ -111,6 +128,10 @@ export interface ConformanceAdmin {
|
|
|
111
128
|
issueSingleUseInvitation(principal: ConformancePrincipal, purpose: 'owner' | 'client'): Promise<string>;
|
|
112
129
|
observeAuthorityState(): Promise<ConformanceAuthorityState>;
|
|
113
130
|
inspectCreatedCube(creator: ConformancePrincipal, response: CreateCubeResponse): Promise<ConformanceCreatedCubeState>;
|
|
131
|
+
prepareRepositoryCube(cube: ConformanceCube, input: {
|
|
132
|
+
name: string;
|
|
133
|
+
template: CubeTemplate;
|
|
134
|
+
}): Promise<ConformanceRepositoryCubeFixture>;
|
|
114
135
|
inspectEnrollmentPrincipal(principal: ConformancePrincipal, responseClientId: string): Promise<ConformanceEnrollmentPrincipalState>;
|
|
115
136
|
revokePrincipal(principal: ConformancePrincipal): Promise<void>;
|
|
116
137
|
expireCursor(cube: ConformanceCube, cursor: LogCursor): Promise<void>;
|
|
@@ -121,6 +142,8 @@ export interface ConformanceOperations {
|
|
|
121
142
|
protocol(credential: string | null): Promise<ConformanceHttpResponse>;
|
|
122
143
|
enroll(request: unknown): Promise<ConformanceHttpResponse>;
|
|
123
144
|
createCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
145
|
+
resolveRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
146
|
+
associateRepositoryCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
124
147
|
attach(credential: string, request: unknown): Promise<ConformanceHttpResponse>;
|
|
125
148
|
selfMetadataUpdate(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
|
|
126
149
|
append(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
|
|
@@ -150,6 +173,9 @@ export declare const ADAPTER_CONFORMANCE_FIXTURES: readonly [{
|
|
|
150
173
|
}, {
|
|
151
174
|
readonly id: "enrollment.retry-authority";
|
|
152
175
|
readonly area: "enrollment";
|
|
176
|
+
}, {
|
|
177
|
+
readonly id: "repository.explicit-association";
|
|
178
|
+
readonly area: "repository";
|
|
153
179
|
}, {
|
|
154
180
|
readonly id: "security.adapter-boundary-injection";
|
|
155
181
|
readonly area: "security";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/conformance/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/conformance/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeCreateCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeAttachResponseEnvelope, decodeDroneRuntimeMetadataPatch, decodeEvictDroneResultEnvelope, decodeProtocolEnvelope, decodeProtocolErrorEnvelope, decodeProtocolTagPreflight, decodeReadLogResultEnvelope, decodeReassignDroneResultEnvelope, decodeUpdateDroneRuntimeMetadataResponseEnvelope, decodeSseFrames, PROTOCOL_LIMIT_CEILINGS, PROTOCOL_HTTP_CONTRACT, PROTOCOL_VERSION, utf8ByteLength, } from '../protocol/index.js';
|
|
2
|
-
import { ENROLLMENT_RETRY_CONFORMANCE } from './index.js';
|
|
1
|
+
import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeAssociateRepositoryCubeResponseEnvelope, decodeCreateCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeAttachResponseEnvelope, decodeDroneRuntimeMetadataPatch, decodeEvictDroneResultEnvelope, decodeProtocolEnvelope, decodeProtocolErrorEnvelope, decodeProtocolTagPreflight, decodeReadLogResultEnvelope, decodeResolveRepositoryCubeResponseEnvelope, decodeReassignDroneResultEnvelope, decodeUpdateDroneRuntimeMetadataResponseEnvelope, decodeSseFrames, PROTOCOL_LIMIT_CEILINGS, PROTOCOL_HTTP_CONTRACT, PROTOCOL_VERSION, utf8ByteLength, } from '../protocol/index.js';
|
|
2
|
+
import { CREATE_CUBE_ASSOCIATION_CONFORMANCE, CREATE_CUBE_RETRY_CONFORMANCE, ENROLLMENT_RETRY_CONFORMANCE, } from './index.js';
|
|
3
3
|
export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
4
4
|
{ id: 'http.unauthenticated-liveness', area: 'http' },
|
|
5
5
|
{ id: 'protocol.credential-free-preflight', area: 'protocol' },
|
|
6
6
|
{ id: 'enrollment.retry-authority', area: 'enrollment' },
|
|
7
|
+
{ id: 'repository.explicit-association', area: 'repository' },
|
|
7
8
|
{ id: 'security.adapter-boundary-injection', area: 'security' },
|
|
8
9
|
{ id: 'security.oversize-request', area: 'security' },
|
|
9
10
|
{ id: 'security.cross-cube-isolation', area: 'security' },
|
|
@@ -285,7 +286,12 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
285
286
|
assertStateDelta(beforeAuthorityEnrollment, await environment.admin.observeAuthorityState(), { enrolled_clients: 2, enrollment_claims: 2, server_capabilities: 1 }, 'Owner and ordinary enrollment');
|
|
286
287
|
const cubeRequest = {
|
|
287
288
|
retry_key: '00000000-0000-4000-8000-000000000213',
|
|
288
|
-
name: '
|
|
289
|
+
name: 'Repository One',
|
|
290
|
+
working_repo_name: 'repository-one',
|
|
291
|
+
repository: {
|
|
292
|
+
kind: 'origin',
|
|
293
|
+
value: 'https://github.com/Byte-Ventures/repository-one',
|
|
294
|
+
},
|
|
289
295
|
template: 'default',
|
|
290
296
|
};
|
|
291
297
|
const droneCredential = await environment.admin.issueDroneSession(ownerPrincipal);
|
|
@@ -299,47 +305,91 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
299
305
|
const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
|
|
300
306
|
expectStatus(createdResponse, 201, 'Owner cube create');
|
|
301
307
|
const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
|
|
302
|
-
assertStateDelta(beforeCreate, await environment.admin.observeAuthorityState(), { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 }, 'Owner cube create');
|
|
308
|
+
assertStateDelta(beforeCreate, await environment.admin.observeAuthorityState(), { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 }, 'Owner cube create');
|
|
303
309
|
invariant(same(await environment.admin.inspectCreatedCube(ownerPrincipal, created), {
|
|
304
310
|
cube_exists: true,
|
|
305
311
|
creator_has_grant: true,
|
|
312
|
+
creator_access: 'manage',
|
|
306
313
|
grant_count: 1,
|
|
307
314
|
role_count: 2,
|
|
315
|
+
name: cubeRequest.name,
|
|
316
|
+
working_repo_name: cubeRequest.working_repo_name,
|
|
317
|
+
repository: cubeRequest.repository,
|
|
318
|
+
template: cubeRequest.template,
|
|
319
|
+
human_seat_role_id: created.human_seat_role_id,
|
|
320
|
+
default_worker_role_id: created.default_worker_role_id,
|
|
308
321
|
human_seat_role_matches: true,
|
|
309
322
|
default_worker_role_matches: true,
|
|
310
323
|
}), 'Created cube identities or creator grant did not match persisted authority state.');
|
|
311
324
|
const beforeCreateRetry = await environment.admin.observeAuthorityState();
|
|
312
325
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
313
326
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
314
|
-
|
|
327
|
+
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
328
|
+
invariant(retriedCreate.result === 'resolved', 'Exact cube-create retry did not report resolved readback.');
|
|
329
|
+
invariant(same({ ...retriedCreate, result: 'created' }, created), 'Exact cube-create retry returned different authoritative fields.');
|
|
315
330
|
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find((vector) => vector.expected.outcome === 'resolved_response' &&
|
|
332
|
+
vector.retry.working_repo_name !== vector.initial.working_repo_name);
|
|
333
|
+
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
334
|
+
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
335
|
+
const displayRetryResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-display-retry', {
|
|
336
|
+
...displayRetryVector.retry,
|
|
337
|
+
retry_key: cubeRequest.retry_key,
|
|
338
|
+
}));
|
|
339
|
+
expectStatus(displayRetryResponse, 201, 'Changed repository display retry');
|
|
340
|
+
invariant(same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate), 'Changed repository display retry did not return stored authoritative display.');
|
|
341
|
+
assertStateDelta(beforeDisplayRetry, await environment.admin.observeAuthorityState(), {}, 'Changed repository display retry');
|
|
342
|
+
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
343
|
+
if (vector.expected.outcome !== 'retry_tuple_mismatch')
|
|
344
|
+
continue;
|
|
345
|
+
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
346
|
+
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
347
|
+
expectSecretFreeError(await environment.operations.createCube(ownerCredential, createProtocolEnvelope(`cube-mismatch-${index}`, mismatch)), 409, ErrorCode.INVALID_INPUT, `Cube-create ${vector.name}`, [cubeRequest.retry_key]);
|
|
348
|
+
assertStateDelta(beforeCreateMismatch, await environment.admin.observeAuthorityState(), {}, `Cube-create ${vector.name}`);
|
|
349
|
+
}
|
|
350
|
+
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find((vector) => vector.expected.outcome === 'resolved');
|
|
351
|
+
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
352
|
+
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
353
|
+
const associationResolveResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-association-resolve', {
|
|
354
|
+
...sameAssociationVector.request,
|
|
355
|
+
repository: cubeRequest.repository,
|
|
356
|
+
}));
|
|
357
|
+
expectStatus(associationResolveResponse, 201, 'Fresh retry for associated repository');
|
|
358
|
+
const associationResolved = decodeCreateCubeResponseEnvelope(associationResolveResponse.body).payload;
|
|
359
|
+
invariant(associationResolved.result === 'resolved', 'Associated repository did not resolve.');
|
|
360
|
+
invariant(same({ ...associationResolved, result: 'created' }, created), 'Associated repository did not return stored authoritative fields.');
|
|
361
|
+
assertStateDelta(beforeAssociationResolve, await environment.admin.observeAuthorityState(), sameAssociationVector.expected.authority_state_delta, 'Fresh retry for associated repository');
|
|
319
362
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
320
|
-
const crossClientRequest = {
|
|
363
|
+
const crossClientRequest = {
|
|
364
|
+
...cubeRequest,
|
|
365
|
+
working_repo_name: 'ordinary-repository',
|
|
366
|
+
repository: {
|
|
367
|
+
kind: 'local',
|
|
368
|
+
value: '00000000-0000-4000-8000-000000000216',
|
|
369
|
+
},
|
|
370
|
+
};
|
|
321
371
|
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
322
372
|
const crossClientResponse = await environment.operations.createCube(ordinaryCredential, createProtocolEnvelope('cube-cross-client', crossClientRequest));
|
|
323
373
|
expectStatus(crossClientResponse, 201, 'Cross-client cube create with reused retry key');
|
|
324
374
|
const crossClientCreated = decodeCreateCubeResponseEnvelope(crossClientResponse.body).payload;
|
|
325
375
|
invariant(crossClientCreated.cube_id !== created.cube_id, 'Cross-client retry key reused another client\'s cube.');
|
|
326
|
-
assertStateDelta(beforeCrossClientCreate, await environment.admin.observeAuthorityState(), { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 }, 'Cross-client cube create');
|
|
376
|
+
assertStateDelta(beforeCrossClientCreate, await environment.admin.observeAuthorityState(), { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 }, 'Cross-client cube create');
|
|
327
377
|
invariant((await environment.admin.inspectCreatedCube(ordinaryPrincipal, crossClientCreated)).creator_has_grant, 'Cross-client cube creation did not grant its authenticated creator.');
|
|
328
378
|
const beforeCrossClientRetry = await environment.admin.observeAuthorityState();
|
|
329
379
|
const crossClientRetry = await environment.operations.createCube(ordinaryCredential, createProtocolEnvelope('cube-cross-client-retry', crossClientRequest));
|
|
330
380
|
expectStatus(crossClientRetry, 201, 'Exact cross-client cube-create retry');
|
|
331
|
-
|
|
381
|
+
const crossClientResolved = decodeCreateCubeResponseEnvelope(crossClientRetry.body).payload;
|
|
382
|
+
invariant(crossClientResolved.result === 'resolved', 'Exact cross-client cube-create retry did not report resolved readback.');
|
|
383
|
+
invariant(same({ ...crossClientResolved, result: 'created' }, crossClientCreated), 'Exact cross-client cube-create retry returned different authoritative fields.');
|
|
332
384
|
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
385
|
+
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find((vector) => vector.expected.outcome === 'created');
|
|
386
|
+
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
333
387
|
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
334
|
-
const secondCreatedResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create-second',
|
|
335
|
-
...cubeRequest,
|
|
336
|
-
retry_key: '00000000-0000-4000-8000-000000000214',
|
|
337
|
-
name: 'repository-two',
|
|
338
|
-
}));
|
|
388
|
+
const secondCreatedResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create-second', differentAssociationVector.request));
|
|
339
389
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
340
390
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
341
391
|
invariant(secondCreated.cube_id !== created.cube_id, 'Fresh cube-create retry key reused an existing cube.');
|
|
342
|
-
assertStateDelta(beforeSecondCreate, await environment.admin.observeAuthorityState(),
|
|
392
|
+
assertStateDelta(beforeSecondCreate, await environment.admin.observeAuthorityState(), differentAssociationVector.expected.authority_state_delta, 'Second cube create');
|
|
343
393
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
|
344
394
|
const beforeRevokedCreate = await environment.admin.observeAuthorityState();
|
|
345
395
|
expectSecretFreeError(await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-revoked', {
|
|
@@ -394,6 +444,123 @@ export async function runAdapterConformance(environment, options = {}) {
|
|
|
394
444
|
response_secret_free: true,
|
|
395
445
|
};
|
|
396
446
|
});
|
|
447
|
+
await record('repository.explicit-association', async () => {
|
|
448
|
+
const repository = {
|
|
449
|
+
kind: 'origin',
|
|
450
|
+
value: 'https://github.com/Byte-Ventures/legacy-repository',
|
|
451
|
+
};
|
|
452
|
+
const prepared = await environment.admin.prepareRepositoryCube(cubeA, {
|
|
453
|
+
name: 'Legacy Repository Cube',
|
|
454
|
+
template: 'software-dev',
|
|
455
|
+
});
|
|
456
|
+
const resolveRequest = {
|
|
457
|
+
working_repo_name: 'legacy-repository',
|
|
458
|
+
repository,
|
|
459
|
+
};
|
|
460
|
+
const beforeNone = await environment.admin.observeAuthorityState();
|
|
461
|
+
const noneResponse = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve-none', resolveRequest));
|
|
462
|
+
expectStatus(noneResponse, 200, 'Unassociated repository resolution');
|
|
463
|
+
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(noneResponse.body).payload, { result: 'none' }), 'Unassociated repository did not return explicit none.');
|
|
464
|
+
assertStateDelta(beforeNone, await environment.admin.observeAuthorityState(), {}, 'Repository resolution');
|
|
465
|
+
const associationRequest = { cube_id: cubeA.id, ...resolveRequest };
|
|
466
|
+
const beforeDenied = await environment.admin.observeAuthorityState();
|
|
467
|
+
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialB, createProtocolEnvelope('repository-associate-denied', associationRequest)), 403, ErrorCode.ACCESS_DENIED, 'Inaccessible repository cube association', [associationRequest.cube_id, repository.value]);
|
|
468
|
+
assertStateDelta(beforeDenied, await environment.admin.observeAuthorityState(), {}, 'Denied repository association');
|
|
469
|
+
const beforeAssociation = await environment.admin.observeAuthorityState();
|
|
470
|
+
const associatedResponse = await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate', associationRequest));
|
|
471
|
+
expectStatus(associatedResponse, 200, 'Explicit repository cube association');
|
|
472
|
+
const associated = decodeAssociateRepositoryCubeResponseEnvelope(associatedResponse.body).payload;
|
|
473
|
+
const authoritative = {
|
|
474
|
+
result: 'resolved',
|
|
475
|
+
cube_id: prepared.cube_id,
|
|
476
|
+
name: prepared.name,
|
|
477
|
+
working_repo_name: resolveRequest.working_repo_name,
|
|
478
|
+
repository,
|
|
479
|
+
template: prepared.template,
|
|
480
|
+
human_seat_role_id: prepared.human_seat_role_id,
|
|
481
|
+
default_worker_role_id: prepared.default_worker_role_id,
|
|
482
|
+
access: prepared.access,
|
|
483
|
+
};
|
|
484
|
+
invariant(same(associated, authoritative), 'Repository association did not return authoritative stored fields.');
|
|
485
|
+
assertStateDelta(beforeAssociation, await environment.admin.observeAuthorityState(), { repository_associations: 1 }, 'Explicit repository association');
|
|
486
|
+
const beforeResolve = await environment.admin.observeAuthorityState();
|
|
487
|
+
const resolvedResponse = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve', {
|
|
488
|
+
...resolveRequest,
|
|
489
|
+
working_repo_name: 'ignored-new-display',
|
|
490
|
+
}));
|
|
491
|
+
expectStatus(resolvedResponse, 200, 'Associated repository resolution');
|
|
492
|
+
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(resolvedResponse.body).payload, authoritative), 'Associated repository did not return stored authoritative display.');
|
|
493
|
+
assertStateDelta(beforeResolve, await environment.admin.observeAuthorityState(), {}, 'Associated repository resolution');
|
|
494
|
+
const beforeRetry = await environment.admin.observeAuthorityState();
|
|
495
|
+
const retryResponse = await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-retry', associationRequest));
|
|
496
|
+
expectStatus(retryResponse, 200, 'Exact repository association retry');
|
|
497
|
+
invariant(same(decodeAssociateRepositoryCubeResponseEnvelope(retryResponse.body).payload, authoritative), 'Exact repository association retry changed authoritative fields.');
|
|
498
|
+
assertStateDelta(beforeRetry, await environment.admin.observeAuthorityState(), {}, 'Repository association retry');
|
|
499
|
+
const cubeC = await environment.admin.createCube('cube-c');
|
|
500
|
+
await environment.admin.grantCube(principalA, cubeC);
|
|
501
|
+
await environment.admin.prepareRepositoryCube(cubeC, {
|
|
502
|
+
name: 'Other Legacy Cube',
|
|
503
|
+
template: 'starter',
|
|
504
|
+
});
|
|
505
|
+
const beforeRepositoryConflict = await environment.admin.observeAuthorityState();
|
|
506
|
+
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-repository-conflict', {
|
|
507
|
+
...associationRequest,
|
|
508
|
+
cube_id: cubeC.id,
|
|
509
|
+
})), 409, ErrorCode.REPOSITORY_ALREADY_ASSOCIATED, 'Repository-to-other-cube conflict', [cubeA.id, cubeC.id, repository.value]);
|
|
510
|
+
assertStateDelta(beforeRepositoryConflict, await environment.admin.observeAuthorityState(), {}, 'Repository-to-other-cube conflict');
|
|
511
|
+
const beforeCubeConflict = await environment.admin.observeAuthorityState();
|
|
512
|
+
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-cube-conflict', {
|
|
513
|
+
cube_id: cubeA.id,
|
|
514
|
+
working_repo_name: 'other-repository',
|
|
515
|
+
repository: {
|
|
516
|
+
kind: 'origin',
|
|
517
|
+
value: 'https://github.com/Byte-Ventures/other-repository',
|
|
518
|
+
},
|
|
519
|
+
})), 409, ErrorCode.CUBE_ALREADY_ASSOCIATED, 'Cube-to-other-repository conflict', [cubeA.id, repository.value, 'https://github.com/Byte-Ventures/other-repository']);
|
|
520
|
+
assertStateDelta(beforeCubeConflict, await environment.admin.observeAuthorityState(), {}, 'Cube-to-other-repository conflict');
|
|
521
|
+
const preparedB = await environment.admin.prepareRepositoryCube(cubeB, {
|
|
522
|
+
name: 'Other Client Legacy Cube',
|
|
523
|
+
template: 'starter',
|
|
524
|
+
});
|
|
525
|
+
const beforeCrossClientResolve = await environment.admin.observeAuthorityState();
|
|
526
|
+
const crossClientResolve = await environment.operations.resolveRepositoryCube(credentialB, createProtocolEnvelope('repository-resolve-cross-client', resolveRequest));
|
|
527
|
+
expectStatus(crossClientResolve, 200, 'Cross-client repository resolution');
|
|
528
|
+
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(crossClientResolve.body).payload, { result: 'none' }), 'Another client repository binding was exposed by resolution.');
|
|
529
|
+
assertStateDelta(beforeCrossClientResolve, await environment.admin.observeAuthorityState(), {}, 'Cross-client repository resolution');
|
|
530
|
+
const beforeCrossClientAssociation = await environment.admin.observeAuthorityState();
|
|
531
|
+
const crossClientAssociation = await environment.operations.associateRepositoryCube(credentialB, createProtocolEnvelope('repository-associate-cross-client', {
|
|
532
|
+
...associationRequest,
|
|
533
|
+
cube_id: cubeB.id,
|
|
534
|
+
}));
|
|
535
|
+
expectStatus(crossClientAssociation, 200, 'Cross-client repository association');
|
|
536
|
+
const crossClientAssociated = decodeAssociateRepositoryCubeResponseEnvelope(crossClientAssociation.body).payload;
|
|
537
|
+
invariant(crossClientAssociated.cube_id === preparedB.cube_id &&
|
|
538
|
+
crossClientAssociated.repository.value === repository.value, 'Another client binding changed cross-client association identity.');
|
|
539
|
+
assertStateDelta(beforeCrossClientAssociation, await environment.admin.observeAuthorityState(), { repository_associations: 1 }, 'Cross-client repository association');
|
|
540
|
+
await environment.admin.revokeCubeGrant(principalA, cubeA);
|
|
541
|
+
const beforeHiddenResolve = await environment.admin.observeAuthorityState();
|
|
542
|
+
const hiddenResolve = await environment.operations.resolveRepositoryCube(credentialA, createProtocolEnvelope('repository-resolve-inaccessible-binding', resolveRequest));
|
|
543
|
+
expectStatus(hiddenResolve, 200, 'Inaccessible repository binding resolution');
|
|
544
|
+
invariant(same(decodeResolveRepositoryCubeResponseEnvelope(hiddenResolve.body).payload, { result: 'none' }), 'Inaccessible repository binding was exposed by read-only resolution.');
|
|
545
|
+
assertStateDelta(beforeHiddenResolve, await environment.admin.observeAuthorityState(), {}, 'Inaccessible repository binding resolution');
|
|
546
|
+
const beforeHiddenAssociation = await environment.admin.observeAuthorityState();
|
|
547
|
+
expectSecretFreeError(await environment.operations.associateRepositoryCube(credentialA, createProtocolEnvelope('repository-associate-inaccessible-binding', {
|
|
548
|
+
...associationRequest,
|
|
549
|
+
cube_id: cubeC.id,
|
|
550
|
+
})), 403, ErrorCode.ACCESS_DENIED, 'Inaccessible existing repository binding', [cubeA.id, cubeC.id, repository.value]);
|
|
551
|
+
assertStateDelta(beforeHiddenAssociation, await environment.admin.observeAuthorityState(), {}, 'Inaccessible existing repository binding');
|
|
552
|
+
await environment.admin.grantCube(principalA, cubeA);
|
|
553
|
+
return {
|
|
554
|
+
none: true,
|
|
555
|
+
resolved: true,
|
|
556
|
+
idempotent: true,
|
|
557
|
+
repository_conflict: ErrorCode.REPOSITORY_ALREADY_ASSOCIATED,
|
|
558
|
+
cube_conflict: ErrorCode.CUBE_ALREADY_ASSOCIATED,
|
|
559
|
+
inaccessible: ErrorCode.ACCESS_DENIED,
|
|
560
|
+
inaccessible_binding_hidden: true,
|
|
561
|
+
cross_client_binding_hidden: true,
|
|
562
|
+
};
|
|
563
|
+
});
|
|
397
564
|
await record('security.adapter-boundary-injection', async () => {
|
|
398
565
|
const injectedMessage = "'); DROP TABLE log_entries; --\r\ndata: forged-sse-frame";
|
|
399
566
|
const injectedBody = JSON.stringify(createProtocolEnvelope('inject-b1', { message: injectedMessage }));
|