@cadenza.io/service 2.7.0 → 2.8.0

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/dist/index.d.mts CHANGED
@@ -236,6 +236,8 @@ declare class ServiceRegistry {
236
236
  private remoteIntentDeputiesByTask;
237
237
  private dependeesByService;
238
238
  private dependeeByInstance;
239
+ private readinessDependeesByService;
240
+ private readinessDependeeByInstance;
239
241
  private lastHeartbeatAtByInstance;
240
242
  private missedHeartbeatsByInstance;
241
243
  private runtimeStatusFallbackInFlightByInstance;
@@ -270,6 +272,7 @@ declare class ServiceRegistry {
270
272
  handleServiceNotRespondingTask: Task;
271
273
  handleServiceHandshakeTask: Task;
272
274
  collectTransportDiagnosticsTask: Task;
275
+ collectReadinessTask: Task;
273
276
  private buildRemoteIntentDeputyKey;
274
277
  private normalizeIntentMaps;
275
278
  private registerRemoteIntentDeputy;
@@ -279,11 +282,16 @@ declare class ServiceRegistry {
279
282
  private getLocalInstance;
280
283
  private registerDependee;
281
284
  private unregisterDependee;
285
+ private getHeartbeatMisses;
286
+ private shouldRequireReadinessFromCommunicationTypes;
282
287
  private resolveRuntimeStatusSnapshot;
283
288
  private normalizeRuntimeStatusReport;
284
289
  private applyRuntimeStatusReport;
285
290
  private buildLocalRuntimeStatusReport;
286
291
  private selectRuntimeStatusReportForTarget;
292
+ private resolveRuntimeStatusFallbackInquiry;
293
+ private evaluateDependencyReadinessDetail;
294
+ private buildLocalReadinessReport;
287
295
  /**
288
296
  * Initializes a private constructor for managing service instances, remote signals,
289
297
  * service health, and handling updates or synchronization tasks. The constructor
package/dist/index.d.ts CHANGED
@@ -236,6 +236,8 @@ declare class ServiceRegistry {
236
236
  private remoteIntentDeputiesByTask;
237
237
  private dependeesByService;
238
238
  private dependeeByInstance;
239
+ private readinessDependeesByService;
240
+ private readinessDependeeByInstance;
239
241
  private lastHeartbeatAtByInstance;
240
242
  private missedHeartbeatsByInstance;
241
243
  private runtimeStatusFallbackInFlightByInstance;
@@ -270,6 +272,7 @@ declare class ServiceRegistry {
270
272
  handleServiceNotRespondingTask: Task;
271
273
  handleServiceHandshakeTask: Task;
272
274
  collectTransportDiagnosticsTask: Task;
275
+ collectReadinessTask: Task;
273
276
  private buildRemoteIntentDeputyKey;
274
277
  private normalizeIntentMaps;
275
278
  private registerRemoteIntentDeputy;
@@ -279,11 +282,16 @@ declare class ServiceRegistry {
279
282
  private getLocalInstance;
280
283
  private registerDependee;
281
284
  private unregisterDependee;
285
+ private getHeartbeatMisses;
286
+ private shouldRequireReadinessFromCommunicationTypes;
282
287
  private resolveRuntimeStatusSnapshot;
283
288
  private normalizeRuntimeStatusReport;
284
289
  private applyRuntimeStatusReport;
285
290
  private buildLocalRuntimeStatusReport;
286
291
  private selectRuntimeStatusReportForTarget;
292
+ private resolveRuntimeStatusFallbackInquiry;
293
+ private evaluateDependencyReadinessDetail;
294
+ private buildLocalReadinessReport;
287
295
  /**
288
296
  * Initializes a private constructor for managing service instances, remote signals,
289
297
  * service health, and handling updates or synchronization tasks. The constructor
package/dist/index.js CHANGED
@@ -299,6 +299,7 @@ var isBrowser = typeof window !== "undefined" && typeof window.document !== "und
299
299
  var META_INTENT_PREFIX = "meta-";
300
300
  var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
301
301
  var META_RUNTIME_STATUS_INTENT = "meta-runtime-status";
302
+ var META_READINESS_INTENT = "meta-readiness";
302
303
  function isPlainObject(value) {
303
304
  return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
304
305
  }
@@ -362,6 +363,100 @@ function summarizeResponderStatuses(statuses) {
362
363
  return { responded, failed, timedOut, pending };
363
364
  }
364
365
 
366
+ // src/utils/readiness.ts
367
+ function evaluateDependencyReadiness(input) {
368
+ const missedHeartbeats = Math.max(
369
+ 0,
370
+ Math.trunc(Number(input.missedHeartbeats) || 0)
371
+ );
372
+ const stale = missedHeartbeats > 0;
373
+ const timeoutReached = missedHeartbeats >= Math.max(1, input.missThreshold);
374
+ if (!input.exists) {
375
+ return {
376
+ state: "unavailable",
377
+ stale: true,
378
+ blocked: true,
379
+ reason: "missing"
380
+ };
381
+ }
382
+ if (timeoutReached) {
383
+ return {
384
+ state: "unavailable",
385
+ stale: true,
386
+ blocked: true,
387
+ reason: "heartbeat-timeout"
388
+ };
389
+ }
390
+ if (input.runtimeState === "unavailable" || !input.acceptingWork) {
391
+ return {
392
+ state: "unavailable",
393
+ stale,
394
+ blocked: true,
395
+ reason: "runtime-unavailable"
396
+ };
397
+ }
398
+ if (stale) {
399
+ return {
400
+ state: "degraded",
401
+ stale: true,
402
+ blocked: false,
403
+ reason: "heartbeat-stale"
404
+ };
405
+ }
406
+ if (input.runtimeState === "overloaded") {
407
+ return {
408
+ state: "overloaded",
409
+ stale: false,
410
+ blocked: false,
411
+ reason: "runtime-overloaded"
412
+ };
413
+ }
414
+ if (input.runtimeState === "degraded") {
415
+ return {
416
+ state: "degraded",
417
+ stale: false,
418
+ blocked: false,
419
+ reason: "runtime-degraded"
420
+ };
421
+ }
422
+ return {
423
+ state: "ready",
424
+ stale: false,
425
+ blocked: false,
426
+ reason: "runtime-healthy"
427
+ };
428
+ }
429
+ function summarizeDependencyReadiness(evaluations) {
430
+ const summary = {
431
+ total: evaluations.length,
432
+ ready: 0,
433
+ degraded: 0,
434
+ overloaded: 0,
435
+ unavailable: 0,
436
+ stale: 0
437
+ };
438
+ for (const evaluation of evaluations) {
439
+ if (evaluation.state === "ready") summary.ready++;
440
+ if (evaluation.state === "degraded") summary.degraded++;
441
+ if (evaluation.state === "overloaded") summary.overloaded++;
442
+ if (evaluation.state === "unavailable") summary.unavailable++;
443
+ if (evaluation.stale) summary.stale++;
444
+ }
445
+ return summary;
446
+ }
447
+ function resolveServiceReadinessState(localRuntimeState, localAcceptingWork, dependencySummary) {
448
+ if (localRuntimeState === "unavailable" || !localAcceptingWork) {
449
+ return "blocked";
450
+ }
451
+ if (dependencySummary.unavailable > 0) {
452
+ return "blocked";
453
+ }
454
+ if (dependencySummary.degraded > 0 || dependencySummary.overloaded > 0 || dependencySummary.stale > 0) {
455
+ return "degraded";
456
+ }
457
+ return "ready";
458
+ }
459
+
365
460
  // src/utils/runtimeStatus.ts
366
461
  function resolveRuntimeStatus(input) {
367
462
  const numberOfRunningGraphs = Math.max(
@@ -443,6 +538,8 @@ var INTERNAL_RUNTIME_STATUS_TASK_NAMES = /* @__PURE__ */ new Set([
443
538
  "Monitor dependee heartbeat freshness",
444
539
  "Resolve runtime status fallback inquiry",
445
540
  "Respond runtime status inquiry",
541
+ "Respond readiness inquiry",
542
+ "Collect distributed readiness",
446
543
  "Get status"
447
544
  ]);
448
545
  function readPositiveIntegerEnv(name, fallback) {
@@ -479,6 +576,8 @@ var ServiceRegistry = class _ServiceRegistry {
479
576
  this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
480
577
  this.dependeesByService = /* @__PURE__ */ new Map();
481
578
  this.dependeeByInstance = /* @__PURE__ */ new Map();
579
+ this.readinessDependeesByService = /* @__PURE__ */ new Map();
580
+ this.readinessDependeeByInstance = /* @__PURE__ */ new Map();
482
581
  this.lastHeartbeatAtByInstance = /* @__PURE__ */ new Map();
483
582
  this.missedHeartbeatsByInstance = /* @__PURE__ */ new Map();
484
583
  this.runtimeStatusFallbackInFlightByInstance = /* @__PURE__ */ new Set();
@@ -594,6 +693,66 @@ var ServiceRegistry = class _ServiceRegistry {
594
693
  },
595
694
  "Responds to runtime-status inquiries with local service instance status."
596
695
  ).respondsTo(META_RUNTIME_STATUS_INTENT);
696
+ CadenzaService.defineIntent({
697
+ name: META_READINESS_INTENT,
698
+ description: "Gather service readiness reports derived from local runtime status and required dependees.",
699
+ input: {
700
+ type: "object",
701
+ properties: {
702
+ detailLevel: {
703
+ type: "string",
704
+ constraints: {
705
+ oneOf: ["minimal", "full"]
706
+ }
707
+ },
708
+ includeDependencies: {
709
+ type: "boolean"
710
+ },
711
+ refreshStaleDependencies: {
712
+ type: "boolean"
713
+ },
714
+ targetServiceName: {
715
+ type: "string"
716
+ },
717
+ targetServiceInstanceId: {
718
+ type: "string"
719
+ }
720
+ }
721
+ },
722
+ output: {
723
+ type: "object",
724
+ properties: {
725
+ readinessReports: {
726
+ type: "array"
727
+ }
728
+ }
729
+ }
730
+ });
731
+ CadenzaService.createMetaTask(
732
+ "Respond readiness inquiry",
733
+ async (ctx) => {
734
+ const targetServiceName = ctx.targetServiceName;
735
+ const targetServiceInstanceId = ctx.targetServiceInstanceId;
736
+ const report = await this.buildLocalReadinessReport({
737
+ detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
738
+ includeDependencies: ctx.includeDependencies,
739
+ refreshStaleDependencies: ctx.refreshStaleDependencies
740
+ });
741
+ if (!report) {
742
+ return {};
743
+ }
744
+ if (targetServiceName && targetServiceName !== report.serviceName) {
745
+ return {};
746
+ }
747
+ if (targetServiceInstanceId && targetServiceInstanceId !== report.serviceInstanceId) {
748
+ return {};
749
+ }
750
+ return {
751
+ readinessReports: [report]
752
+ };
753
+ },
754
+ "Responds to distributed readiness inquiries using required dependee health."
755
+ ).respondsTo(META_READINESS_INTENT);
597
756
  this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
598
757
  "Handle Instance Update",
599
758
  (ctx, emit) => {
@@ -710,7 +869,11 @@ var ServiceRegistry = class _ServiceRegistry {
710
869
  if (!ctx.serviceName || !ctx.serviceInstanceId) {
711
870
  return false;
712
871
  }
713
- this.registerDependee(ctx.serviceName, ctx.serviceInstanceId);
872
+ this.registerDependee(ctx.serviceName, ctx.serviceInstanceId, {
873
+ requiredForReadiness: this.shouldRequireReadinessFromCommunicationTypes(
874
+ ctx.communicationTypes
875
+ )
876
+ });
714
877
  return true;
715
878
  },
716
879
  "Tracks remote dependency instances for runtime heartbeat monitoring."
@@ -1395,36 +1558,20 @@ var ServiceRegistry = class _ServiceRegistry {
1395
1558
  return false;
1396
1559
  }
1397
1560
  try {
1398
- const inquiryResult = await CadenzaService.inquire(
1399
- META_RUNTIME_STATUS_INTENT,
1400
- {
1401
- targetServiceName: serviceName,
1402
- targetServiceInstanceId: serviceInstanceId,
1403
- detailLevel: ctx.detailLevel === "full" ? "full" : "minimal"
1404
- },
1561
+ const { report, inquiryMeta } = await this.resolveRuntimeStatusFallbackInquiry(
1562
+ serviceName,
1563
+ serviceInstanceId,
1405
1564
  {
1406
- overallTimeoutMs: ctx.overallTimeoutMs ?? this.runtimeStatusFallbackTimeoutMs,
1407
- perResponderTimeoutMs: ctx.perResponderTimeoutMs ?? Math.max(250, Math.floor(this.runtimeStatusFallbackTimeoutMs * 0.75)),
1408
- requireComplete: ctx.requireComplete ?? false
1565
+ detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
1566
+ overallTimeoutMs: ctx.overallTimeoutMs,
1567
+ perResponderTimeoutMs: ctx.perResponderTimeoutMs,
1568
+ requireComplete: ctx.requireComplete
1409
1569
  }
1410
1570
  );
1411
- const report = this.selectRuntimeStatusReportForTarget(
1412
- inquiryResult,
1413
- serviceName,
1414
- serviceInstanceId
1415
- );
1416
- if (!report) {
1417
- throw new Error(
1418
- `No runtime status report for ${serviceName}/${serviceInstanceId}`
1419
- );
1420
- }
1421
- this.applyRuntimeStatusReport(report);
1422
- this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
1423
- this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
1424
1571
  return {
1425
1572
  ...ctx,
1426
1573
  runtimeStatusReport: report,
1427
- __inquiryMeta: inquiryResult.__inquiryMeta
1574
+ __inquiryMeta: inquiryMeta
1428
1575
  };
1429
1576
  } catch (error) {
1430
1577
  const instance = this.getInstance(serviceName, serviceInstanceId);
@@ -1458,6 +1605,27 @@ var ServiceRegistry = class _ServiceRegistry {
1458
1605
  },
1459
1606
  "Runs runtime-status inquiry fallback for a dependee instance after missed heartbeats."
1460
1607
  ).doOn("meta.service_registry.runtime_status_fallback_requested").emits("meta.service_registry.runtime_status_fallback_resolved").emitsOnFail("meta.service_registry.runtime_status_fallback_failed");
1608
+ this.collectReadinessTask = CadenzaService.createMetaTask(
1609
+ "Collect distributed readiness",
1610
+ async (ctx) => {
1611
+ const inquiryResult = await CadenzaService.inquire(
1612
+ META_READINESS_INTENT,
1613
+ {
1614
+ detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
1615
+ includeDependencies: ctx.includeDependencies,
1616
+ refreshStaleDependencies: ctx.refreshStaleDependencies,
1617
+ targetServiceName: ctx.targetServiceName,
1618
+ targetServiceInstanceId: ctx.targetServiceInstanceId
1619
+ },
1620
+ ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
1621
+ );
1622
+ return {
1623
+ ...ctx,
1624
+ ...inquiryResult
1625
+ };
1626
+ },
1627
+ "Collects distributed readiness reports from services."
1628
+ ).doOn("meta.service_registry.readiness_requested").emits("meta.service_registry.readiness_collected").emitsOnFail("meta.service_registry.readiness_failed");
1461
1629
  this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
1462
1630
  "Collect transport diagnostics",
1463
1631
  async (ctx) => {
@@ -1806,7 +1974,7 @@ var ServiceRegistry = class _ServiceRegistry {
1806
1974
  }
1807
1975
  return this.getInstance(this.serviceName, this.serviceInstanceId);
1808
1976
  }
1809
- registerDependee(serviceName, serviceInstanceId) {
1977
+ registerDependee(serviceName, serviceInstanceId, options = {}) {
1810
1978
  if (!serviceName || !serviceInstanceId) {
1811
1979
  return;
1812
1980
  }
@@ -1815,6 +1983,13 @@ var ServiceRegistry = class _ServiceRegistry {
1815
1983
  }
1816
1984
  this.dependeesByService.get(serviceName).add(serviceInstanceId);
1817
1985
  this.dependeeByInstance.set(serviceInstanceId, serviceName);
1986
+ if (options.requiredForReadiness) {
1987
+ if (!this.readinessDependeesByService.has(serviceName)) {
1988
+ this.readinessDependeesByService.set(serviceName, /* @__PURE__ */ new Set());
1989
+ }
1990
+ this.readinessDependeesByService.get(serviceName).add(serviceInstanceId);
1991
+ this.readinessDependeeByInstance.set(serviceInstanceId, serviceName);
1992
+ }
1818
1993
  this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
1819
1994
  this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
1820
1995
  }
@@ -1827,10 +2002,39 @@ var ServiceRegistry = class _ServiceRegistry {
1827
2002
  }
1828
2003
  }
1829
2004
  this.dependeeByInstance.delete(serviceInstanceId);
2005
+ const readinessDependeeServiceName = serviceName ?? this.readinessDependeeByInstance.get(serviceInstanceId);
2006
+ if (readinessDependeeServiceName) {
2007
+ this.readinessDependeesByService.get(readinessDependeeServiceName)?.delete(serviceInstanceId);
2008
+ if (!this.readinessDependeesByService.get(readinessDependeeServiceName)?.size) {
2009
+ this.readinessDependeesByService.delete(readinessDependeeServiceName);
2010
+ }
2011
+ }
2012
+ this.readinessDependeeByInstance.delete(serviceInstanceId);
1830
2013
  this.lastHeartbeatAtByInstance.delete(serviceInstanceId);
1831
2014
  this.missedHeartbeatsByInstance.delete(serviceInstanceId);
1832
2015
  this.runtimeStatusFallbackInFlightByInstance.delete(serviceInstanceId);
1833
2016
  }
2017
+ getHeartbeatMisses(serviceInstanceId, now = Date.now()) {
2018
+ const observedMisses = this.missedHeartbeatsByInstance.get(serviceInstanceId) ?? 0;
2019
+ const lastHeartbeatAt = this.lastHeartbeatAtByInstance.get(serviceInstanceId) ?? 0;
2020
+ if (lastHeartbeatAt <= 0) {
2021
+ return Math.max(observedMisses, this.runtimeStatusMissThreshold);
2022
+ }
2023
+ const estimatedMisses = Math.max(
2024
+ 0,
2025
+ Math.floor((now - lastHeartbeatAt) / this.runtimeStatusHeartbeatIntervalMs)
2026
+ );
2027
+ return Math.max(observedMisses, estimatedMisses);
2028
+ }
2029
+ shouldRequireReadinessFromCommunicationTypes(communicationTypes) {
2030
+ if (!Array.isArray(communicationTypes)) {
2031
+ return false;
2032
+ }
2033
+ return communicationTypes.some((type) => {
2034
+ const normalized = String(type).toLowerCase();
2035
+ return normalized === "delegation" || normalized === "inquiry";
2036
+ });
2037
+ }
1834
2038
  resolveRuntimeStatusSnapshot(numberOfRunningGraphs, isActive, isNonResponsive, isBlocked) {
1835
2039
  return resolveRuntimeStatus({
1836
2040
  numberOfRunningGraphs,
@@ -1974,6 +2178,166 @@ var ServiceRegistry = class _ServiceRegistry {
1974
2178
  }
1975
2179
  return null;
1976
2180
  }
2181
+ async resolveRuntimeStatusFallbackInquiry(serviceName, serviceInstanceId, options = {}) {
2182
+ const inquiryResult = await CadenzaService.inquire(
2183
+ META_RUNTIME_STATUS_INTENT,
2184
+ {
2185
+ targetServiceName: serviceName,
2186
+ targetServiceInstanceId: serviceInstanceId,
2187
+ detailLevel: options.detailLevel ?? "minimal"
2188
+ },
2189
+ {
2190
+ overallTimeoutMs: options.overallTimeoutMs ?? this.runtimeStatusFallbackTimeoutMs,
2191
+ perResponderTimeoutMs: options.perResponderTimeoutMs ?? Math.max(250, Math.floor(this.runtimeStatusFallbackTimeoutMs * 0.75)),
2192
+ requireComplete: options.requireComplete ?? false
2193
+ }
2194
+ );
2195
+ const report = this.selectRuntimeStatusReportForTarget(
2196
+ inquiryResult,
2197
+ serviceName,
2198
+ serviceInstanceId
2199
+ );
2200
+ if (!report) {
2201
+ throw new Error(
2202
+ `No runtime status report for ${serviceName}/${serviceInstanceId}`
2203
+ );
2204
+ }
2205
+ if (!this.applyRuntimeStatusReport(report)) {
2206
+ throw new Error(
2207
+ `No tracked instance for runtime fallback ${serviceName}/${serviceInstanceId}`
2208
+ );
2209
+ }
2210
+ this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
2211
+ this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
2212
+ return {
2213
+ report,
2214
+ inquiryMeta: inquiryResult.__inquiryMeta ?? {}
2215
+ };
2216
+ }
2217
+ evaluateDependencyReadinessDetail(serviceName, serviceInstanceId, now = Date.now()) {
2218
+ const instance = this.getInstance(serviceName, serviceInstanceId);
2219
+ const missedHeartbeats = this.getHeartbeatMisses(serviceInstanceId, now);
2220
+ const runtimeState = instance ? instance.runtimeState ?? this.resolveRuntimeStatusSnapshot(
2221
+ instance.numberOfRunningGraphs ?? 0,
2222
+ instance.isActive,
2223
+ instance.isNonResponsive,
2224
+ instance.isBlocked
2225
+ ).state : "unavailable";
2226
+ const acceptingWork = instance ? typeof instance.acceptingWork === "boolean" ? instance.acceptingWork : this.resolveRuntimeStatusSnapshot(
2227
+ instance.numberOfRunningGraphs ?? 0,
2228
+ instance.isActive,
2229
+ instance.isNonResponsive,
2230
+ instance.isBlocked
2231
+ ).acceptingWork : false;
2232
+ const evaluation = evaluateDependencyReadiness({
2233
+ exists: Boolean(instance),
2234
+ runtimeState,
2235
+ acceptingWork,
2236
+ missedHeartbeats,
2237
+ missThreshold: this.runtimeStatusMissThreshold
2238
+ });
2239
+ const lastHeartbeat = this.lastHeartbeatAtByInstance.get(serviceInstanceId);
2240
+ return {
2241
+ serviceName,
2242
+ serviceInstanceId,
2243
+ dependencyState: evaluation.state,
2244
+ runtimeState,
2245
+ acceptingWork,
2246
+ missedHeartbeats,
2247
+ stale: evaluation.stale,
2248
+ blocked: evaluation.blocked,
2249
+ reason: evaluation.reason,
2250
+ lastHeartbeatAt: lastHeartbeat ? new Date(lastHeartbeat).toISOString() : null,
2251
+ reportedAt: instance?.reportedAt ?? null
2252
+ };
2253
+ }
2254
+ async buildLocalReadinessReport(options = {}) {
2255
+ const localRuntime = this.buildLocalRuntimeStatusReport("minimal");
2256
+ if (!localRuntime) {
2257
+ return null;
2258
+ }
2259
+ const detailLevel = options.detailLevel ?? "minimal";
2260
+ const includeDependencies = options.includeDependencies ?? detailLevel === "full";
2261
+ const refreshStaleDependencies = options.refreshStaleDependencies ?? true;
2262
+ const dependencyPairs = Array.from(this.readinessDependeesByService.entries()).flatMap(
2263
+ ([serviceName, instanceIds]) => Array.from(instanceIds).map((serviceInstanceId) => ({
2264
+ serviceName,
2265
+ serviceInstanceId
2266
+ }))
2267
+ ).sort((left, right) => {
2268
+ if (left.serviceName !== right.serviceName) {
2269
+ return left.serviceName.localeCompare(right.serviceName);
2270
+ }
2271
+ return left.serviceInstanceId.localeCompare(right.serviceInstanceId);
2272
+ });
2273
+ if (refreshStaleDependencies) {
2274
+ for (const dependency of dependencyPairs) {
2275
+ const misses = this.getHeartbeatMisses(dependency.serviceInstanceId);
2276
+ if (misses < this.runtimeStatusMissThreshold) {
2277
+ continue;
2278
+ }
2279
+ if (this.runtimeStatusFallbackInFlightByInstance.has(
2280
+ dependency.serviceInstanceId
2281
+ )) {
2282
+ continue;
2283
+ }
2284
+ this.runtimeStatusFallbackInFlightByInstance.add(
2285
+ dependency.serviceInstanceId
2286
+ );
2287
+ try {
2288
+ await this.resolveRuntimeStatusFallbackInquiry(
2289
+ dependency.serviceName,
2290
+ dependency.serviceInstanceId
2291
+ );
2292
+ } catch (error) {
2293
+ CadenzaService.log(
2294
+ "Readiness dependency fallback failed.",
2295
+ {
2296
+ serviceName: dependency.serviceName,
2297
+ serviceInstanceId: dependency.serviceInstanceId,
2298
+ error: error instanceof Error ? error.message : String(error)
2299
+ },
2300
+ "warning"
2301
+ );
2302
+ } finally {
2303
+ this.runtimeStatusFallbackInFlightByInstance.delete(
2304
+ dependency.serviceInstanceId
2305
+ );
2306
+ }
2307
+ }
2308
+ }
2309
+ const now = Date.now();
2310
+ const dependencyDetails = dependencyPairs.map(
2311
+ (dependency) => this.evaluateDependencyReadinessDetail(
2312
+ dependency.serviceName,
2313
+ dependency.serviceInstanceId,
2314
+ now
2315
+ )
2316
+ );
2317
+ const dependencySummary = summarizeDependencyReadiness(
2318
+ dependencyDetails.map((detail) => ({
2319
+ state: detail.dependencyState,
2320
+ stale: detail.stale,
2321
+ blocked: detail.blocked,
2322
+ reason: detail.reason
2323
+ }))
2324
+ );
2325
+ const readinessState = resolveServiceReadinessState(
2326
+ localRuntime.state,
2327
+ localRuntime.acceptingWork,
2328
+ dependencySummary
2329
+ );
2330
+ return {
2331
+ serviceName: localRuntime.serviceName,
2332
+ serviceInstanceId: localRuntime.serviceInstanceId,
2333
+ reportedAt: new Date(now).toISOString(),
2334
+ readinessState,
2335
+ runtimeState: localRuntime.state,
2336
+ acceptingWork: localRuntime.acceptingWork,
2337
+ dependencySummary,
2338
+ ...includeDependencies ? { dependencies: dependencyDetails } : {}
2339
+ };
2340
+ }
1977
2341
  reset() {
1978
2342
  this.instances.clear();
1979
2343
  this.deputies.clear();
@@ -1983,6 +2347,8 @@ var ServiceRegistry = class _ServiceRegistry {
1983
2347
  this.remoteIntentDeputiesByTask.clear();
1984
2348
  this.dependeesByService.clear();
1985
2349
  this.dependeeByInstance.clear();
2350
+ this.readinessDependeesByService.clear();
2351
+ this.readinessDependeeByInstance.clear();
1986
2352
  this.lastHeartbeatAtByInstance.clear();
1987
2353
  this.missedHeartbeatsByInstance.clear();
1988
2354
  this.runtimeStatusFallbackInFlightByInstance.clear();