borgmcp-shared 0.4.0 → 0.4.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 CHANGED
@@ -88,8 +88,14 @@ Every JSON coordination request and successful JSON response is carried inside
88
88
  exceptions are the `204` liveness and acknowledgement responses. Payload codecs
89
89
  are exported separately so adapters can validate the envelope first and then
90
90
  validate the operation-specific payload without accepting ambiguous fields.
91
+ Cube managers reassign a seat with `PATCH /api/cubes/:cubeId/drones/:droneId`
92
+ and evict one with `DELETE` on the same path. Both operations use strict
93
+ versioned request and success envelopes. An evicted seat's former bearer receives
94
+ the terminal `410 DRONE_EVICTED` signal; revoked sessions return
95
+ `401 SESSION_REVOKED`, while expired sessions return the only recoverable
96
+ authentication outcome, `401 AUTH_EXPIRED`.
91
97
  See [docs/enrollment.md](docs/enrollment.md) for purpose-bound owner enrollment,
92
- ordinary ungranted enrollment, cube creation, pending-keychain, and retry contracts.
98
+ ordinary ungranted enrollment, cube creation, pending enrollment, and retry contracts.
93
99
 
94
100
  ## Conformance
95
101
 
@@ -99,14 +105,19 @@ readonly data rather than Vitest-specific helpers, so they work with any test
99
105
  runner and in any JavaScript runtime. Cases cover HTTP and canonical errors,
100
106
  credential misuse, isolation and revocation, SSE framing/replay/cursor ordering,
101
107
  executable enrollment authority/retry/mismatch/redaction and cube-create
102
- idempotency, acks, claims, and decisions.
108
+ idempotency, acks, claims, decisions, cube-scoped drone reassignment, role-class
109
+ and single-seat invariants, eviction exclusion, and terminal bearer signaling.
110
+ Manage-scoped cube, role, taxonomy, decision, and drone operations also share an
111
+ authority matrix: managing parents may mutate; known same-cube read/write
112
+ parents receive `403 ACCESS_DENIED`; drone sessions remain non-managing; and
113
+ no-grant, foreign, or unknown cubes remain hidden behind `404 NOT_FOUND`.
103
114
 
104
115
  Implement `AdapterConformanceDriver` with raw responses from the target adapter,
105
116
  then call `runAdapterConformance`. The runner creates and decodes envelopes,
106
117
  drives state transitions, and decides pass/fail; adapters do not submit expected
107
- results. `runEquivalentAdapterConformance(cloud, local)` executes the identical
108
- sequence against both implementations and fails when either implementation or
109
- their normalized observable transcripts diverge.
118
+ results. Each server or client adapter runs this single portable suite against
119
+ its local/self-hosted implementation. The package does not define a second
120
+ authority, migration target, or fallback implementation.
110
121
 
111
122
  The package's own suite covers built-in templates, role-section patching,
112
123
  broadcast high-water-mark ordering, drone-address formatting, and current public
@@ -126,17 +137,19 @@ the coordinated rollout order for introducing protocol changes.
126
137
 
127
138
  ## Distribution
128
139
 
129
- `borgmcp-shared@0.3.0` is the published v1 baseline on the registry (its latest
130
- release) and is immutable. This source now identifies the still-unpublished
131
- `0.4.0` candidate for the breaking clean-slate v2 contract — its package
132
- manifest, lockfile, exported identity, and the SBOM / packed-artifact verifiers
133
- all read `0.4.0`. This reviewed version bump grants no tag or publish authority
134
- and must not republish `0.3.0`; creating the annotated `v0.4.0` tag and the
135
- registry publication remain separate, independently gated steps. After that
136
- separately authorized registry release, consumers adopting this contract pin
137
- `borgmcp-shared@^0.4.0` and commit registry lockfiles. No registry token belongs
138
- in this repository, package metadata, lockfiles, or a committed `.npmrc`;
139
- publishing uses protected external credentials and provenance.
140
+ `borgmcp-shared@0.4.0` is the published clean-slate v2 baseline and is
141
+ immutable, but its artifact predates the local/self-hosted package cleanup. The
142
+ immutable `v0.4.1` verification artifact exposed a platform-dependent SBOM audit
143
+ and must never be published, moved, reused, rerun, or substituted.
144
+ `borgmcp-shared@0.4.2` is published and immutable. This source now identifies
145
+ the unpublished `0.4.3` session-lifecycle contract release: expired credentials
146
+ return `AUTH_EXPIRED`, while explicitly revoked credentials return `SESSION_REVOKED`.
147
+ This reviewed version bump grants no tag or publish authority; creating the
148
+ annotated `v0.4.3` tag and publishing its exact reviewed artifact remain separate,
149
+ independently gated steps. After that release, consumers pin exact
150
+ `borgmcp-shared@0.4.3` and commit registry lockfiles. No registry token belongs in
151
+ this repository, package metadata, lockfiles, or a committed `.npmrc`; publishing
152
+ uses protected external credentials and provenance.
140
153
 
