borgmcp-shared 0.5.0 → 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 -7
- 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/dist/templates.d.ts +13 -13
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +342 -673
- package/dist/templates.js.map +1 -1
- package/docs/compatibility.md +29 -1
- package/docs/enrollment.md +13 -5
- package/docs/releasing.md +15 -4
- 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/src/templates.ts +365 -803
|
@@ -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