borgmcp-shared 0.5.1 → 0.6.1
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 +29 -8
- package/dist/conformance/adapter.d.ts +54 -1
- package/dist/conformance/adapter.d.ts.map +1 -1
- package/dist/conformance/adapter.js +265 -1
- package/dist/conformance/adapter.js.map +1 -1
- package/dist/conformance/index.d.ts +9 -0
- package/dist/conformance/index.d.ts.map +1 -1
- package/dist/conformance/index.js +57 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/protocol/contract.d.ts +30 -2
- package/dist/protocol/contract.d.ts.map +1 -1
- package/dist/protocol/contract.js +62 -4
- package/dist/protocol/contract.js.map +1 -1
- package/dist/protocol/types.d.ts +16 -5
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/runtime-metadata.d.ts +28 -0
- package/dist/runtime-metadata.d.ts.map +1 -0
- package/dist/runtime-metadata.js +251 -0
- package/dist/runtime-metadata.js.map +1 -0
- package/docs/compatibility.md +28 -0
- package/docs/enrollment.md +12 -4
- package/docs/releasing.md +15 -5
- package/package.json +5 -1
- package/src/conformance/adapter.ts +457 -0
- package/src/conformance/index.ts +66 -1
- package/src/index.ts +1 -0
- package/src/protocol/contract.ts +110 -5
- package/src/protocol/types.ts +18 -5
- package/src/runtime-metadata.ts +304 -0
package/docs/enrollment.md
CHANGED
|
@@ -9,10 +9,18 @@ This contract replaced the server-generated bearer response used by the
|
|
|
9
9
|
published `borgmcp-shared@0.3.0` v1 baseline and shipped in the immutable
|
|
10
10
|
`0.4.0` clean-slate v2 release. The immutable `v0.4.1` verification artifact
|
|
11
11
|
failed its cross-platform SBOM audit and must never be published or rerun.
|
|
12
|
-
`borgmcp-shared@0.4.2`, `borgmcp-shared@0.4.3`,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
`borgmcp-shared@0.4.2`, `borgmcp-shared@0.4.3`, `borgmcp-shared@0.5.0`,
|
|
13
|
+
and `borgmcp-shared@0.5.1` are published and immutable. The accepted `0.5.1`
|
|
14
|
+
incident is bound to tag object `045268aa8873da330819860012ecaddb4bc2883c`, protected-main commit
|
|
15
|
+
`1981d7373e77f6edb4567872c1544bdbe2b1ef20`, attempt-1 run `29984423571`, and registry integrity
|
|
16
|
+
`sha512-XUJq+FjY/cbarU9V1dIWnhNYcqyURTiGb6KyIzg99gy5hk/fEs5ee/8X/qvp7pw1Rshqt2J6I3TVbwJdlde2tA==`.
|
|
17
|
+
`borgmcp-shared@0.6.0` was never published: annotated tag object
|
|
18
|
+
`90a1cf686a0ce32a7aef836b0b82a930191b9030` peels to protected-main commit
|
|
19
|
+
`fd69b08586481a60c88099dede8e4e066f73f2f2`; attempt-1 workflow run
|
|
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,
|
|
16
24
|
independently gated steps.
|
|
17
25
|
There is no compatibility path that returns a bearer from the server.
|
|
18
26
|
|
package/docs/releasing.md
CHANGED
|
@@ -122,10 +122,20 @@ These records remain evidence, not reusable release inputs:
|
|
|
122
122
|
postpublish check reconstructed the wrong workflow identity and is the reason
|
|
123
123
|
that machinery was removed.
|
|
124
124
|
|
|
125
|
-
`borgmcp-shared@0.4.3
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
postpublish registry
|
|
125
|
+
`borgmcp-shared@0.4.3`, `borgmcp-shared@0.5.0`, and `borgmcp-shared@0.5.1`
|
|
126
|
+
are published and immutable. The annotated `v0.5.1` tag object `045268aa8873da330819860012ecaddb4bc2883c`
|
|
127
|
+
peels to protected-main commit `1981d7373e77f6edb4567872c1544bdbe2b1ef20`. Attempt-1 workflow run
|
|
128
|
+
`29984423571` published registry integrity `sha512-XUJq+FjY/cbarU9V1dIWnhNYcqyURTiGb6KyIzg99gy5hk/fEs5ee/8X/qvp7pw1Rshqt2J6I3TVbwJdlde2tA==`; its
|
|
129
|
+
postpublish signature check raced registry propagation and the immutable run ended
|
|
130
|
+
failed. Independent pinned npm verification confirmed the registry signature and
|
|
131
|
+
SLSA provenance, and the incident was explicitly accepted. Never rerun or move
|
|
132
|
+
that tag. `borgmcp-shared@0.6.0` was never published. Its annotated tag object
|
|
133
|
+
`90a1cf686a0ce32a7aef836b0b82a930191b9030` peels to protected-main commit
|
|
134
|
+
`fd69b08586481a60c88099dede8e4e066f73f2f2`; attempt-1 workflow run
|
|
135
|
+
`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
|
|
130
140
|
must update the reviewed shared artifact before adopting the matching server and
|
|
131
141
|
client releases; protocol v2 and v3 peers fail closed at credential-free preflight.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Shared protocol, domain, templates, and conformance contracts for Borg MCP implementations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./dist/drone-address.d.ts",
|
|
27
27
|
"import": "./dist/drone-address.js"
|
|
28
28
|
},
|
|
29
|
+
"./runtime-metadata": {
|
|
30
|
+
"types": "./dist/runtime-metadata.d.ts",
|
|
31
|
+
"import": "./dist/runtime-metadata.js"
|
|
32
|
+
},
|
|
29
33
|
"./protocol": {
|
|
30
34
|
"types": "./dist/protocol/index.d.ts",
|
|
31
35
|
"import": "./dist/protocol/index.js"
|
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
decodeDecisionResultEnvelope,
|
|
8
8
|
decodeDecisionsResultEnvelope,
|
|
9
9
|
decodeEnrollmentExchangeResponseEnvelope,
|
|
10
|
+
decodeAttachResponseEnvelope,
|
|
11
|
+
decodeDroneRuntimeMetadataPatch,
|
|
10
12
|
decodeEvictDroneResultEnvelope,
|
|
11
13
|
decodeProtocolEnvelope,
|
|
12
14
|
decodeProtocolErrorEnvelope,
|
|
13
15
|
decodeProtocolTagPreflight,
|
|
14
16
|
decodeReadLogResultEnvelope,
|
|
15
17
|
decodeReassignDroneResultEnvelope,
|
|
18
|
+
decodeUpdateDroneRuntimeMetadataResponseEnvelope,
|
|
16
19
|
decodeSseFrames,
|
|
17
20
|
PROTOCOL_LIMIT_CEILINGS,
|
|
18
21
|
PROTOCOL_HTTP_CONTRACT,
|
|
@@ -21,6 +24,7 @@ import {
|
|
|
21
24
|
type CreateCubeResponse,
|
|
22
25
|
type LogCursor,
|
|
23
26
|
type StreamEvent,
|
|
27
|
+
type DroneRuntimeMetadata,
|
|
24
28
|
} from '../protocol/index.js';
|
|
25
29
|
import { ENROLLMENT_RETRY_CONFORMANCE } from './index.js';
|
|
26
30
|
|
|
@@ -45,6 +49,36 @@ export interface ConformanceDrone {
|
|
|
45
49
|
readonly id: string;
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
export interface ConformanceDroneRuntimeState {
|
|
53
|
+
readonly metadata: DroneRuntimeMetadata;
|
|
54
|
+
readonly metadata_reported: boolean;
|
|
55
|
+
readonly metadata_revision: number;
|
|
56
|
+
readonly cube_id: string;
|
|
57
|
+
readonly role_id: string;
|
|
58
|
+
readonly session_state: 'active' | 'revoked' | 'expired';
|
|
59
|
+
readonly evicted: boolean;
|
|
60
|
+
readonly last_seen: string;
|
|
61
|
+
readonly heartbeat_count: number;
|
|
62
|
+
readonly wake_count: number;
|
|
63
|
+
readonly log_count: number;
|
|
64
|
+
readonly model_turn_count: number;
|
|
65
|
+
readonly grant_access: ConformanceCubeAccess | null;
|
|
66
|
+
readonly server_capabilities: readonly string[];
|
|
67
|
+
readonly principal_revoked: boolean;
|
|
68
|
+
readonly session_bound: boolean;
|
|
69
|
+
readonly last_log_post: string | null;
|
|
70
|
+
readonly last_regen_at: string | null;
|
|
71
|
+
readonly last_read_log_at: string | null;
|
|
72
|
+
readonly last_event_received_at: string | null;
|
|
73
|
+
readonly wake_path: 'live';
|
|
74
|
+
readonly wake_alert: null;
|
|
75
|
+
readonly monitor_armed: boolean;
|
|
76
|
+
readonly sse_connected: boolean;
|
|
77
|
+
readonly claim_count: number;
|
|
78
|
+
readonly decision_count: number;
|
|
79
|
+
readonly routing_eligible: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
export type ConformanceCubeAccess = 'read' | 'write' | 'manage';
|
|
49
83
|
|
|
50
84
|
export interface ConformanceCubeManagementState {
|
|
@@ -133,6 +167,7 @@ export interface ConformanceAdmin {
|
|
|
133
167
|
readonly evicted: boolean;
|
|
134
168
|
readonly session_revoked: boolean;
|
|
135
169
|
}>;
|
|
170
|
+
inspectDroneRuntimeState(drone: ConformanceDrone): Promise<ConformanceDroneRuntimeState>;
|
|
136
171
|
/** Observes every cube field mutated by a represented manage-scoped operation. */
|
|
137
172
|
inspectCubeManagementState(cube: ConformanceCube): Promise<ConformanceCubeManagementState>;
|
|
138
173
|
grantCreateCubeCapability(principal: ConformancePrincipal): Promise<void>;
|
|
@@ -158,6 +193,12 @@ export interface ConformanceOperations {
|
|
|
158
193
|
protocol(credential: string | null): Promise<ConformanceHttpResponse>;
|
|
159
194
|
enroll(request: unknown): Promise<ConformanceHttpResponse>;
|
|
160
195
|
createCube(credential: string | null, request: unknown): Promise<ConformanceHttpResponse>;
|
|
196
|
+
attach(credential: string, request: unknown): Promise<ConformanceHttpResponse>;
|
|
197
|
+
selfMetadataUpdate(
|
|
198
|
+
credential: string,
|
|
199
|
+
cube: ConformanceCube,
|
|
200
|
+
request: unknown,
|
|
201
|
+
): Promise<ConformanceHttpResponse>;
|
|
161
202
|
append(
|
|
162
203
|
credential: string,
|
|
163
204
|
cube: ConformanceCube,
|
|
@@ -251,6 +292,13 @@ export const ADAPTER_CONFORMANCE_FIXTURES = [
|
|
|
251
292
|
{ id: 'security.cross-cube-drone-management', area: 'security' },
|
|
252
293
|
{ id: 'drones.evict-terminal-signal', area: 'drones' },
|
|
253
294
|
{ id: 'security.drone-session-rejection-causes', area: 'security' },
|
|
295
|
+
{ id: 'metadata.attach-report', area: 'metadata' },
|
|
296
|
+
{ id: 'metadata.self-heal-patch', area: 'metadata' },
|
|
297
|
+
{ id: 'security.metadata-invalid-atomic', area: 'security' },
|
|
298
|
+
{ id: 'security.metadata-own-seat', area: 'security' },
|
|
299
|
+
{ id: 'security.metadata-cross-cube-isolation', area: 'security' },
|
|
300
|
+
{ id: 'security.metadata-noninterference', area: 'security' },
|
|
301
|
+
{ id: 'security.metadata-secret-non-echo', area: 'security' },
|
|
254
302
|
{ id: 'security.active-stream-revocation', area: 'security' },
|
|
255
303
|
] as const;
|
|
256
304
|
|
|
@@ -1086,6 +1134,9 @@ export async function runAdapterConformance(
|
|
|
1086
1134
|
let readCredential = '';
|
|
1087
1135
|
let writeCredential = '';
|
|
1088
1136
|
let workerSession = '';
|
|
1137
|
+
let metadataDroneA!: ConformanceDrone;
|
|
1138
|
+
let metadataDroneB!: ConformanceDrone;
|
|
1139
|
+
let metadataSessionA = '';
|
|
1089
1140
|
await record('security.drone-management-authorization', async () => {
|
|
1090
1141
|
workerRoleA = await environment.admin.createRole(cubeA, {
|
|
1091
1142
|
roleClass: 'worker', isHumanSeat: false,
|
|
@@ -1566,6 +1617,412 @@ export async function runAdapterConformance(
|
|
|
1566
1617
|
};
|
|
1567
1618
|
});
|
|
1568
1619
|
|
|
1620
|
+
const knownMetadata = {
|
|
1621
|
+
agent_kind: 'opencode' as const,
|
|
1622
|
+
reported_model: 'openai/gpt-5.6-sol',
|
|
1623
|
+
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
1624
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
1625
|
+
};
|
|
1626
|
+
await record('metadata.attach-report', async () => {
|
|
1627
|
+
metadataSessionA = 'M'.repeat(43);
|
|
1628
|
+
const created = await environment.operations.attach(
|
|
1629
|
+
credentialA,
|
|
1630
|
+
createProtocolEnvelope('metadata-attach-created', {
|
|
1631
|
+
cube_id: cubeA.id,
|
|
1632
|
+
role_id: workerRoleA.id,
|
|
1633
|
+
session_credential: metadataSessionA,
|
|
1634
|
+
runtime_metadata: {
|
|
1635
|
+
...knownMetadata,
|
|
1636
|
+
working_repo_origin: 'git@github.com:Byte-Ventures/borg-mcp.git',
|
|
1637
|
+
},
|
|
1638
|
+
}),
|
|
1639
|
+
);
|
|
1640
|
+
expectStatus(created, 200, 'Metadata attach create');
|
|
1641
|
+
const createdPayload = decodeAttachResponseEnvelope(created.body).payload;
|
|
1642
|
+
invariant(createdPayload.result === 'created', 'First metadata attach did not create a seat.');
|
|
1643
|
+
invariant(same(createdPayload.drone.runtime_metadata, knownMetadata), 'Attach did not echo canonical metadata.');
|
|
1644
|
+
invariant(createdPayload.drone.runtime_metadata_reported, 'Present attach report was not marked reported.');
|
|
1645
|
+
metadataDroneA = { id: createdPayload.drone.id };
|
|
1646
|
+
|
|
1647
|
+
const unavailableSession = 'N'.repeat(43);
|
|
1648
|
+
const unavailable = await environment.operations.attach(
|
|
1649
|
+
credentialA,
|
|
1650
|
+
createProtocolEnvelope('metadata-attach-unavailable', {
|
|
1651
|
+
cube_id: cubeA.id,
|
|
1652
|
+
role_id: workerRoleA.id,
|
|
1653
|
+
session_credential: unavailableSession,
|
|
1654
|
+
}),
|
|
1655
|
+
);
|
|
1656
|
+
expectStatus(unavailable, 200, 'Unavailable metadata attach');
|
|
1657
|
+
const unavailableDrone = decodeAttachResponseEnvelope(unavailable.body).payload.drone;
|
|
1658
|
+
invariant(
|
|
1659
|
+
same(unavailableDrone.runtime_metadata, {
|
|
1660
|
+
agent_kind: null,
|
|
1661
|
+
reported_model: null,
|
|
1662
|
+
working_repo_name: null,
|
|
1663
|
+
working_repo_origin: null,
|
|
1664
|
+
}),
|
|
1665
|
+
'Omitted attach report synthesized metadata.',
|
|
1666
|
+
);
|
|
1667
|
+
invariant(!unavailableDrone.runtime_metadata_reported, 'Omitted attach report was marked reported.');
|
|
1668
|
+
|
|
1669
|
+
const explicitUnknown = await environment.operations.attach(
|
|
1670
|
+
credentialA,
|
|
1671
|
+
createProtocolEnvelope('metadata-attach-explicit-unknown', {
|
|
1672
|
+
cube_id: cubeA.id,
|
|
1673
|
+
role_id: workerRoleA.id,
|
|
1674
|
+
session_credential: 'U'.repeat(43),
|
|
1675
|
+
runtime_metadata: {
|
|
1676
|
+
agent_kind: null,
|
|
1677
|
+
reported_model: null,
|
|
1678
|
+
working_repo_name: null,
|
|
1679
|
+
working_repo_origin: null,
|
|
1680
|
+
},
|
|
1681
|
+
}),
|
|
1682
|
+
);
|
|
1683
|
+
expectStatus(explicitUnknown, 200, 'Explicit unknown metadata attach');
|
|
1684
|
+
const explicitUnknownDrone = decodeAttachResponseEnvelope(explicitUnknown.body).payload.drone;
|
|
1685
|
+
invariant(explicitUnknownDrone.runtime_metadata_reported, 'All-null attach report was not marked reported.');
|
|
1686
|
+
|
|
1687
|
+
const reused = await environment.operations.attach(
|
|
1688
|
+
credentialA,
|
|
1689
|
+
createProtocolEnvelope('metadata-attach-reused', {
|
|
1690
|
+
cube_id: cubeA.id,
|
|
1691
|
+
role_id: workerRoleA.id,
|
|
1692
|
+
prior_drone_id: metadataDroneA.id,
|
|
1693
|
+
session_credential: metadataSessionA,
|
|
1694
|
+
runtime_metadata: knownMetadata,
|
|
1695
|
+
}),
|
|
1696
|
+
);
|
|
1697
|
+
expectStatus(reused, 200, 'Metadata attach reuse');
|
|
1698
|
+
invariant(decodeAttachResponseEnvelope(reused.body).payload.result === 'reused', 'Prior seat was not reused.');
|
|
1699
|
+
const cleared = await environment.operations.selfMetadataUpdate(
|
|
1700
|
+
metadataSessionA,
|
|
1701
|
+
cubeA,
|
|
1702
|
+
createProtocolEnvelope('metadata-clear-all', {
|
|
1703
|
+
agent_kind: null,
|
|
1704
|
+
reported_model: null,
|
|
1705
|
+
working_repo_name: null,
|
|
1706
|
+
working_repo_origin: null,
|
|
1707
|
+
}),
|
|
1708
|
+
);
|
|
1709
|
+
expectStatus(cleared, 200, 'Clear-all metadata patch');
|
|
1710
|
+
const clearedPayload = decodeUpdateDroneRuntimeMetadataResponseEnvelope(cleared.body).payload;
|
|
1711
|
+
invariant(clearedPayload.runtime_metadata_reported, 'Clear-all patch lost reported state.');
|
|
1712
|
+
invariant(
|
|
1713
|
+
same(clearedPayload.runtime_metadata, explicitUnknownDrone.runtime_metadata),
|
|
1714
|
+
'Clear-all patch did not produce explicit unknown metadata.',
|
|
1715
|
+
);
|
|
1716
|
+
expectStatus(
|
|
1717
|
+
await environment.operations.selfMetadataUpdate(
|
|
1718
|
+
metadataSessionA,
|
|
1719
|
+
cubeA,
|
|
1720
|
+
createProtocolEnvelope('metadata-restore-known', knownMetadata),
|
|
1721
|
+
),
|
|
1722
|
+
200,
|
|
1723
|
+
'Restore known metadata',
|
|
1724
|
+
);
|
|
1725
|
+
return {
|
|
1726
|
+
created: true,
|
|
1727
|
+
reused: true,
|
|
1728
|
+
report_states: [
|
|
1729
|
+
unavailableDrone.runtime_metadata_reported,
|
|
1730
|
+
explicitUnknownDrone.runtime_metadata_reported,
|
|
1731
|
+
clearedPayload.runtime_metadata_reported,
|
|
1732
|
+
],
|
|
1733
|
+
canonical_response: true,
|
|
1734
|
+
};
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
await record('metadata.self-heal-patch', async () => {
|
|
1738
|
+
const updates = [
|
|
1739
|
+
{ request_id: 'metadata-known-new', patch: { reported_model: 'openai/gpt-5.6-terra' } },
|
|
1740
|
+
{ request_id: 'metadata-null-clear', patch: { reported_model: null } },
|
|
1741
|
+
{ request_id: 'metadata-null-repeat', patch: { reported_model: null } },
|
|
1742
|
+
{ request_id: 'metadata-same-value', patch: { agent_kind: 'opencode' as const } },
|
|
1743
|
+
];
|
|
1744
|
+
for (const update of updates) {
|
|
1745
|
+
const response = await environment.operations.selfMetadataUpdate(
|
|
1746
|
+
metadataSessionA,
|
|
1747
|
+
cubeA,
|
|
1748
|
+
createProtocolEnvelope(update.request_id, update.patch),
|
|
1749
|
+
);
|
|
1750
|
+
expectStatus(response, 200, update.request_id);
|
|
1751
|
+
decodeUpdateDroneRuntimeMetadataResponseEnvelope(response.body);
|
|
1752
|
+
}
|
|
1753
|
+
const state = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1754
|
+
invariant(state.metadata.reported_model === null, 'Explicit null did not clear model metadata.');
|
|
1755
|
+
invariant(state.metadata.agent_kind === 'opencode', 'Omitted field was not preserved.');
|
|
1756
|
+
return { known_replace: true, omitted_unchanged: true, null_clear: true, repeat_safe: true };
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
await record('security.metadata-invalid-atomic', async () => {
|
|
1760
|
+
const invalidPatches: unknown[] = [
|
|
1761
|
+
{},
|
|
1762
|
+
{ agent_kind: 'OpenCode' },
|
|
1763
|
+
{ reported_model: '' },
|
|
1764
|
+
{ reported_model: 'a'.repeat(161) },
|
|
1765
|
+
{ reported_model: 'safe\u001b[2J' },
|
|
1766
|
+
{ reported_model: 'safe\u061cnext' },
|
|
1767
|
+
{ working_repo_name: 'owner/repo' },
|
|
1768
|
+
{ working_repo_name: 'owner/repo', working_repo_origin: 'https://user:secret@github.com/owner/repo' },
|
|
1769
|
+
{
|
|
1770
|
+
working_repo_name: 'owner/repo',
|
|
1771
|
+
working_repo_origin: ['file:/', '', 'Users', 'secret', 'repo'].join('/'),
|
|
1772
|
+
},
|
|
1773
|
+
{ role_id: workerRoleA.id },
|
|
1774
|
+
{ last_seen: '2026-07-14T10:00:00.000Z' },
|
|
1775
|
+
];
|
|
1776
|
+
for (const [index, patch] of invalidPatches.entries()) {
|
|
1777
|
+
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1778
|
+
const response = await environment.operations.selfMetadataUpdate(
|
|
1779
|
+
metadataSessionA,
|
|
1780
|
+
cubeA,
|
|
1781
|
+
createProtocolEnvelope(`metadata-invalid-${index}`, patch),
|
|
1782
|
+
);
|
|
1783
|
+
expectError(response, 400, ErrorCode.INVALID_INPUT, `Invalid metadata patch ${index}`);
|
|
1784
|
+
invariant(
|
|
1785
|
+
same(await environment.admin.inspectDroneRuntimeState(metadataDroneA), before),
|
|
1786
|
+
`Invalid metadata patch ${index} partially mutated state.`,
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
return { cases: invalidPatches.length, atomic: true, prior_safe_state_retained: true };
|
|
1790
|
+
});
|
|
1791
|
+
|
|
1792
|
+
await record('security.metadata-own-seat', async () => {
|
|
1793
|
+
const peer = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1794
|
+
const peerBefore = await environment.admin.inspectDroneRuntimeState(peer);
|
|
1795
|
+
const managedBefore = await environment.admin.inspectDroneRuntimeState(managedWorker);
|
|
1796
|
+
const ownBefore = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1797
|
+
const response = await environment.operations.selfMetadataUpdate(
|
|
1798
|
+
metadataSessionA,
|
|
1799
|
+
cubeA,
|
|
1800
|
+
createProtocolEnvelope('metadata-own-seat', { agent_kind: 'claude' }),
|
|
1801
|
+
);
|
|
1802
|
+
expectStatus(response, 200, 'Own-seat metadata update');
|
|
1803
|
+
invariant(
|
|
1804
|
+
same(await environment.admin.inspectDroneRuntimeState(peer), peerBefore),
|
|
1805
|
+
'Own-seat update mutated a peer seat.',
|
|
1806
|
+
);
|
|
1807
|
+
invariant(
|
|
1808
|
+
same(await environment.admin.inspectDroneRuntimeState(managedWorker), managedBefore),
|
|
1809
|
+
'Own-seat update mutated another existing seat.',
|
|
1810
|
+
);
|
|
1811
|
+
invariant(
|
|
1812
|
+
(await environment.admin.inspectDroneRuntimeState(metadataDroneA)).role_id === ownBefore.role_id,
|
|
1813
|
+
'Metadata update changed the current seat role.',
|
|
1814
|
+
);
|
|
1815
|
+
expectError(
|
|
1816
|
+
await environment.operations.selfMetadataUpdate(
|
|
1817
|
+
credentialA,
|
|
1818
|
+
cubeA,
|
|
1819
|
+
createProtocolEnvelope('metadata-manager-denied', { agent_kind: 'codex' }),
|
|
1820
|
+
),
|
|
1821
|
+
403,
|
|
1822
|
+
ErrorCode.ACCESS_DENIED,
|
|
1823
|
+
'Manager metadata update',
|
|
1824
|
+
);
|
|
1825
|
+
expectError(
|
|
1826
|
+
await environment.operations.selfMetadataUpdate(
|
|
1827
|
+
'Z'.repeat(43),
|
|
1828
|
+
cubeA,
|
|
1829
|
+
createProtocolEnvelope('metadata-invalid-session', { agent_kind: null }),
|
|
1830
|
+
),
|
|
1831
|
+
401,
|
|
1832
|
+
ErrorCode.AUTH_INVALID,
|
|
1833
|
+
'Unknown metadata session',
|
|
1834
|
+
);
|
|
1835
|
+
const rejectedStates: Array<[string, ConformanceDrone, string, number, ErrorCode]> = [];
|
|
1836
|
+
const revoked = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1837
|
+
const revokedSession = await environment.admin.issueManagedDroneSession(revoked);
|
|
1838
|
+
await environment.admin.revokeManagedDroneSession(revoked);
|
|
1839
|
+
rejectedStates.push(['revoked', revoked, revokedSession, 401, ErrorCode.SESSION_REVOKED]);
|
|
1840
|
+
const expired = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1841
|
+
const expiredSession = await environment.admin.issueManagedDroneSession(expired);
|
|
1842
|
+
await environment.admin.expireManagedDroneSession(expired);
|
|
1843
|
+
rejectedStates.push(['expired', expired, expiredSession, 401, ErrorCode.AUTH_EXPIRED]);
|
|
1844
|
+
const evicted = await environment.admin.createDrone(principalA, cubeA, workerRoleA);
|
|
1845
|
+
const evictedSession = await environment.admin.issueManagedDroneSession(evicted);
|
|
1846
|
+
expectStatus(
|
|
1847
|
+
await environment.operations.evictDrone(
|
|
1848
|
+
credentialA,
|
|
1849
|
+
cubeA,
|
|
1850
|
+
evicted,
|
|
1851
|
+
createProtocolEnvelope('metadata-evict-seat', {}),
|
|
1852
|
+
),
|
|
1853
|
+
200,
|
|
1854
|
+
'Metadata seat eviction',
|
|
1855
|
+
);
|
|
1856
|
+
rejectedStates.push(['evicted', evicted, evictedSession, 410, ErrorCode.DRONE_EVICTED]);
|
|
1857
|
+
for (const [label, drone, session, status, code] of rejectedStates) {
|
|
1858
|
+
const before = await environment.admin.inspectDroneRuntimeState(drone);
|
|
1859
|
+
expectError(
|
|
1860
|
+
await environment.operations.selfMetadataUpdate(
|
|
1861
|
+
session,
|
|
1862
|
+
cubeA,
|
|
1863
|
+
createProtocolEnvelope(`metadata-${label}-denied`, { agent_kind: null }),
|
|
1864
|
+
),
|
|
1865
|
+
status,
|
|
1866
|
+
code,
|
|
1867
|
+
`${label} metadata session`,
|
|
1868
|
+
);
|
|
1869
|
+
invariant(
|
|
1870
|
+
same(await environment.admin.inspectDroneRuntimeState(drone), before),
|
|
1871
|
+
`${label} metadata denial mutated state.`,
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
return {
|
|
1875
|
+
own_seat_only: true,
|
|
1876
|
+
manager_denied: true,
|
|
1877
|
+
unknown_session_denied: true,
|
|
1878
|
+
revoked_expired_evicted_denied: true,
|
|
1879
|
+
};
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
await record('security.metadata-cross-cube-isolation', async () => {
|
|
1883
|
+
const sessionB = 'P'.repeat(43);
|
|
1884
|
+
const metadataRoleB = await environment.admin.createRole(cubeB, {
|
|
1885
|
+
roleClass: 'worker', isHumanSeat: false,
|
|
1886
|
+
});
|
|
1887
|
+
const attachedB = await environment.operations.attach(
|
|
1888
|
+
credentialB,
|
|
1889
|
+
createProtocolEnvelope('metadata-attach-b', {
|
|
1890
|
+
cube_id: cubeB.id,
|
|
1891
|
+
role_id: metadataRoleB.id,
|
|
1892
|
+
session_credential: sessionB,
|
|
1893
|
+
runtime_metadata: {
|
|
1894
|
+
agent_kind: 'codex',
|
|
1895
|
+
reported_model: null,
|
|
1896
|
+
working_repo_name: 'Byte-Ventures/borg-mcp-server',
|
|
1897
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp-server',
|
|
1898
|
+
},
|
|
1899
|
+
}),
|
|
1900
|
+
);
|
|
1901
|
+
expectStatus(attachedB, 200, 'Cube B metadata attach');
|
|
1902
|
+
metadataDroneB = { id: decodeAttachResponseEnvelope(attachedB.body).payload.drone.id };
|
|
1903
|
+
const beforeB = await environment.admin.inspectDroneRuntimeState(metadataDroneB);
|
|
1904
|
+
const foreign = await environment.operations.selfMetadataUpdate(
|
|
1905
|
+
metadataSessionA,
|
|
1906
|
+
cubeB,
|
|
1907
|
+
createProtocolEnvelope('metadata-cross-cube', { reported_model: 'foreign/probe' }),
|
|
1908
|
+
);
|
|
1909
|
+
expectError(foreign, 404, ErrorCode.NOT_FOUND, 'Cross-cube metadata update');
|
|
1910
|
+
const unknown = await environment.operations.selfMetadataUpdate(
|
|
1911
|
+
metadataSessionA,
|
|
1912
|
+
{ id: '90000000-0000-4000-8000-000000000001' },
|
|
1913
|
+
createProtocolEnvelope('metadata-unknown-cube', { reported_model: 'foreign/probe' }),
|
|
1914
|
+
);
|
|
1915
|
+
expectError(unknown, 404, ErrorCode.NOT_FOUND, 'Unknown-cube metadata update');
|
|
1916
|
+
invariant(same(foreign.body, unknown.body), 'Foreign and unknown metadata probes were distinguishable.');
|
|
1917
|
+
invariant(
|
|
1918
|
+
same(await environment.admin.inspectDroneRuntimeState(metadataDroneB), beforeB),
|
|
1919
|
+
'Cross-cube metadata update mutated the foreign seat.',
|
|
1920
|
+
);
|
|
1921
|
+
const listedA = await environment.operations.listDrones(credentialA, cubeA);
|
|
1922
|
+
invariant(!listedDroneIds(listedA).includes(metadataDroneB.id), 'Cube A roster disclosed cube B metadata seat.');
|
|
1923
|
+
return { update_status: 404, foreign_unknown_indistinguishable: true, foreign_unchanged: true, roster_isolated: true };
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
await record('security.metadata-noninterference', async () => {
|
|
1927
|
+
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1928
|
+
for (const [requestId, patch] of [
|
|
1929
|
+
['metadata-noninterference-known', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
1930
|
+
['metadata-noninterference-repeat', { reported_model: 'openai/gpt-5.6-sol' }],
|
|
1931
|
+
['metadata-noninterference-null', { reported_model: null }],
|
|
1932
|
+
] as const) {
|
|
1933
|
+
expectStatus(
|
|
1934
|
+
await environment.operations.selfMetadataUpdate(
|
|
1935
|
+
metadataSessionA,
|
|
1936
|
+
cubeA,
|
|
1937
|
+
createProtocolEnvelope(requestId, patch),
|
|
1938
|
+
),
|
|
1939
|
+
200,
|
|
1940
|
+
requestId,
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
expectError(
|
|
1944
|
+
await environment.operations.selfMetadataUpdate(
|
|
1945
|
+
metadataSessionA,
|
|
1946
|
+
cubeA,
|
|
1947
|
+
createProtocolEnvelope('metadata-noninterference-reject', { authority: 'manage' }),
|
|
1948
|
+
),
|
|
1949
|
+
400,
|
|
1950
|
+
ErrorCode.INVALID_INPUT,
|
|
1951
|
+
'Rejected metadata non-interference update',
|
|
1952
|
+
);
|
|
1953
|
+
const reattached = await environment.operations.attach(
|
|
1954
|
+
credentialA,
|
|
1955
|
+
createProtocolEnvelope('metadata-noninterference-reattach', {
|
|
1956
|
+
cube_id: cubeA.id,
|
|
1957
|
+
role_id: workerRoleA.id,
|
|
1958
|
+
prior_drone_id: metadataDroneA.id,
|
|
1959
|
+
session_credential: metadataSessionA,
|
|
1960
|
+
}),
|
|
1961
|
+
);
|
|
1962
|
+
expectStatus(reattached, 200, 'Metadata non-interference reattach');
|
|
1963
|
+
const after = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1964
|
+
const { metadata: _beforeMetadata, metadata_revision: _beforeRevision, ...beforeInvariant } = before;
|
|
1965
|
+
const { metadata: _afterMetadata, metadata_revision: _afterRevision, ...afterInvariant } = after;
|
|
1966
|
+
invariant(same(beforeInvariant, afterInvariant), 'Metadata update changed authority/liveness/log state.');
|
|
1967
|
+
return {
|
|
1968
|
+
authority_unchanged: true,
|
|
1969
|
+
role_unchanged: true,
|
|
1970
|
+
liveness_unchanged: true,
|
|
1971
|
+
logs_unchanged: true,
|
|
1972
|
+
model_turns_unchanged: true,
|
|
1973
|
+
};
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
await record('security.metadata-secret-non-echo', async () => {
|
|
1977
|
+
const marker = 'SECRET-METADATA-MARKER';
|
|
1978
|
+
const before = await environment.admin.inspectDroneRuntimeState(metadataDroneA);
|
|
1979
|
+
const response = await environment.operations.selfMetadataUpdate(
|
|
1980
|
+
metadataSessionA,
|
|
1981
|
+
cubeA,
|
|
1982
|
+
createProtocolEnvelope('metadata-secret', {
|
|
1983
|
+
working_repo_name: 'owner/repo',
|
|
1984
|
+
working_repo_origin: `https://user:${marker}@github.com/owner/repo?token=${marker}`,
|
|
1985
|
+
}),
|
|
1986
|
+
);
|
|
1987
|
+
expectError(response, 400, ErrorCode.INVALID_INPUT, 'Secret-bearing metadata update');
|
|
1988
|
+
invariant(!JSON.stringify(response.body).includes(marker), 'Metadata error echoed hostile secret input.');
|
|
1989
|
+
invariant(
|
|
1990
|
+
same(await environment.admin.inspectDroneRuntimeState(metadataDroneA), before),
|
|
1991
|
+
'Secret-bearing metadata input was persisted.',
|
|
1992
|
+
);
|
|
1993
|
+
const hostileKeys = [
|
|
1994
|
+
'SECRET-METADATA-KEY-MARKER',
|
|
1995
|
+
`control\u001b[2J`,
|
|
1996
|
+
`bidi\u061cmarker`,
|
|
1997
|
+
`token_${'a'.repeat(48)}`,
|
|
1998
|
+
];
|
|
1999
|
+
for (const [index, key] of hostileKeys.entries()) {
|
|
2000
|
+
const rejected = await environment.operations.selfMetadataUpdate(
|
|
2001
|
+
metadataSessionA,
|
|
2002
|
+
cubeA,
|
|
2003
|
+
createProtocolEnvelope(`metadata-secret-key-${index}`, { [key]: 'x' }),
|
|
2004
|
+
);
|
|
2005
|
+
expectError(rejected, 400, ErrorCode.INVALID_INPUT, `Hostile metadata key ${index}`);
|
|
2006
|
+
invariant(!JSON.stringify(rejected.body).includes(key), `Metadata error echoed hostile key ${index}.`);
|
|
2007
|
+
}
|
|
2008
|
+
const logs = await environment.operations.read(
|
|
2009
|
+
credentialA,
|
|
2010
|
+
cubeA,
|
|
2011
|
+
createProtocolEnvelope('metadata-secret-log-read', { cursor: null, limit: 500 }),
|
|
2012
|
+
);
|
|
2013
|
+
expectStatus(logs, 200, 'Metadata secret log read');
|
|
2014
|
+
for (const hostile of [marker, ...hostileKeys]) {
|
|
2015
|
+
invariant(!JSON.stringify(logs.body).includes(hostile), 'Metadata input leaked into activity logs.');
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
rejected_pre_persistence: true,
|
|
2019
|
+
response_secret_free: true,
|
|
2020
|
+
state_secret_free: true,
|
|
2021
|
+
log_secret_free: true,
|
|
2022
|
+
unknown_key_secret_free: true,
|
|
2023
|
+
};
|
|
2024
|
+
});
|
|
2025
|
+
|
|
1569
2026
|
await record('security.active-stream-revocation', async () => {
|
|
1570
2027
|
invariant(liveCursor, 'Stream fixture did not produce a live cursor.');
|
|
1571
2028
|
const opened = await environment.operations.openStream(credentialA, cubeA, liveCursor);
|
package/src/conformance/index.ts
CHANGED
|
@@ -216,7 +216,17 @@ const ATTACH_RESPONSE = {
|
|
|
216
216
|
result: 'created',
|
|
217
217
|
cube: { id: '10000000-0000-4000-8000-000000000001', name: 'test-cube' },
|
|
218
218
|
role: { id: '20000000-0000-4000-8000-000000000001', name: 'Coordinator' },
|
|
219
|
-
drone: {
|
|
219
|
+
drone: {
|
|
220
|
+
id: '30000000-0000-4000-8000-000000000001',
|
|
221
|
+
label: 'one-of-one-coordinator',
|
|
222
|
+
runtime_metadata: {
|
|
223
|
+
agent_kind: null,
|
|
224
|
+
reported_model: null,
|
|
225
|
+
working_repo_name: null,
|
|
226
|
+
working_repo_origin: null,
|
|
227
|
+
},
|
|
228
|
+
runtime_metadata_reported: false,
|
|
229
|
+
},
|
|
220
230
|
session: { id: '40000000-0000-4000-8000-000000000001' },
|
|
221
231
|
} satisfies AttachResponse;
|
|
222
232
|
|
|
@@ -237,3 +247,58 @@ export const ATTACH_SESSION_CONFORMANCE: readonly AttachSessionConformanceVector
|
|
|
237
247
|
accepts: false,
|
|
238
248
|
},
|
|
239
249
|
];
|
|
250
|
+
|
|
251
|
+
export interface RuntimeMetadataRepositoryConformanceVector {
|
|
252
|
+
name: string;
|
|
253
|
+
origin: string;
|
|
254
|
+
expected: { working_repo_name: string; working_repo_origin: string } | null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** One canonical corpus consumed unchanged by shared, server, and client tests. */
|
|
258
|
+
export const RUNTIME_METADATA_REPOSITORY_CONFORMANCE:
|
|
259
|
+
readonly RuntimeMetadataRepositoryConformanceVector[] = [
|
|
260
|
+
{
|
|
261
|
+
name: 'canonical HTTPS',
|
|
262
|
+
origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
263
|
+
expected: {
|
|
264
|
+
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
265
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'HTTPS default port and suffix',
|
|
270
|
+
origin: 'https://GITHUB.com:443/Byte-Ventures/borg-mcp.git',
|
|
271
|
+
expected: {
|
|
272
|
+
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
273
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'SSH URL',
|
|
278
|
+
origin: 'ssh://git@github.com:22/Byte-Ventures/borg-mcp.git',
|
|
279
|
+
expected: {
|
|
280
|
+
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
281
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'SCP syntax',
|
|
286
|
+
origin: 'git@github.com:Byte-Ventures/borg-mcp.git',
|
|
287
|
+
expected: {
|
|
288
|
+
working_repo_name: 'Byte-Ventures/borg-mcp',
|
|
289
|
+
working_repo_origin: 'https://github.com/Byte-Ventures/borg-mcp',
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{ name: 'HTTPS userinfo', origin: 'https://user@github.com/owner/repo', expected: null },
|
|
293
|
+
{ name: 'SSH arbitrary user', origin: 'ssh://owner@github.com/owner/repo', expected: null },
|
|
294
|
+
{ name: 'query', origin: 'https://github.com/owner/repo?token=value', expected: null },
|
|
295
|
+
{ name: 'fragment', origin: 'https://github.com/owner/repo#fragment', expected: null },
|
|
296
|
+
{ name: 'percent encoding', origin: 'https://github.com/owner/re%70o', expected: null },
|
|
297
|
+
{ name: 'non-default port', origin: 'https://github.com:444/owner/repo', expected: null },
|
|
298
|
+
{ name: 'local file URL', origin: ['file:/', '', 'home', 'user', 'repo'].join('/'), expected: null },
|
|
299
|
+
{ name: 'relative path', origin: ['..', 'repo'].join('/'), expected: null },
|
|
300
|
+
{ name: 'loopback host', origin: 'https://127.0.0.1/owner/repo', expected: null },
|
|
301
|
+
{ name: 'IPv6 host', origin: 'https://[::1]/owner/repo', expected: null },
|
|
302
|
+
{ name: 'single-label host', origin: 'https://git/owner/repo', expected: null },
|
|
303
|
+
{ name: 'private suffix', origin: 'https://git.internal/owner/repo', expected: null },
|
|
304
|
+
];
|
package/src/index.ts
CHANGED