141
154
  The first client and server releases must consume the reviewed registry release,
142
155
  not a Git, tag, or local-path dependency. Public release requires the
package/SECURITY.md CHANGED
@@ -6,10 +6,10 @@ Please report suspected vulnerabilities privately through GitHub Security
6
6
  Advisories for this repository. Open the repository's **Security** tab, select
7
7
  **Advisories**, and choose **Report a vulnerability**.
8
8
 
9
- If GitHub's private reporting interface is unavailable, email
10
- `queen@borgmcp.ai`. Do not include credentials, access tokens, customer data,
11
- or other secrets unless the maintainers explicitly request them through an
12
- encrypted channel.
9
+ If GitHub's private reporting interface is unavailable, do not open a public
10
+ issue containing vulnerability details. Wait until private repository reporting
11
+ is available. Do not include credentials, access tokens, user data, or other
12
+ secrets unless the maintainers explicitly request them through a secure channel.
13
13
 
14
14
  Do not disclose the issue publicly until the maintainers confirm that a fix is
15
15
  available and coordinated disclosure is appropriate.
@@ -18,7 +18,8 @@ available and coordinated disclosure is appropriate.
18
18
 
19
19
  Security reports may concern this package's published artifacts, protocol
20
20
  contracts, conformance data, dependency chain, or documentation. Vulnerabilities
21
- in hosted Borg MCP services can be reported through the same channels.
21
+ in the client or self-hosted server belong in those repositories' private
22
+ security-reporting channels.
22
23
 
23
24
  Only supported package versions receive security fixes. The latest published
24
25
  version is always supported; older versions may receive fixes when a safe
