borgmcp-shared 0.6.2 → 0.6.3
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 +11 -7
- package/dist/conformance/adapter.d.ts +9 -1
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +65 -16
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +34 -1
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +87 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +16 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +74 -5
- package/dist/protocol/contract.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 +8 -1
- package/docs/enrollment.md +54 -24
- package/docs/releasing.md +23 -22
- package/package.json +1 -1
- package/src/conformance/adapter.ts +137 -22
- package/src/conformance/index.ts +125 -1
- package/src/protocol/contract.ts +97 -11
- package/src/protocol/version.ts +2 -2
- package/src/templates.ts +20 -3
package/docs/enrollment.md
CHANGED
|
@@ -18,11 +18,11 @@ incident is bound to tag object `045268aa8873da330819860012ecaddb4bc2883c`, prot
|
|
|
18
18
|
`90a1cf686a0ce32a7aef836b0b82a930191b9030` peels to protected-main commit
|
|
19
19
|
`fd69b08586481a60c88099dede8e4e066f73f2f2`; attempt-1 workflow run
|
|
20
20
|
`30054936226` failed in tests before build, packaging, authentication, or registry
|
|
21
|
-
mutation and must never be rerun or moved. `borgmcp-shared@0.6.1`
|
|
22
|
-
and immutable. This source now identifies
|
|
23
|
-
|
|
24
|
-
authority: creating `v0.6.
|
|
25
|
-
independently gated steps.
|
|
21
|
+
mutation and must never be rerun or moved. `borgmcp-shared@0.6.1` and
|
|
22
|
+
`borgmcp-shared@0.6.2` are published and immutable. This source now identifies
|
|
23
|
+
the reviewed, unpublished `0.6.3` protocol-v4 cube-creation contract release.
|
|
24
|
+
The version bump grants no tag or publication authority: creating `v0.6.3` and
|
|
25
|
+
publishing the reviewed artifact remain separate, independently gated steps.
|
|
26
26
|
There is no compatibility path that returns a bearer from the server.
|
|
27
27
|
|
|
28
28
|
A client verifies the credential-free `GET /api/protocol` tag preflight before
|
|
@@ -128,43 +128,67 @@ enrollment and attach routes never create or widen grants.
|
|
|
128
128
|
|
|
129
129
|
## Cube Creation
|
|
130
130
|
|
|
131
|
-
`POST /api/cubes` requires an active parent-client credential
|
|
132
|
-
`create_cube` server capability. Ordinary clients, revoked
|
|
133
|
-
sessions cannot use it. Its strict payload is:
|
|
131
|
+
Under protocol v4, `POST /api/cubes` requires an active parent-client credential
|
|
132
|
+
with the persisted `create_cube` server capability. Ordinary clients, revoked
|
|
133
|
+
clients, and drone sessions cannot use it. Its strict payload is:
|
|
134
134
|
|
|
135
135
|
```json
|
|
136
136
|
{
|
|
137
137
|
"retry_key": "00000000-0000-4000-8000-000000000201",
|
|
138
|
-
"name": "
|
|
139
|
-
"
|
|
138
|
+
"name": "My Repository Cube",
|
|
139
|
+
"working_repo_name": "repository-display-name",
|
|
140
|
+
"repository": {
|
|
141
|
+
"kind": "origin",
|
|
142
|
+
"value": "https://github.com/owner/repository"
|
|
143
|
+
},
|
|
144
|
+
"template": "software-dev"
|
|
140
145
|
}
|
|
141
146
|
```
|
|
142
147
|
|
|
143
148
|
The client persists the pending retry tuple before network I/O and reuses it
|
|
144
|
-
after ambiguous transport failure. `name` is bounded
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
after ambiguous transport failure. `name` is the bounded user-selected cube
|
|
150
|
+
name. `working_repo_name` is derived repository display metadata and does not
|
|
151
|
+
identify or authorize the repository. `repository` is either an exact canonical
|
|
152
|
+
public origin or a client-generated opaque local UUID. `template` accepts
|
|
153
|
+
`software-dev`, `starter`, or the compatible legacy `default` seed. The request
|
|
154
|
+
cannot supply cube or role IDs, an owner, access, grant target, capability,
|
|
155
|
+
arbitrary template data, local paths, raw origins, modules, commands, or
|
|
156
|
+
repository credentials.
|
|
157
|
+
|
|
158
|
+
The server binds `(authenticated client, retry_key)` to the exact cube name,
|
|
159
|
+
repository identity, and template. A successful transaction atomically creates
|
|
160
|
+
one cube, the selected template directive, roles and flags, message taxonomy,
|
|
161
|
+
and exactly one creator `manage` grant. The legacy `default` input retains its
|
|
162
|
+
empty directive, platform Coordinator, default Builder, and no taxonomy. The
|
|
163
|
+
authoritative response is:
|
|
153
164
|
|
|
154
165
|
```json
|
|
155
166
|
{
|
|
167
|
+
"result": "created",
|
|
156
168
|
"cube_id": "<canonical UUID>",
|
|
169
|
+
"name": "My Repository Cube",
|
|
170
|
+
"working_repo_name": "repository-display-name",
|
|
171
|
+
"repository": {
|
|
172
|
+
"kind": "origin",
|
|
173
|
+
"value": "https://github.com/owner/repository"
|
|
174
|
+
},
|
|
175
|
+
"template": "software-dev",
|
|
157
176
|
"human_seat_role_id": "<canonical UUID>",
|
|
158
177
|
"default_worker_role_id": "<canonical UUID>",
|
|
159
178
|
"access": "manage"
|
|
160
179
|
}
|
|
161
180
|
```
|
|
162
181
|
|
|
163
|
-
An exact retry returns
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
An exact retry returns `result: "resolved"` with the stored authoritative fields
|
|
183
|
+
and no mutation. A fresh retry key for the same creator-scoped repository
|
|
184
|
+
association also resolves that stored cube, name, repository display, and
|
|
185
|
+
template without mutation. Changed `working_repo_name` input does not overwrite
|
|
186
|
+
stored display metadata. Reusing a bound retry key with a different cube name,
|
|
187
|
+
repository identity, or template returns non-enumerating HTTP `409`
|
|
188
|
+
`INVALID_INPUT` and creates nothing. A fresh retry for a different, unassociated
|
|
189
|
+
repository may create another cube, subject to implementation quotas. `owner_id`
|
|
190
|
+
and role labels remain metadata; cube access derives only from the explicit
|
|
191
|
+
cube-scoped grant.
|
|
168
192
|
|
|
169
193
|
## Conformance
|
|
170
194
|
|
|
@@ -178,3 +202,9 @@ every retry vector, observes client/capability/cube/role/grant counts, verifies
|
|
|
178
202
|
secret-free errors, and proves authorized idempotent cube creation. Hostile
|
|
179
203
|
reference adapters demonstrate that each retry, authority, and idempotency
|
|
180
204
|
violation fails conformance.
|
|
205
|
+
|
|
206
|
+
`CREATE_CUBE_RETRY_CONFORMANCE` independently pins exact resolution, stored
|
|
207
|
+
display readback, and mismatch rejection for cube name, repository kind/value,
|
|
208
|
+
and template. `CREATE_CUBE_ASSOCIATION_CONFORMANCE` distinguishes no-mutation
|
|
209
|
+
resolution of an existing repository association from creation for a different,
|
|
210
|
+
unassociated repository.
|
package/docs/releasing.md
CHANGED
|
@@ -20,16 +20,16 @@ The release lane has one build, test, package, and publication authority:
|
|
|
20
20
|
4. rejects an existing immutable version or a package not owned solely by the
|
|
21
21
|
configured npm owner before any registry mutation;
|
|
22
22
|
5. publishes that exact local tarball through npm Trusted Publishing with
|
|
23
|
-
provenance
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
with scripts disabled and runs `npm audit signatures`.
|
|
23
|
+
provenance. Successful completion of `npm publish` is the workflow's terminal
|
|
24
|
+
release boundary; no post-publication registry readback can fail the immutable
|
|
25
|
+
release after npm has accepted it.
|
|
27
26
|
|
|
28
|
-
npm and GitHub
|
|
29
|
-
repository does not reconstruct
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
npm and GitHub produce the registry signature and publish attestation as part of
|
|
28
|
+
Trusted Publishing. The repository does not reconstruct or immediately read back
|
|
29
|
+
those records, reconstruct DSSE or SLSA statements, transfer approval tuples
|
|
30
|
+
between runs, rebuild in a second job, or place checksum, SBOM, and report bundles
|
|
31
|
+
on the critical publication path. SBOM portability remains covered by CI and the
|
|
32
|
+
repository's dedicated deterministic SBOM tests.
|
|
33
33
|
|
|
34
34
|
## Permanent Configuration
|
|
35
35
|
|
|
@@ -77,8 +77,9 @@ repository, workflow output, artifact, issue, or shell history.
|
|
|
77
77
|
tag gets one first attempt.
|
|
78
78
|
5. Obtain the separately required environment approval, then approve that exact
|
|
79
79
|
pending job. Approval does not permit a local rebuild or alternate artifact.
|
|
80
|
-
6. Require
|
|
81
|
-
|
|
80
|
+
6. Require the protected publish job to complete successfully before announcing
|
|
81
|
+
the version or updating consumers. Registry propagation and later consumer
|
|
82
|
+
availability are operational observations, not release-workflow gates.
|
|
82
83
|
|
|
83
84
|
The workflow publishes only `./release/<tarball>`. It never publishes from the
|
|
84
85
|
repository directory, a package name, a URL, a prior workflow artifact, or a
|
|
@@ -96,11 +97,9 @@ and run as immutable evidence. Fix the source and begin a separately reviewed an
|
|
|
96
97
|
authorized version/tag plan. Never move, reuse, rerun, or force-update the failed
|
|
97
98
|
tag.
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
consumer adoption. npm versions are immutable; never overwrite, unpublish, or
|
|
103
|
-
silently substitute a replacement.
|
|
100
|
+
Once npm accepts the version, the immutable release has occurred. Never rerun,
|
|
101
|
+
republish, overwrite, unpublish, or silently substitute a replacement because a
|
|
102
|
+
later registry read is delayed or unavailable.
|
|
104
103
|
|
|
105
104
|
## Immutable Historical Evidence
|
|
106
105
|
|
|
@@ -133,10 +132,12 @@ that tag. `borgmcp-shared@0.6.0` was never published. Its annotated tag object
|
|
|
133
132
|
`90a1cf686a0ce32a7aef836b0b82a930191b9030` peels to protected-main commit
|
|
134
133
|
`fd69b08586481a60c88099dede8e4e066f73f2f2`; attempt-1 workflow run
|
|
135
134
|
`30054936226` failed in tests before build, packaging, authentication, or registry
|
|
136
|
-
mutation. Never rerun or move that tag. `borgmcp-shared@0.6.1`
|
|
137
|
-
immutable. This source identifies the
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
mutation. Never rerun or move that tag. `borgmcp-shared@0.6.1` and
|
|
136
|
+
`borgmcp-shared@0.6.2` are published and immutable. This source identifies the
|
|
137
|
+
reviewed, unpublished `0.6.3` protocol-v4 cube-creation contract release. Its
|
|
138
|
+
release requires reviewed source and explicit publication authorization through
|
|
139
|
+
one protected publish run whose terminal boundary is successful `npm publish`.
|
|
140
|
+
The workflow performs no post-publication registry readback. Consumers
|
|
141
141
|
must update the reviewed shared artifact before adopting the matching server and
|
|
142
|
-
client releases; protocol v2 and
|
|
142
|
+
client releases; incompatible protocol v2, v3, and v4 peers fail closed at
|
|
143
|
+
credential-free preflight.
|
package/package.json
CHANGED
|
@@ -22,11 +22,17 @@ import {
|
|
|
22
22
|
PROTOCOL_VERSION,
|
|
23
23
|
utf8ByteLength,
|
|
24
24
|
type CreateCubeResponse,
|
|
25
|
+
type CreateCubeRepository,
|
|
26
|
+
type CubeTemplate,
|
|
25
27
|
type LogCursor,
|
|
26
28
|
type StreamEvent,
|
|
27
29
|
type DroneRuntimeMetadata,
|
|
28
30
|
} from '../protocol/index.js';
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
CREATE_CUBE_ASSOCIATION_CONFORMANCE,
|
|
33
|
+
CREATE_CUBE_RETRY_CONFORMANCE,
|
|
34
|
+
ENROLLMENT_RETRY_CONFORMANCE,
|
|
35
|
+
} from './index.js';
|
|
30
36
|
|
|
31
37
|
export interface ConformanceHttpResponse {
|
|
32
38
|
status: number;
|
|
@@ -102,13 +108,21 @@ export interface ConformanceAuthorityState {
|
|
|
102
108
|
grants: number;
|
|
103
109
|
server_capabilities: number;
|
|
104
110
|
cube_create_bindings: number;
|
|
111
|
+
repository_associations: number;
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
export interface ConformanceCreatedCubeState {
|
|
108
115
|
cube_exists: boolean;
|
|
109
116
|
creator_has_grant: boolean;
|
|
117
|
+
creator_access: 'manage' | null;
|
|
110
118
|
grant_count: number;
|
|
111
119
|
role_count: number;
|
|
120
|
+
name: string;
|
|
121
|
+
working_repo_name: string;
|
|
122
|
+
repository: CreateCubeRepository;
|
|
123
|
+
template: CubeTemplate;
|
|
124
|
+
human_seat_role_id: string;
|
|
125
|
+
default_worker_role_id: string;
|
|
112
126
|
human_seat_role_matches: boolean;
|
|
113
127
|
default_worker_role_matches: boolean;
|
|
114
128
|
}
|
|
@@ -686,7 +700,12 @@ export async function runAdapterConformance(
|
|
|
686
700
|
|
|
687
701
|
const cubeRequest = {
|
|
688
702
|
retry_key: '00000000-0000-4000-8000-000000000213',
|
|
689
|
-
name: '
|
|
703
|
+
name: 'Repository One',
|
|
704
|
+
working_repo_name: 'repository-one',
|
|
705
|
+
repository: {
|
|
706
|
+
kind: 'origin' as const,
|
|
707
|
+
value: 'https://github.com/Byte-Ventures/repository-one',
|
|
708
|
+
},
|
|
690
709
|
template: 'default',
|
|
691
710
|
};
|
|
692
711
|
const droneCredential = await environment.admin.issueDroneSession(ownerPrincipal);
|
|
@@ -724,13 +743,25 @@ export async function runAdapterConformance(
|
|
|
724
743
|
const createdResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-create', cubeRequest));
|
|
725
744
|
expectStatus(createdResponse, 201, 'Owner cube create');
|
|
726
745
|
const created = decodeCreateCubeResponseEnvelope(createdResponse.body).payload;
|
|
727
|
-
assertStateDelta(
|
|
746
|
+
assertStateDelta(
|
|
747
|
+
beforeCreate,
|
|
748
|
+
await environment.admin.observeAuthorityState(),
|
|
749
|
+
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 },
|
|
750
|
+
'Owner cube create',
|
|
751
|
+
);
|
|
728
752
|
invariant(
|
|
729
753
|
same(await environment.admin.inspectCreatedCube(ownerPrincipal, created), {
|
|
730
754
|
cube_exists: true,
|
|
731
755
|
creator_has_grant: true,
|
|
756
|
+
creator_access: 'manage',
|
|
732
757
|
grant_count: 1,
|
|
733
758
|
role_count: 2,
|
|
759
|
+
name: cubeRequest.name,
|
|
760
|
+
working_repo_name: cubeRequest.working_repo_name,
|
|
761
|
+
repository: cubeRequest.repository,
|
|
762
|
+
template: cubeRequest.template,
|
|
763
|
+
human_seat_role_id: created.human_seat_role_id,
|
|
764
|
+
default_worker_role_id: created.default_worker_role_id,
|
|
734
765
|
human_seat_role_matches: true,
|
|
735
766
|
default_worker_role_matches: true,
|
|
736
767
|
}),
|
|
@@ -739,19 +770,92 @@ export async function runAdapterConformance(
|
|
|
739
770
|
const beforeCreateRetry = await environment.admin.observeAuthorityState();
|
|
740
771
|
const retriedCreateResponse = await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-retry', cubeRequest));
|
|
741
772
|
expectStatus(retriedCreateResponse, 201, 'Exact cube-create retry');
|
|
742
|
-
|
|
773
|
+
const retriedCreate = decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload;
|
|
774
|
+
invariant(retriedCreate.result === 'resolved', 'Exact cube-create retry did not report resolved readback.');
|
|
775
|
+
invariant(
|
|
776
|
+
same({ ...retriedCreate, result: 'created' }, created),
|
|
777
|
+
'Exact cube-create retry returned different authoritative fields.',
|
|
778
|
+
);
|
|
743
779
|
assertStateDelta(beforeCreateRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cube-create retry');
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
780
|
+
const displayRetryVector = CREATE_CUBE_RETRY_CONFORMANCE.find(
|
|
781
|
+
(vector) => vector.expected.outcome === 'resolved_response' &&
|
|
782
|
+
vector.retry.working_repo_name !== vector.initial.working_repo_name,
|
|
783
|
+
);
|
|
784
|
+
invariant(displayRetryVector !== undefined, 'Missing repository display readback vector.');
|
|
785
|
+
const beforeDisplayRetry = await environment.admin.observeAuthorityState();
|
|
786
|
+
const displayRetryResponse = await environment.operations.createCube(
|
|
787
|
+
ownerCredential,
|
|
788
|
+
createProtocolEnvelope('cube-display-retry', {
|
|
789
|
+
...displayRetryVector.retry,
|
|
790
|
+
retry_key: cubeRequest.retry_key,
|
|
791
|
+
}),
|
|
792
|
+
);
|
|
793
|
+
expectStatus(displayRetryResponse, 201, 'Changed repository display retry');
|
|
794
|
+
invariant(
|
|
795
|
+
same(decodeCreateCubeResponseEnvelope(displayRetryResponse.body).payload, retriedCreate),
|
|
796
|
+
'Changed repository display retry did not return stored authoritative display.',
|
|
797
|
+
);
|
|
798
|
+
assertStateDelta(
|
|
799
|
+
beforeDisplayRetry,
|
|
800
|
+
await environment.admin.observeAuthorityState(),
|
|
801
|
+
{},
|
|
802
|
+
'Changed repository display retry',
|
|
803
|
+
);
|
|
804
|
+
for (const [index, vector] of CREATE_CUBE_RETRY_CONFORMANCE.entries()) {
|
|
805
|
+
if (vector.expected.outcome !== 'retry_tuple_mismatch') continue;
|
|
806
|
+
const mismatch = { ...vector.retry, retry_key: cubeRequest.retry_key };
|
|
807
|
+
const beforeCreateMismatch = await environment.admin.observeAuthorityState();
|
|
808
|
+
expectSecretFreeError(
|
|
809
|
+
await environment.operations.createCube(
|
|
810
|
+
ownerCredential,
|
|
811
|
+
createProtocolEnvelope(`cube-mismatch-${index}`, mismatch),
|
|
812
|
+
),
|
|
813
|
+
409,
|
|
814
|
+
ErrorCode.INVALID_INPUT,
|
|
815
|
+
`Cube-create ${vector.name}`,
|
|
816
|
+
[cubeRequest.retry_key],
|
|
817
|
+
);
|
|
818
|
+
assertStateDelta(
|
|
819
|
+
beforeCreateMismatch,
|
|
820
|
+
await environment.admin.observeAuthorityState(),
|
|
821
|
+
{},
|
|
822
|
+
`Cube-create ${vector.name}`,
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
const sameAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
826
|
+
(vector) => vector.expected.outcome === 'resolved',
|
|
827
|
+
);
|
|
828
|
+
invariant(sameAssociationVector !== undefined, 'Missing same-repository association vector.');
|
|
829
|
+
const beforeAssociationResolve = await environment.admin.observeAuthorityState();
|
|
830
|
+
const associationResolveResponse = await environment.operations.createCube(
|
|
831
|
+
ownerCredential,
|
|
832
|
+
createProtocolEnvelope('cube-association-resolve', {
|
|
833
|
+
...sameAssociationVector.request,
|
|
834
|
+
repository: cubeRequest.repository,
|
|
835
|
+
}),
|
|
836
|
+
);
|
|
837
|
+
expectStatus(associationResolveResponse, 201, 'Fresh retry for associated repository');
|
|
838
|
+
const associationResolved = decodeCreateCubeResponseEnvelope(associationResolveResponse.body).payload;
|
|
839
|
+
invariant(associationResolved.result === 'resolved', 'Associated repository did not resolve.');
|
|
840
|
+
invariant(
|
|
841
|
+
same({ ...associationResolved, result: 'created' }, created),
|
|
842
|
+
'Associated repository did not return stored authoritative fields.',
|
|
843
|
+
);
|
|
844
|
+
assertStateDelta(
|
|
845
|
+
beforeAssociationResolve,
|
|
846
|
+
await environment.admin.observeAuthorityState(),
|
|
847
|
+
sameAssociationVector.expected.authority_state_delta,
|
|
848
|
+
'Fresh retry for associated repository',
|
|
751
849
|
);
|
|
752
|
-
assertStateDelta(beforeCreateMismatch, await environment.admin.observeAuthorityState(), {}, 'Cube-create retry mismatch');
|
|
753
850
|
await environment.admin.grantCreateCubeCapability(ordinaryPrincipal);
|
|
754
|
-
const crossClientRequest = {
|
|
851
|
+
const crossClientRequest = {
|
|
852
|
+
...cubeRequest,
|
|
853
|
+
working_repo_name: 'ordinary-repository',
|
|
854
|
+
repository: {
|
|
855
|
+
kind: 'local' as const,
|
|
856
|
+
value: '00000000-0000-4000-8000-000000000216',
|
|
857
|
+
},
|
|
858
|
+
};
|
|
755
859
|
const beforeCrossClientCreate = await environment.admin.observeAuthorityState();
|
|
756
860
|
const crossClientResponse = await environment.operations.createCube(
|
|
757
861
|
ordinaryCredential,
|
|
@@ -763,7 +867,7 @@ export async function runAdapterConformance(
|
|
|
763
867
|
assertStateDelta(
|
|
764
868
|
beforeCrossClientCreate,
|
|
765
869
|
await environment.admin.observeAuthorityState(),
|
|
766
|
-
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 },
|
|
870
|
+
{ cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1, repository_associations: 1 },
|
|
767
871
|
'Cross-client cube create',
|
|
768
872
|
);
|
|
769
873
|
invariant(
|
|
@@ -776,24 +880,35 @@ export async function runAdapterConformance(
|
|
|
776
880
|
createProtocolEnvelope('cube-cross-client-retry', crossClientRequest),
|
|
777
881
|
);
|
|
778
882
|
expectStatus(crossClientRetry, 201, 'Exact cross-client cube-create retry');
|
|
883
|
+
const crossClientResolved = decodeCreateCubeResponseEnvelope(crossClientRetry.body).payload;
|
|
779
884
|
invariant(
|
|
780
|
-
|
|
781
|
-
'Exact cross-client cube-create retry
|
|
885
|
+
crossClientResolved.result === 'resolved',
|
|
886
|
+
'Exact cross-client cube-create retry did not report resolved readback.',
|
|
887
|
+
);
|
|
888
|
+
invariant(
|
|
889
|
+
same(
|
|
890
|
+
{ ...crossClientResolved, result: 'created' },
|
|
891
|
+
crossClientCreated,
|
|
892
|
+
),
|
|
893
|
+
'Exact cross-client cube-create retry returned different authoritative fields.',
|
|
782
894
|
);
|
|
783
895
|
assertStateDelta(beforeCrossClientRetry, await environment.admin.observeAuthorityState(), {}, 'Exact cross-client cube-create retry');
|
|
896
|
+
const differentAssociationVector = CREATE_CUBE_ASSOCIATION_CONFORMANCE.find(
|
|
897
|
+
(vector) => vector.expected.outcome === 'created',
|
|
898
|
+
);
|
|
899
|
+
invariant(differentAssociationVector !== undefined, 'Missing different-repository association vector.');
|
|
784
900
|
const beforeSecondCreate = await environment.admin.observeAuthorityState();
|
|
785
|
-
const secondCreatedResponse = await environment.operations.createCube(
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}));
|
|
901
|
+
const secondCreatedResponse = await environment.operations.createCube(
|
|
902
|
+
ownerCredential,
|
|
903
|
+
createProtocolEnvelope('cube-create-second', differentAssociationVector.request),
|
|
904
|
+
);
|
|
790
905
|
expectStatus(secondCreatedResponse, 201, 'Second cube create');
|
|
791
906
|
const secondCreated = decodeCreateCubeResponseEnvelope(secondCreatedResponse.body).payload;
|
|
792
907
|
invariant(secondCreated.cube_id !== created.cube_id, 'Fresh cube-create retry key reused an existing cube.');
|
|
793
908
|
assertStateDelta(
|
|
794
909
|
beforeSecondCreate,
|
|
795
910
|
await environment.admin.observeAuthorityState(),
|
|
796
|
-
|
|
911
|
+
differentAssociationVector.expected.authority_state_delta,
|
|
797
912
|
'Second cube create',
|
|
798
913
|
);
|
|
799
914
|
await environment.admin.revokePrincipal(ownerPrincipal);
|
package/src/conformance/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { BroadcastHwm } from '../log-stream-hwm.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AttachResponse,
|
|
4
|
+
CreateCubeRequest,
|
|
5
|
+
EnrollmentExchangeRequest,
|
|
6
|
+
} from '../protocol/contract.js';
|
|
3
7
|
|
|
4
8
|
export * from './adapter.js';
|
|
5
9
|
|
|
@@ -167,6 +171,126 @@ export const ENROLLMENT_RETRY_CONFORMANCE: readonly EnrollmentRetryConformanceVe
|
|
|
167
171
|
},
|
|
168
172
|
];
|
|
169
173
|
|
|
174
|
+
export interface CreateCubeRetryConformanceVector {
|
|
175
|
+
name: string;
|
|
176
|
+
initial: CreateCubeRequest;
|
|
177
|
+
retry: CreateCubeRequest;
|
|
178
|
+
expected:
|
|
179
|
+
| { outcome: 'resolved_response'; status: 201 }
|
|
180
|
+
| { outcome: 'retry_tuple_mismatch'; status: 409; error: 'INVALID_INPUT' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const CREATE_CUBE_RETRY_KEY = '00000000-0000-4000-8000-000000000121';
|
|
184
|
+
const CREATE_CUBE_INITIAL: CreateCubeRequest = {
|
|
185
|
+
retry_key: CREATE_CUBE_RETRY_KEY,
|
|
186
|
+
name: 'Repository One',
|
|
187
|
+
working_repo_name: 'repository-one',
|
|
188
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-one' },
|
|
189
|
+
template: 'default',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Stateful vectors: name, repository identity, and template bind the retry key. */
|
|
193
|
+
export const CREATE_CUBE_RETRY_CONFORMANCE: readonly CreateCubeRetryConformanceVector[] = [
|
|
194
|
+
{
|
|
195
|
+
name: 'exact retry resolves the authoritative response',
|
|
196
|
+
initial: CREATE_CUBE_INITIAL,
|
|
197
|
+
retry: CREATE_CUBE_INITIAL,
|
|
198
|
+
expected: { outcome: 'resolved_response', status: 201 },
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'changed repository display metadata resolves stored authoritative display',
|
|
202
|
+
initial: CREATE_CUBE_INITIAL,
|
|
203
|
+
retry: { ...CREATE_CUBE_INITIAL, working_repo_name: 'repository-one-renamed' },
|
|
204
|
+
expected: { outcome: 'resolved_response', status: 201 },
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'cube-name mismatch is rejected',
|
|
208
|
+
initial: CREATE_CUBE_INITIAL,
|
|
209
|
+
retry: { ...CREATE_CUBE_INITIAL, name: 'Repository One Renamed' },
|
|
210
|
+
expected: { outcome: 'retry_tuple_mismatch', status: 409, error: 'INVALID_INPUT' },
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'template mismatch is rejected',
|
|
214
|
+
initial: CREATE_CUBE_INITIAL,
|
|
215
|
+
retry: { ...CREATE_CUBE_INITIAL, template: 'software-dev' },
|
|
216
|
+
expected: { outcome: 'retry_tuple_mismatch', status: 409, error: 'INVALID_INPUT' },
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'repository kind mismatch is rejected',
|
|
220
|
+
initial: CREATE_CUBE_INITIAL,
|
|
221
|
+
retry: {
|
|
222
|
+
...CREATE_CUBE_INITIAL,
|
|
223
|
+
repository: { kind: 'local', value: '00000000-0000-4000-8000-000000000122' },
|
|
224
|
+
},
|
|
225
|
+
expected: { outcome: 'retry_tuple_mismatch', status: 409, error: 'INVALID_INPUT' },
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'repository value mismatch is rejected',
|
|
229
|
+
initial: CREATE_CUBE_INITIAL,
|
|
230
|
+
retry: {
|
|
231
|
+
...CREATE_CUBE_INITIAL,
|
|
232
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-two' },
|
|
233
|
+
},
|
|
234
|
+
expected: { outcome: 'retry_tuple_mismatch', status: 409, error: 'INVALID_INPUT' },
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
export interface CreateCubeAssociationConformanceVector {
|
|
239
|
+
name: string;
|
|
240
|
+
created: CreateCubeRequest;
|
|
241
|
+
request: CreateCubeRequest;
|
|
242
|
+
expected:
|
|
243
|
+
| { outcome: 'resolved'; authority_state_delta: Record<string, never> }
|
|
244
|
+
| {
|
|
245
|
+
outcome: 'created';
|
|
246
|
+
authority_state_delta: {
|
|
247
|
+
cubes: 1;
|
|
248
|
+
roles: 2;
|
|
249
|
+
grants: 1;
|
|
250
|
+
cube_create_bindings: 1;
|
|
251
|
+
repository_associations: 1;
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** A creator-scoped repository association resolves independently of operation retry keys. */
|
|
257
|
+
export const CREATE_CUBE_ASSOCIATION_CONFORMANCE:
|
|
258
|
+
readonly CreateCubeAssociationConformanceVector[] = [
|
|
259
|
+
{
|
|
260
|
+
name: 'fresh retry for the same repository resolves stored authoritative fields',
|
|
261
|
+
created: CREATE_CUBE_INITIAL,
|
|
262
|
+
request: {
|
|
263
|
+
...CREATE_CUBE_INITIAL,
|
|
264
|
+
retry_key: '00000000-0000-4000-8000-000000000123',
|
|
265
|
+
name: 'Ignored New Cube Name',
|
|
266
|
+
working_repo_name: 'ignored-new-display',
|
|
267
|
+
template: 'starter',
|
|
268
|
+
},
|
|
269
|
+
expected: { outcome: 'resolved', authority_state_delta: {} },
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'fresh retry for a different repository may create',
|
|
273
|
+
created: CREATE_CUBE_INITIAL,
|
|
274
|
+
request: {
|
|
275
|
+
...CREATE_CUBE_INITIAL,
|
|
276
|
+
retry_key: '00000000-0000-4000-8000-000000000124',
|
|
277
|
+
name: 'Repository Two',
|
|
278
|
+
working_repo_name: 'repository-two',
|
|
279
|
+
repository: { kind: 'origin', value: 'https://github.com/Byte-Ventures/repository-two' },
|
|
280
|
+
},
|
|
281
|
+
expected: {
|
|
282
|
+
outcome: 'created',
|
|
283
|
+
authority_state_delta: {
|
|
284
|
+
cubes: 1,
|
|
285
|
+
roles: 2,
|
|
286
|
+
grants: 1,
|
|
287
|
+
cube_create_bindings: 1,
|
|
288
|
+
repository_associations: 1,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
];
|
|
293
|
+
|
|
170
294
|
export const ENROLLMENT_AUTHORITY_CONFORMANCE = [
|
|
171
295
|
{
|
|
172
296
|
name: 'ordinary enrollment creates no authority or cube state',
|