@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 +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +392 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +392 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
1399
|
-
|
|
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
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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:
|
|
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();
|