Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/code/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,17 @@ export interface BridgeWorkerRegistrationResponse {
supportedWorkspaceListFileFeatures?: WorkspaceListFileFeature[];
}

/** Administrator-visible liveness for a configured worker. Credentials,
* bindings, host paths, and worker identity material are deliberately omitted. */
export interface BridgeWorkerStatusResponse {
protocolVersion: BridgeProtocolVersion;
workerId: string;
online: boolean;
ready: boolean;
leaseExpiresInMs?: number;
capabilities?: BridgeWorkerCapabilities;
}

export interface BridgePairingRedemption {
protocolVersion: BridgeProtocolVersion;
workerId: string;
Expand Down
73 changes: 73 additions & 0 deletions service/src/bridge/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,79 @@ afterEach(async () => {
});

describe('paired bridge HTTP API', () => {
test('reports authenticated worker readiness without exposing identity or binding data', async () => {
const store = new RedisBridgeStore(redis);
const app = express();
app.use(json());
app.use(
'/v1/bridge',
createBridgeRouter({
store,
pairings: new RedisBridgePairingStore(redis),
authMode: 'paired',
adminToken: 'strong-administrator-bootstrap-token',
allowDynamicWorkers: true,
}),
);
server = createServer(app);
await new Promise<void>((resolve) => server?.listen(0, '127.0.0.1', resolve));
const address = server.address();
if (address == null || typeof address === 'string') {
throw new Error('Expected TCP listener');
}
const baseUrl = `http://127.0.0.1:${address.port}/v1/bridge`;
const headers = { Authorization: 'Bearer strong-administrator-bootstrap-token' };

const offline = await fetch(`${baseUrl}/workers/user-vm/status`, { headers });
expect(offline.status).toBe(200);
await expect(offline.json()).resolves.toEqual({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
workerId: 'user-vm',
online: false,
ready: false,
});

const capabilities = {
statefulWorkspace: true,
sandboxProfile: 'native-srt',
runtimes: ['bash'],
requiresReadyConfirmation: true,
};
const registrationGeneration = await store.register({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
workerId: 'user-vm',
incarnationId: 'incarnation-00000001',
capabilities,
binding: {
tenantId: 'tenant-1',
principal: { type: 'user', id: 'user-1' },
},
credentialId: 'secret-credential-id',
identityId: 'secret-identity-id',
});

const starting = await fetch(`${baseUrl}/workers/user-vm/status`, { headers });
expect(starting.status).toBe(200);
await expect(starting.json()).resolves.toMatchObject({
workerId: 'user-vm',
online: true,
ready: false,
capabilities,
});
expect(
JSON.stringify(await (await fetch(`${baseUrl}/workers/user-vm/status`, { headers })).json()),
).not.toMatch(/tenant-1|user-1|secret-credential-id|secret-identity-id/);

await store.confirmReady('user-vm', 'incarnation-00000001', registrationGeneration);
const ready = await fetch(`${baseUrl}/workers/user-vm/status`, { headers });
const status = (await ready.json()) as Record<string, unknown>;
expect(status).toMatchObject({ workerId: 'user-vm', online: true, ready: true, capabilities });
expect(status.leaseExpiresInMs).toBeNumber();

const unauthorized = await fetch(`${baseUrl}/workers/user-vm/status`);
expect(unauthorized.status).toBe(401);
});

test('rejects a malformed optional binding for a configured worker', async () => {
const app = express();
app.use(json());
Expand Down
17 changes: 17 additions & 0 deletions service/src/bridge/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,23 @@ export function createBridgeRouter(options: BridgeRouterOptions): Router {
}),
);

router.get(
'/workers/:workerId/status',
adminAuth,
asyncRoute(async (req, res) => {
const workerId = req.params.workerId;
if (!validWorkerId(workerId) || !configuredWorker(workerId)) {
res.status(400).json({ error: 'Invalid bridge worker ID' });
return;
}
const status = await options.store.workerStatus(workerId);
res.json({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
workerId,
...status,
});
}),
);

router.post(
'/workers/register',
Expand Down
28 changes: 28 additions & 0 deletions service/src/bridge/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,34 @@ afterEach(async () => {
});

describe('RedisBridgeStore', () => {
test('reports an atomic, capability-limited worker status snapshot', async () => {
const store = new RedisBridgeStore(redis);
const capabilities = {
statefulWorkspace: true,
sandboxProfile: 'native-srt',
runtimes: ['bash'],
requiresReadyConfirmation: true,
};
expect(await store.workerStatus('vm-status')).toEqual({ online: false, ready: false });

const generation = await store.register({
protocolVersion: BRIDGE_PROTOCOL_VERSION,
workerId: 'vm-status',
incarnationId: 'incarnation-status-01',
capabilities,
});
expect(await store.workerStatus('vm-status')).toMatchObject({
online: true,
ready: false,
capabilities,
});

await store.confirmReady('vm-status', 'incarnation-status-01', generation);
const status = await store.workerStatus('vm-status');
expect(status).toMatchObject({ online: true, ready: true, capabilities });
expect(status.leaseExpiresInMs).toBeGreaterThan(0);
});

test('rejects a registration whose authenticated identity was replaced', async () => {
await redis.set(
'codeapi:bridge:v1:identity:fenced-worker',
Expand Down
66 changes: 66 additions & 0 deletions service/src/bridge/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import type {

import {
BRIDGE_PROTOCOL_VERSION,
isValidBridgeWorkerCapabilities,
isValidBridgeWorkerId,
isWorkspaceToolRequest,
isWorkspaceToolResult,
} from '../../../packages/code/src/protocol';
Expand Down Expand Up @@ -68,6 +70,13 @@ export interface RegisteredBridgeWorker extends BridgeWorkerRegistration {
identityId?: string;
}

export interface BridgeWorkerStatus {
online: boolean;
ready: boolean;
leaseExpiresInMs?: number;
capabilities?: BridgeWorkerRegistration['capabilities'];
}

function supportsWorkspaceTool(
registration: RegisteredBridgeWorker,
request: WorkspaceToolRequest,
Expand Down Expand Up @@ -309,6 +318,63 @@ export class RedisBridgeStore {
);
}

/** Returns only the worker's ephemeral registration state. The registration
* is the heartbeat: when its TTL expires the worker is offline. */
async workerStatus(workerId: string): Promise<BridgeWorkerStatus> {
const snapshot = (await boundedCommand(
this.redis.eval(
[
"local registration = redis.call('GET', KEYS[1])",
"if not registration then return { false, false, false, -2 } end",
'return {',
' registration,',
" redis.call('GET', KEYS[2]) or false,",
" redis.call('GET', KEYS[3]) or false,",
" redis.call('PTTL', KEYS[1])",
'}',
].join('\n'),
3,
workerKey(workerId),
workerReadyKey(workerId),
workerRegistrationGenerationKey(workerId),
),
this.redisCommandTimeoutMs,
'Bridge worker status',
)) as [string | null, string | null, string | null, number];
const [rawRegistration, readyToken, registrationGeneration, leaseExpiresInMs] = snapshot;
if (rawRegistration == null || rawRegistration === '' || leaseExpiresInMs <= 0) {
return { online: false, ready: false };
}

let registration: RegisteredBridgeWorker;
try {
registration = JSON.parse(rawRegistration) as RegisteredBridgeWorker;
} catch {
return { online: false, ready: false };
}
if (
registration.protocolVersion !== BRIDGE_PROTOCOL_VERSION ||
!isValidBridgeWorkerId(registration.workerId) ||
registration.workerId !== workerId ||
typeof registration.incarnationId !== 'string' ||
!isValidBridgeWorkerCapabilities(registration.capabilities)
) {
return { online: false, ready: false };
}

const requiresConfirmation = registration.capabilities.requiresReadyConfirmation === true;
const ready =
!requiresConfirmation ||
(registrationGeneration != null &&
readyToken === workerReadyToken(registration.incarnationId, Number(registrationGeneration)));
return {
online: true,
ready,
leaseExpiresInMs,
capabilities: registration.capabilities,
};
}

async register(
registration: RegisteredBridgeWorker,
authorization?: string | {
Expand Down