@@ -9,6 +9,25 @@ export interface ConformancePrincipal {
9
9
  export interface ConformanceCube {
10
10
  readonly id: string;
11
11
  }
12
+ export interface ConformanceRole {
13
+ readonly id: string;
14
+ }
15
+ export interface ConformanceDrone {
16
+ readonly id: string;
17
+ }
18
+ export type ConformanceCubeAccess = 'read' | 'write' | 'manage';
19
+ export interface ConformanceCubeManagementState {
20
+ readonly directive: string;
21
+ readonly taxonomy_marker: string | null;
22
+ readonly role_ids: readonly string[];
23
+ readonly active_decision_ids: readonly string[];
24
+ readonly drones: ReadonlyArray<{
25
+ readonly id: string;
26
+ readonly role_id: string;
27
+ readonly evicted: boolean;
28
+ readonly session_revoked: boolean;
29
+ }>;
30
+ }
12
31
  export interface ConformanceAuthorityState {
13
32
  enrolled_clients: number;
14
33
  enrollment_claims: number;
@@ -42,7 +61,21 @@ export interface ConformanceAdmin {
42
61
  reset(): Promise<void>;
43
62
  createPrincipal(name: string): Promise<ConformancePrincipal>;
44
63
  createCube(name: string): Promise<ConformanceCube>;
45
- grantCube(principal: ConformancePrincipal, cube: ConformanceCube): Promise<void>;
64
+ grantCube(principal: ConformancePrincipal, cube: ConformanceCube, access?: ConformanceCubeAccess): Promise<void>;
65
+ createRole(cube: ConformanceCube, input: {
66
+ readonly roleClass: 'queen' | 'worker';
67
+ readonly isHumanSeat: boolean;
68
+ }): Promise<ConformanceRole>;
69
+ createDrone(principal: ConformancePrincipal, cube: ConformanceCube, role: ConformanceRole): Promise<ConformanceDrone>;
70
+ issueManagedDroneSession(drone: ConformanceDrone): Promise<string>;
71
+ revokeManagedDroneSession(drone: ConformanceDrone): Promise<void>;
72
+ expireManagedDroneSession(drone: ConformanceDrone): Promise<void>;
73
+ inspectManagedDrone(drone: ConformanceDrone): Promise<{
74
+ readonly role_id: string;
75
+ readonly evicted: boolean;
76
+ readonly session_revoked: boolean;
77
+ }>;
78
+ inspectCubeManagementState(cube: ConformanceCube): Promise<ConformanceCubeManagementState>;
46
79
  grantCreateCubeCapability(principal: ConformancePrincipal): Promise<void>;
47
80
  issueDroneSession(principal: ConformancePrincipal): Promise<string>;
48
81
  issueSingleUseInvitation(principal: ConformancePrincipal, purpose: 'owner' | 'client'): Promise<string>;
@@ -62,8 +95,14 @@ export interface ConformanceOperations {
62
95
  appendRaw(credential: string, cube: ConformanceCube, body: string): Promise<ConformanceHttpResponse>;
63
96
  read(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
64
97
  ack(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
98
+ updateCube(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
99
+ createRole(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
100
+ patchTaxonomy(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
65
101
  recordDecision(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
66
102
  listDecisions(credential: string, cube: ConformanceCube, request: unknown): Promise<ConformanceHttpResponse>;
103
+ listDrones(credential: string, cube: ConformanceCube): Promise<ConformanceHttpResponse>;
104
+ reassignDrone(credential: string, cube: ConformanceCube, drone: ConformanceDrone, request: unknown): Promise<ConformanceHttpResponse>;
105
+ evictDrone(credential: string, cube: ConformanceCube, drone: ConformanceDrone, request: unknown): Promise<ConformanceHttpResponse>;
67
106
  openStream(credential: string, cube: ConformanceCube, cursor: LogCursor | null): Promise<ConformanceStreamResponse>;
68
107
  }
69
108
  export interface ConformanceEnvironment {
@@ -106,6 +145,24 @@ export declare const ADAPTER_CONFORMANCE_FIXTURES: readonly [{
106
145
  }, {
107
146
  readonly id: "decisions.topic-supersession";
108
147
  readonly area: "decisions";
148
+ }, {
149
+ readonly id: "security.drone-management-authorization";
150
+ readonly area: "security";
151
+ }, {
152
+ readonly id: "security.manage-access-matrix";
153
+ readonly area: "security";
154
+ }, {
155
+ readonly id: "drones.reassign-invariants";
156
+ readonly area: "drones";
157
+ }, {
158
+ readonly id: "security.cross-cube-drone-management";
159
+ readonly area: "security";
160
+ }, {
161
+ readonly id: "drones.evict-terminal-signal";
162
+ readonly area: "drones";
163
+ }, {
164
+ readonly id: "security.drone-session-rejection-causes";
165
+ readonly area: "security";
109
166
  }, {
110
167
  readonly id: "security.active-stream-revocation";
111
168
  readonly area: "security";
@@ -125,16 +182,9 @@ export interface AdapterConformanceReport {
125
182
  observations: Record<string, unknown>;
126
183
  }>;
127
184
  }
128
- export interface EquivalentAdapterConformanceReport {
129
- ok: boolean;
130
- cloud: AdapterConformanceReport;
131
- local: AdapterConformanceReport;
132
- equivalent: boolean;
133
- }
134
185
  export interface AdapterConformanceOptions {
135
186
  streamDeadlineMs?: number;
136
187
  pendingProbeMs?: number;
137
188
  }
138
189
  export declare function runAdapterConformance(environment: ConformanceEnvironment, options?: AdapterConformanceOptions): Promise<AdapterConformanceReport>;
139
- export declare function runEquivalentAdapterConformance(cloud: ConformanceEnvironment, local: ConformanceEnvironment, options?: AdapterConformanceOptions): Promise<EquivalentAdapterConformanceReport>;
140
190
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/conformance/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/conformance/adapter.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeCreateCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeProtocolErrorEnvelope, decodeProtocolTagPreflight, decodeReadLogResultEnvelope, decodeSseFrames, PROTOCOL_LIMIT_CEILINGS, PROTOCOL_VERSION, utf8ByteLength, } from '../protocol/index.js';
1
+ import { ErrorCode, compareLogCursor, createProtocolEnvelope, decodeCreateCubeResponseEnvelope, decodeAppendLogResultEnvelope, decodeDecisionResultEnvelope, decodeDecisionsResultEnvelope, decodeEnrollmentExchangeResponseEnvelope, decodeEvictDroneResultEnvelope, decodeProtocolEnvelope, decodeProtocolErrorEnvelope, decodeProtocolTagPreflight, decodeReadLogResultEnvelope, decodeReassignDroneResultEnvelope, decodeSseFrames, PROTOCOL_LIMIT_CEILINGS, PROTOCOL_HTTP_CONTRACT, PROTOCOL_VERSION, utf8ByteLength, } from '../protocol/index.js';
2
2
  import { ENROLLMENT_RETRY_CONFORMANCE } from './index.js';
3
3
  export const ADAPTER_CONFORMANCE_FIXTURES = [
4
4
  { id: 'http.unauthenticated-liveness', area: 'http' },
@@ -13,6 +13,12 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
13
13
  { id: 'acks.idempotent', area: 'acks' },
14
14
  { id: 'claims.durable-noncursor', area: 'claims' },
15
15
  { id: 'decisions.topic-supersession', area: 'decisions' },
16
+ { id: 'security.drone-management-authorization', area: 'security' },
17
+ { id: 'security.manage-access-matrix', area: 'security' },
18
+ { id: 'drones.reassign-invariants', area: 'drones' },
19
+ { id: 'security.cross-cube-drone-management', area: 'security' },
20
+ { id: 'drones.evict-terminal-signal', area: 'drones' },
21
+ { id: 'security.drone-session-rejection-causes', area: 'security' },
16
22
  { id: 'security.active-stream-revocation', area: 'security' },
17
23
  ];
18
24
  const DEFAULT_STREAM_DEADLINE_MS = 5_000;
@@ -117,6 +123,19 @@ function logEvent(event, description) {
117
123
  function same(left, right) {
118
124
  return JSON.stringify(left) === JSON.stringify(right);
119
125
  }
126
+ function listedDroneIds(response) {
127
+ return decodeProtocolEnvelope(response.body, (payload) => {
128
+ invariant(typeof payload === 'object' && payload !== null, 'Roster payload is not an object.');
129
+ const drones = payload.drones;
130
+ invariant(Array.isArray(drones), 'Roster payload omitted drones.');
131
+ return drones.map((drone) => {
132
+ invariant(typeof drone === 'object' && drone !== null, 'Roster contains an invalid drone.');
133
+ const id = drone.id;
134
+ invariant(typeof id === 'string', 'Roster drone omitted its id.');
135
+ return id;
136
+ });
137
+ }).payload;
138
+ }
120
139
  function assertStateDelta(before, after, expected, description) {
121
140
  for (const key of Object.keys(before)) {
122
141
  invariant(after[key] - before[key] === (expected[key] ?? 0), `${description} changed ${key} by ${after[key] - before[key]}; expected ${expected[key] ?? 0}.`);
@@ -529,6 +548,246 @@ export async function runAdapterConformance(environment, options = {}) {
529
548
  invariant(second.supersedes === first.id, 'Superseding decision did not reference its predecessor.');
530
549
  return { active_count: 1, active_decision: 'second', supersedes_first: true };
531
550
  });
551
+ let workerRoleA;
552
+ let workerRoleB;
553
+ let managedWorker;
554
+ let readCredential = '';
555
+ let writeCredential = '';
556
+ let workerSession = '';
557
+ await record('security.drone-management-authorization', async () => {
558
+ workerRoleA = await environment.admin.createRole(cubeA, {
559
+ roleClass: 'worker', isHumanSeat: false,
560
+ });
561
+ managedWorker = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
562
+ const deniedCredentials = [];
563
+ for (const [index, access] of ['read', 'write'].entries()) {
564
+ const principalName = access === 'read' ? 'Coordinator' : 'write-principal';
565
+ const principal = await environment.admin.createPrincipal(principalName);
566
+ await environment.admin.grantCube(principal, cubeA, access);
567
+ const invitation = await environment.admin.issueSingleUseInvitation(principal, 'client');
568
+ const credential = `${(access === 'read' ? 'R' : 'W').repeat(42)}Q`;
569
+ if (access === 'read')
570
+ readCredential = credential;
571
+ else
572
+ writeCredential = credential;
573
+ const enrollment = await environment.operations.enroll(createProtocolEnvelope(`${access}-principal-enroll`, {
574
+ invitation,
575
+ retry_key: `00000000-0000-4000-8000-${String(301 + index).padStart(12, '0')}`,
576
+ client_credential: credential,
577
+ client_name: principalName,
578
+ }));
579
+ expectStatus(enrollment, 201, `${access} principal enrollment`);
580
+ deniedCredentials.push({ access: access, credential });
581
+ }
582
+ for (const { access, credential } of deniedCredentials) {
583
+ expectError(await environment.operations.reassignDrone(credential, cubeA, managedWorker, createProtocolEnvelope(`${access}-reassign-denied`, { role_id: workerRoleA.id })), 403, ErrorCode.ACCESS_DENIED, `${access} principal reassignment`);
584
+ expectError(await environment.operations.evictDrone(credential, cubeA, managedWorker, createProtocolEnvelope(`${access}-evict-denied`, {})), 403, ErrorCode.ACCESS_DENIED, `${access} principal eviction`);
585
+ }
586
+ return { read_status: 403, write_status: 403, manage_required: true };
587
+ });
588
+ await record('security.manage-access-matrix', async () => {
589
+ const noGrantPrincipal = await environment.admin.createPrincipal('Coordinator role without cube grant');
590
+ const noGrantInvitation = await environment.admin.issueSingleUseInvitation(noGrantPrincipal, 'client');
591
+ const noGrantCredential = `${'N'.repeat(42)}Q`;
592
+ expectStatus(await environment.operations.enroll(createProtocolEnvelope('no-grant-principal-enroll', {
593
+ invitation: noGrantInvitation,
594
+ retry_key: '00000000-0000-4000-8000-000000000303',
595
+ client_credential: noGrantCredential,
596
+ client_name: 'Coordinator',
597
+ })), 201, 'No-grant principal enrollment');
598
+ const droneCredential = await environment.admin.issueManagedDroneSession(managedWorker);
599
+ const matrixTargetRole = await environment.admin.createRole(cubeA, {
600
+ roleClass: 'worker', isHumanSeat: false,
601
+ });
602
+ const evictionTarget = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
603
+ await environment.admin.issueManagedDroneSession(evictionTarget);
604
+ const unknownCube = { id: '00000000-0000-4000-8000-000000000399' };
605
+ const snapshot = async () => ({
606
+ cubeA: await environment.admin.inspectCubeManagementState(cubeA),
607
+ cubeB: await environment.admin.inspectCubeManagementState(cubeB),
608
+ });
609
+ const operations = [
610
+ {
611
+ name: 'cube-update',
612
+ successStatus: 200,
613
+ invoke: (credential, cube) => environment.operations.updateCube(credential, cube, createProtocolEnvelope('matrix-cube-update', { cube_directive: 'matrix-directive' })),
614
+ },
615
+ {
616
+ name: 'role-create',
617
+ successStatus: 201,
618
+ invoke: (credential, cube) => environment.operations.createRole(credential, cube, createProtocolEnvelope('matrix-role-create', { name: 'Matrix Role' })),
619
+ },
620
+ {
621
+ name: 'taxonomy-patch',
622
+ successStatus: 200,
623
+ invoke: (credential, cube) => environment.operations.patchTaxonomy(credential, cube, createProtocolEnvelope('matrix-taxonomy-patch', { marker: 'matrix-taxonomy' })),
624
+ },
625
+ {
626
+ name: 'decision-record',
627
+ successStatus: 201,
628
+ invoke: (credential, cube) => environment.operations.recordDecision(credential, cube, createProtocolEnvelope('matrix-decision-record', {
629
+ topic: 'matrix-authority', decision: 'manage only',
630
+ })),
631
+ },
632
+ {
633
+ name: 'drone-reassign',
634
+ successStatus: 200,
635
+ invoke: (credential, cube) => environment.operations.reassignDrone(credential, cube, managedWorker, createProtocolEnvelope('matrix-drone-reassign', { role_id: matrixTargetRole.id })),
636
+ },
637
+ {
638
+ name: 'drone-evict',
639
+ successStatus: 200,
640
+ invoke: (credential, cube) => environment.operations.evictDrone(credential, cube, evictionTarget, createProtocolEnvelope('matrix-drone-evict', {})),
641
+ },
642
+ ];
643
+ for (const operation of operations) {
644
+ for (const [kind, credential] of [
645
+ ['read', readCredential],
646
+ ['write', writeCredential],
647
+ ['drone-session', droneCredential],
648
+ ]) {
649
+ const before = await snapshot();
650
+ expectError(await operation.invoke(credential, cubeA), 403, ErrorCode.ACCESS_DENIED, `${operation.name} by ${kind} principal`);
651
+ invariant(same(await snapshot(), before), `${operation.name} mutated state after ${kind} denial.`);
652
+ }
653
+ for (const [kind, credential, cube] of [
654
+ ['no-grant', noGrantCredential, cubeA],
655
+ ['foreign-principal', credentialB, cubeA],
656
+ ['foreign-cube', credentialA, cubeB],
657
+ ['unknown-cube', credentialA, unknownCube],
658
+ ]) {
659
+ const before = await snapshot();
660
+ expectError(await operation.invoke(credential, cube), 404, ErrorCode.NOT_FOUND, `${operation.name} against ${kind}`);
661
+ invariant(same(await snapshot(), before), `${operation.name} mutated state after ${kind} denial.`);
662
+ }
663
+ const beforeSuccess = await environment.admin.inspectCubeManagementState(cubeA);
664
+ const success = await operation.invoke(credentialA, cubeA);
665
+ expectStatus(success, operation.successStatus, `${operation.name} by managing principal`);
666
+ invariant(!same(await environment.admin.inspectCubeManagementState(cubeA), beforeSuccess), `${operation.name} managing success did not mutate its declared state.`);
667
+ }
668
+ return {
669
+ operation_count: operations.length,
670
+ manage_success: true,
671
+ read_write_status: 403,
672
+ drone_session_status: 403,
673
+ hidden_status: 404,
674
+ denied_mutations: 0,
675
+ role_labels_authoritative: false,
676
+ };
677
+ });
678
+ await record('drones.reassign-invariants', async () => {
679
+ workerRoleB = await environment.admin.createRole(cubeA, {
680
+ roleClass: 'worker', isHumanSeat: false,
681
+ });
682
+ const humanSourceRole = await environment.admin.createRole(cubeA, {
683
+ roleClass: 'worker', isHumanSeat: true,
684
+ });
685
+ const queenRole = await environment.admin.createRole(cubeA, {
686
+ roleClass: 'queen', isHumanSeat: false,
687
+ });
688
+ const occupiedHumanRole = await environment.admin.createRole(cubeA, {
689
+ roleClass: 'worker', isHumanSeat: true,
690
+ });
691
+ const humanSource = await environment.admin.createDrone(principalA, cubeA, humanSourceRole);
692
+ await environment.admin.createDrone(principalA, cubeA, occupiedHumanRole);
693
+ const contender = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
694
+ workerSession = await environment.admin.issueManagedDroneSession(managedWorker);
695
+ const reassigned = await environment.operations.reassignDrone(credentialA, cubeA, managedWorker, createProtocolEnvelope('reassign-worker', { role_id: workerRoleB.id }));
696
+ expectStatus(reassigned, 200, 'Worker reassignment');
697
+ const reassignedDrone = decodeReassignDroneResultEnvelope(reassigned.body).payload.drone;
698
+ invariant(reassignedDrone.id === managedWorker.id && reassignedDrone.role_id === workerRoleB.id, 'Reassignment response did not identify the persisted target role.');
699
+ expectError(await environment.operations.reassignDrone(workerSession, cubeA, managedWorker, createProtocolEnvelope('reassign-drone-session-denied', { role_id: workerRoleA.id })), 403, ErrorCode.ACCESS_DENIED, 'Drone-session reassignment');
700
+ expectError(await environment.operations.reassignDrone(credentialA, cubeA, managedWorker, createProtocolEnvelope('reassign-queen-denied', { role_id: queenRole.id })), 403, ErrorCode.ACCESS_DENIED, 'Worker-to-queen reassignment');
701
+ const promoted = await environment.operations.reassignDrone(credentialA, cubeA, humanSource, createProtocolEnvelope('reassign-queen-allowed', { role_id: queenRole.id }));
702
+ expectStatus(promoted, 200, 'Human-seat-to-queen reassignment');
703
+ invariant(decodeReassignDroneResultEnvelope(promoted.body).payload.drone.role_id === queenRole.id, 'Human-seat-to-queen reassignment did not persist.');
704
+ expectError(await environment.operations.reassignDrone(credentialA, cubeA, contender, createProtocolEnvelope('reassign-occupied-denied', { role_id: occupiedHumanRole.id })), 409, ErrorCode.ROLE_IN_USE, 'Occupied human-seat reassignment');
705
+ return {
706
+ worker_reassigned: true,
707
+ drone_session_denied: true,
708
+ queen_requires_human_seat: true,
709
+ occupied_human_seat_denied: true,
710
+ };
711
+ });
712
+ await record('security.cross-cube-drone-management', async () => {
713
+ const foreignRole = await environment.admin.createRole(cubeB, {
714
+ roleClass: 'worker', isHumanSeat: false,
715
+ });
716
+ const foreignDrone = await environment.admin.createDrone(principalB, cubeB, foreignRole);
717
+ expectError(await environment.operations.reassignDrone(credentialA, cubeB, foreignDrone, createProtocolEnvelope('cross-cube-reassign', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Cross-cube reassignment');
718
+ expectError(await environment.operations.evictDrone(credentialA, cubeB, foreignDrone, createProtocolEnvelope('cross-cube-evict', {})), 404, ErrorCode.NOT_FOUND, 'Cross-cube eviction');
719
+ expectError(await environment.operations.reassignDrone(credentialA, cubeA, foreignDrone, createProtocolEnvelope('foreign-drone-local-route', { role_id: workerRoleA.id })), 404, ErrorCode.NOT_FOUND, 'Foreign drone reassignment through authorized cube route');
720
+ expectError(await environment.operations.evictDrone(credentialA, cubeA, foreignDrone, createProtocolEnvelope('foreign-drone-local-evict', {})), 404, ErrorCode.NOT_FOUND, 'Foreign drone eviction through authorized cube route');
721
+ expectError(await environment.operations.reassignDrone(credentialA, cubeA, managedWorker, createProtocolEnvelope('foreign-role-local-drone', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Foreign role reassignment through authorized cube route');
722
+ await environment.admin.grantCube(principalA, cubeB, 'read');
723
+ const foreignDroneBefore = await environment.admin.inspectManagedDrone(foreignDrone);
724
+ expectError(await environment.operations.reassignDrone(workerSession, cubeB, foreignDrone, createProtocolEnvelope('bound-drone-cross-cube-reassign', { role_id: foreignRole.id })), 404, ErrorCode.NOT_FOUND, 'Bound drone cross-cube reassignment');
725
+ invariant(same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore), 'Bound drone cross-cube reassignment mutated the foreign target.');
726
+ expectError(await environment.operations.evictDrone(workerSession, cubeB, foreignDrone, createProtocolEnvelope('bound-drone-cross-cube-evict', {})), 404, ErrorCode.NOT_FOUND, 'Bound drone cross-cube eviction');
727
+ invariant(same(await environment.admin.inspectManagedDrone(foreignDrone), foreignDroneBefore), 'Bound drone cross-cube eviction mutated the foreign target.');
728
+ return {
729
+ unauthorized_cube_status: 404,
730
+ foreign_drone_status: 404,
731
+ foreign_role_status: 404,
732
+ bound_drone_cross_cube_status: 404,
733
+ code: ErrorCode.NOT_FOUND,
734
+ };
735
+ });
736
+ await record('drones.evict-terminal-signal', async () => {
737
+ const evictedDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
738
+ const evictedCredential = await environment.admin.issueManagedDroneSession(evictedDrone);
739
+ expectStatus(await environment.operations.read(evictedCredential, cubeA, createProtocolEnvelope('evict-probe-before', { cursor: null, limit: 1 })), 200, 'Pre-eviction seat probe');
740
+ const before = await environment.operations.listDrones(credentialA, cubeA);
741
+ expectStatus(before, 200, 'Pre-eviction roster');
742
+ invariant(listedDroneIds(before).includes(evictedDrone.id), 'Active drone was absent from roster.');
743
+ const evicted = await environment.operations.evictDrone(credentialA, cubeA, evictedDrone, createProtocolEnvelope('evict-managed-drone', {}));
744
+ expectStatus(evicted, 200, 'Drone eviction');
745
+ invariant(same(decodeEvictDroneResultEnvelope(evicted.body).payload, {
746
+ drone_id: evictedDrone.id,
747
+ evicted: true,
748
+ }), 'Eviction response did not identify the terminal seat.');
749
+ invariant(same(await environment.admin.inspectManagedDrone(evictedDrone), {
750
+ role_id: workerRoleA.id,
751
+ evicted: true,
752
+ session_revoked: true,
753
+ }), 'Eviction did not atomically mark the drone evicted and revoke its session.');
754
+ const after = await environment.operations.listDrones(credentialA, cubeA);
755
+ expectStatus(after, 200, 'Post-eviction roster');
756
+ invariant(!listedDroneIds(after).includes(evictedDrone.id), 'Evicted drone remained in roster.');
757
+ expectError(await environment.operations.append(credentialA, cubeA, createProtocolEnvelope('evict-direct-target', {
758
+ message: 'must-not-fan-out',
759
+ visibility: 'direct',
760
+ recipientDroneIds: [evictedDrone.id],
761
+ })), 404, ErrorCode.NOT_FOUND, 'Evicted direct recipient');
762
+ expectError(await environment.operations.read(evictedCredential, cubeA, createProtocolEnvelope('evict-probe-after', { cursor: null, limit: 1 })), PROTOCOL_HTTP_CONTRACT.drone_evicted_status, ErrorCode.DRONE_EVICTED, 'Evicted seat probe');
763
+ return {
764
+ eviction_status: 200,
765
+ roster_visible: false,
766
+ fanout_reachable: false,
767
+ old_bearer_status: 410,
768
+ old_bearer_code: ErrorCode.DRONE_EVICTED,
769
+ };
770
+ });
771
+ await record('security.drone-session-rejection-causes', async () => {
772
+ const revokedDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
773
+ const expiredDrone = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
774
+ const revokedCredential = await environment.admin.issueManagedDroneSession(revokedDrone);
775
+ const expiredCredential = await environment.admin.issueManagedDroneSession(expiredDrone);
776
+ await environment.admin.revokeManagedDroneSession(revokedDrone);
777
+ await environment.admin.expireManagedDroneSession(expiredDrone);
778
+ for (const [label, credential, code] of [
779
+ ['revoked', revokedCredential, ErrorCode.SESSION_REVOKED],
780
+ ['expired', expiredCredential, ErrorCode.AUTH_EXPIRED],
781
+ ]) {
782
+ expectError(await environment.operations.read(credential, cubeA, createProtocolEnvelope(`${label}-seat-probe`, { cursor: null, limit: 1 })), 401, code, `${label} seat probe`);
783
+ }
784
+ return {
785
+ revoked_status: 401,
786
+ expired_status: 401,
787
+ revoked_code: ErrorCode.SESSION_REVOKED,
788
+ expired_code: ErrorCode.AUTH_EXPIRED,
789
+ };
790
+ });
532
791
  await record('security.active-stream-revocation', async () => {
533
792
  invariant(liveCursor, 'Stream fixture did not produce a live cursor.');
534
793
  const opened = await environment.operations.openStream(credentialA, cubeA, liveCursor);
@@ -562,17 +821,4 @@ export async function runAdapterConformance(environment, options = {}) {
562
821
  const normalizedTranscript = results.map(({ id, observations }) => ({ id, observations }));
563
822
  return { ok: results.every((result) => result.ok), results, normalizedTranscript };
564
823
  }
565
- export async function runEquivalentAdapterConformance(cloud, local, options = {}) {
566
- const [cloudReport, localReport] = await Promise.all([
567
- runAdapterConformance(cloud, options),
568
- runAdapterConformance(local, options),
569
- ]);
570
- const equivalent = same(cloudReport.normalizedTranscript, localReport.normalizedTranscript);
571
- return {
572
- ok: cloudReport.ok && localReport.ok && equivalent,
573
- cloud: cloudReport,
574
- local: localReport,
575
- equivalent,
576
- };
577
- }
578
824
  //# sourceMappingURL=adapter.js.map