Skip to content

Commit 7444664

Browse files
committed
fix(runtime): stop reporting inert workers as ready
1 parent 727196a commit 7444664

3 files changed

Lines changed: 90 additions & 5 deletions

File tree

apps/server/src/app.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,19 +339,31 @@ class Application {
339339
logger.error(' Memory | Error:', safeError);
340340
}
341341

342-
// 4. Check canonical Runtime Event authority.
342+
// 4. Check both the canonical Event authority and the executable Runtime.
343+
// Event-store health alone must never be reported as continuous execution
344+
// readiness when the graph or durable workers have not been started.
343345
try {
344346
const runtimeHealth = await this.canonicalRuntime?.get().backbone.eventStore.health();
345-
const healthy = runtimeHealth?.status === 'healthy';
347+
const execution = this.canonicalRuntime?.executionReadiness();
348+
const eventStoreHealthy = runtimeHealth?.status === 'healthy';
349+
const healthy = eventStoreHealthy && execution?.ready === true;
346350
checks.push({
347351
name: 'Runtime',
348352
status: healthy ? 'pass' : 'fail',
349-
detail: runtimeHealth?.message ?? runtimeHealth?.status ?? 'not initialized',
353+
detail: !eventStoreHealthy
354+
? (runtimeHealth?.message ?? runtimeHealth?.status ?? 'Event authority not initialized')
355+
: (execution?.message ?? 'Runtime execution state is unavailable'),
350356
});
351357
if (healthy) {
352-
logger.info(' ✅ Runtime │ Canonical Event store ready');
358+
logger.info(' ✅ Runtime │ Canonical execution workers ready');
353359
} else {
354-
logger.error(' ❌ Runtime │ Canonical Event store unavailable');
360+
logger.error(
361+
` ❌ Runtime │ ${
362+
!eventStoreHealthy
363+
? 'Canonical Event store unavailable'
364+
: (execution?.message ?? 'Execution state unavailable')
365+
}`
366+
);
355367
}
356368
} catch (err) {
357369
checks.push({ name: 'Runtime', status: 'fail', detail: String(err) });

apps/server/src/runtime/ServerCanonicalRuntime.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,24 @@ describe('ServerCanonicalRuntime', () => {
107107
const service = createService(new InMemoryEventStore());
108108
const bindings = executionBindings();
109109

110+
expect(service.executionReadiness()).toMatchObject({
111+
ready: false,
112+
state: 'not_initialized',
113+
});
110114
expect(() => service.composeRuntime(bindings)).toThrow('Canonical Runtime is not initialized');
111115

112116
const canonical = await service.initialize();
117+
expect(service.executionReadiness()).toMatchObject({
118+
ready: false,
119+
state: 'event_authority_ready',
120+
});
113121
const runtime = service.composeRuntime(bindings);
114122

123+
expect(service.executionReadiness()).toMatchObject({
124+
ready: false,
125+
state: 'execution_graph_ready',
126+
});
127+
115128
expect(runtime.events).toBe(canonical.backbone.events);
116129
expect(runtime.projections).toBe(canonical.backbone.projections);
117130
expect(runtime.projectionStore).toBe(canonical.backbone.projectionStore);
@@ -137,6 +150,11 @@ describe('ServerCanonicalRuntime', () => {
137150

138151
expect(first).toBe(second);
139152
expect(service.areWorkersRunning()).toBe(true);
153+
expect(service.executionReadiness()).toEqual({
154+
ready: true,
155+
state: 'workers_running',
156+
message: 'Canonical Runtime execution graph and durable workers are running',
157+
});
140158

141159
const firstClose = service.close();
142160
const secondClose = service.close();
@@ -146,6 +164,7 @@ describe('ServerCanonicalRuntime', () => {
146164
expect(first.timer.isRunning()).toBe(false);
147165
expect(first.recovery.isRunning()).toBe(false);
148166
expect(service.areWorkersRunning()).toBe(false);
167+
expect(service.executionReadiness()).toMatchObject({ ready: false, state: 'closed' });
149168
expect(() => service.composeRuntime(executionBindings())).toThrow(
150169
'Canonical Runtime is closed'
151170
);

apps/server/src/runtime/ServerCanonicalRuntime.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ export interface ServerCanonicalRuntimeComposition {
5050
migration: CanonicalEventFamilyMigrationReport;
5151
}
5252

53+
export type ServerRuntimeExecutionState =
54+
| 'not_initialized'
55+
| 'event_authority_ready'
56+
| 'execution_graph_ready'
57+
| 'workers_running'
58+
| 'closed';
59+
60+
export interface ServerRuntimeExecutionReadiness {
61+
ready: boolean;
62+
state: ServerRuntimeExecutionState;
63+
message: string;
64+
}
65+
5366
/**
5467
* Owns the Server cutover from compatibility Events to the canonical Runtime
5568
* store. Migration and bounded replay complete before the merged EventStore is
@@ -222,6 +235,47 @@ export class ServerCanonicalRuntime {
222235
return this.workerLifecycle?.isRunning() ?? false;
223236
}
224237

238+
/**
239+
* Reports product execution readiness separately from Event-store health.
240+
* A healthy Event authority is necessary, but it does not prove that the
241+
* execution graph or any durable worker loop is active.
242+
*/
243+
executionReadiness(): Readonly<ServerRuntimeExecutionReadiness> {
244+
if (this.closed) {
245+
return Object.freeze({
246+
ready: false,
247+
state: 'closed',
248+
message: 'Canonical Runtime is closed',
249+
});
250+
}
251+
if (!this.composition) {
252+
return Object.freeze({
253+
ready: false,
254+
state: 'not_initialized',
255+
message: 'Canonical Runtime Event authority is not initialized',
256+
});
257+
}
258+
if (!this.runtimeComposition) {
259+
return Object.freeze({
260+
ready: false,
261+
state: 'event_authority_ready',
262+
message: 'Canonical Runtime execution graph is not composed',
263+
});
264+
}
265+
if (!this.areWorkersRunning()) {
266+
return Object.freeze({
267+
ready: false,
268+
state: 'execution_graph_ready',
269+
message: 'Canonical Runtime durable workers are not running',
270+
});
271+
}
272+
return Object.freeze({
273+
ready: true,
274+
state: 'workers_running',
275+
message: 'Canonical Runtime execution graph and durable workers are running',
276+
});
277+
}
278+
225279
isInitialized(): boolean {
226280
return !this.closed && this.composition !== undefined;
227281
}

0 commit comments

Comments
 (0)