borgmcp-shared 0.6.1 → 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.
@@ -18,10 +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. This source now identifies the
22
- unpublished `0.6.1` runtime-metadata contract release. The version bump grants no
23
- tag or publication authority: creating `v0.6.1` and publishing the reviewed artifact remain separate,
24
- 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.
25
26
  There is no compatibility path that returns a bearer from the server.
26
27
 
27
28
  A client verifies the credential-free `GET /api/protocol` tag preflight before
@@ -127,43 +128,67 @@ enrollment and attach routes never create or widen grants.
127
128
 
128
129
  ## Cube Creation
129
130
 
130
- `POST /api/cubes` requires an active parent-client credential with the persisted
131
- `create_cube` server capability. Ordinary clients, revoked clients, and drone
132
- 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:
133
134
 
134
135
  ```json
135
136
  {
136
137
  "retry_key": "00000000-0000-4000-8000-000000000201",
137
- "name": "repository presentation name",
138
- "template": "default"
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"
139
145
  }
140
146
  ```
141
147
 
142
148
  The client persists the pending retry tuple before network I/O and reuses it
143
- after ambiguous transport failure. `name` is bounded presentation data and
144
- `template` selects allowlisted, server-owned inert data. The request cannot
145
- supply cube or role IDs, an owner, access, grant target, capability, arbitrary
146
- template data, paths, URLs, modules, commands, or repository credentials.
147
-
148
- The server binds `(authenticated client, retry_key)` to the exact canonical name
149
- and template. A successful transaction atomically creates one cube, exactly one
150
- human-seat role, exactly one default worker role, and exactly one creator
151
- `manage` grant. It returns:
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:
152
164
 
153
165
  ```json
154
166
  {
167
+ "result": "created",
155
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",
156
176
  "human_seat_role_id": "<canonical UUID>",
157
177
  "default_worker_role_id": "<canonical UUID>",
158
178
  "access": "manage"
159
179
  }
160
180
  ```
161
181
 
162
- An exact retry returns that stable response without mutation. Reusing the same
163
- retry key with a different name or template returns non-enumerating HTTP `409`
164
- `INVALID_INPUT` and creates nothing. A fresh retry key may create another cube,
165
- subject to implementation quotas. `owner_id` and role labels remain metadata;
166
- cube access derives only from the explicit cube-scoped grant.
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.
167
192
 
168
193
  ## Conformance
169
194
 
@@ -177,3 +202,9 @@ every retry vector, observes client/capability/cube/role/grant counts, verifies
177
202
  secret-free errors, and proves authorized idempotent cube creation. Hostile
178
203
  reference adapters demonstrate that each retry, authority, and idempotency
179
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; and
24
- 6. uses bounded registry-propagation retries to compare the published
25
- `dist.integrity` with the tarball, then installs the exact registry version
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 verify the registry signature and publish attestation. The
29
- repository does not reconstruct DSSE or SLSA statements, transfer approval
30
- tuples between runs, rebuild in a second job, or place checksum, SBOM, and report
31
- bundles on the critical publication path. SBOM portability remains covered by
32
- CI and the repository's dedicated deterministic SBOM tests.
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 every job step to pass, including exact registry integrity and
81
- `npm audit signatures`, before announcing the version or updating consumers.
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
- If npm accepts the version but a bounded integrity or signature check fails, do
100
- not rerun or republish. Preserve the run, inspect the live registry integrity and
101
- `npm audit signatures` result, and require an explicit recovery decision before
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,9 +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. This source identifies the unpublished
137
- `0.6.1` runtime-metadata contract release. Its release requires reviewed source and explicit publication authorization
138
- through one protected publish run, followed by postpublish registry integrity,
139
- signature, and attestation verification. Consumers
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
140
141
  must update the reviewed shared artifact before adopting the matching server and
141
- client releases; protocol v2 and v3 peers fail closed at credential-free preflight.
142
+ client releases; incompatible protocol v2, v3, and v4 peers fail closed at
143
+ credential-free preflight.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp-shared",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Shared protocol, domain, templates, and conformance contracts for Borg MCP implementations.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 { ENROLLMENT_RETRY_CONFORMANCE } from './index.js';
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: 'repository-one',
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(beforeCreate, await environment.admin.observeAuthorityState(), { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 }, 'Owner cube create');
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
- invariant(same(decodeCreateCubeResponseEnvelope(retriedCreateResponse.body).payload, created), 'Exact cube-create retry returned different identities.');
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 beforeCreateMismatch = await environment.admin.observeAuthorityState();
745
- expectSecretFreeError(
746
- await environment.operations.createCube(ownerCredential, createProtocolEnvelope('cube-mismatch', { ...cubeRequest, name: 'repository-two' })),
747
- 409,
748
- ErrorCode.INVALID_INPUT,
749
- 'Cube-create retry mismatch',
750
- [cubeRequest.retry_key],
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 = { ...cubeRequest, name: 'ordinary-repository' };
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
- same(decodeCreateCubeResponseEnvelope(crossClientRetry.body).payload, crossClientCreated),
781
- 'Exact cross-client cube-create retry returned different identities.',
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(ownerCredential, createProtocolEnvelope('cube-create-second', {
786
- ...cubeRequest,
787
- retry_key: '00000000-0000-4000-8000-000000000214',
788
- name: 'repository-two',
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
- { cubes: 1, roles: 2, grants: 1, cube_create_bindings: 1 },
911
+ differentAssociationVector.expected.authority_state_delta,
797
912
  'Second cube create',
798
913
  );
799
914
  await environment.admin.revokePrincipal(ownerPrincipal);
@@ -1,5 +1,9 @@
1
1
  import type { BroadcastHwm } from '../log-stream-hwm.js';
2
- import type { AttachResponse, EnrollmentExchangeRequest } from '../protocol/contract.js';
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',