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
27 changes: 27 additions & 0 deletions packages/runtime/src/http-dispatcher.ready.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
import { describe, it, expect } from 'vitest';
import { HttpDispatcher } from './http-dispatcher.js';

function kernel(state: string): any {
return {
getState: () => state,
getService: () => undefined,
getServiceAsync: async () => undefined,
};
}
const ctx: any = {};

describe('HttpDispatcher — GET /ready readiness probe', () => {
it('returns 200 when the kernel is running', async () => {
const res = await new HttpDispatcher(kernel('running')).dispatch('GET', '/ready', undefined, undefined, ctx);
expect(res.handled).toBe(true);
expect(res.response.status).toBe(200);
expect(res.response.body.data.state).toBe('running');
});

it('returns 503 while booting or shutting down', async () => {
for (const state of ['idle', 'initializing', 'stopping', 'stopped']) {
const res = await new HttpDispatcher(kernel(state)).dispatch('GET', '/ready', undefined, undefined, ctx);
expect(res.response.status).toBe(503);
}
});
});
16 changes: 15 additions & 1 deletion packages/runtime/src/http-dispatcher.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -699,7 +699,7 @@ export class HttpDispatcher {
if (!this.enforceMembership) return null;

// Control-plane paths — never gated by project membership.
const skipPaths = ['/auth', '/cloud', '/health', '/discovery'];
const skipPaths = ['/auth', '/cloud', '/health', '/ready', '/discovery'];
if (skipPaths.some(p => path.startsWith(p))) return null;

// Public share-link resolve/messages — the token IS the authorisation,
Expand DownExpand Up@@ -3076,6 +3076,20 @@ export class HttpDispatcher {
};
}

// 0b2. Readiness Endpoint (GET /ready) — k8s / load-balancer readiness probe.
// 200 only when the kernel is fully running; 503 while booting
// (idle/initializing) or shutting down (stopping/stopped) so a load
// balancer stops routing to this replica BEFORE in-flight requests are
// drained and the server closes (graceful rolling restart).
if (cleanPath === '/ready' && method === 'GET') {
const state: string = typeof (this.kernel as any)?.getState === 'function'
? (this.kernel as any).getState()
: 'running';
return state === 'running'
? { handled: true, response: this.success({ status: 'ready', state }) }
: { handled: true, response: this.error('Service not ready', 503, { state }) };
}

// 0c. Plan-A diagnostics removed; the seed-replay and oauth2/callback
// probes were temporary debugging tools used during the SSO rollout.

Expand Down