@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.mjs
CHANGED
|
@@ -251,6 +251,7 @@ var isBrowser = typeof window !== "undefined" && typeof window.document !== "und
|
|
|
251
251
|
var META_INTENT_PREFIX = "meta-";
|
|
252
252
|
var META_RUNTIME_TRANSPORT_DIAGNOSTICS_INTENT = "meta-runtime-transport-diagnostics";
|
|
253
253
|
var META_RUNTIME_STATUS_INTENT = "meta-runtime-status";
|
|
254
|
+
var META_READINESS_INTENT = "meta-readiness";
|
|
254
255
|
function isPlainObject(value) {
|
|
255
256
|
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
256
257
|
}
|
|
@@ -314,6 +315,100 @@ function summarizeResponderStatuses(statuses) {
|
|
|
314
315
|
return { responded, failed, timedOut, pending };
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
// src/utils/readiness.ts
|
|
319
|
+
function evaluateDependencyReadiness(input) {
|
|
320
|
+
const missedHeartbeats = Math.max(
|
|
321
|
+
0,
|
|
322
|
+
Math.trunc(Number(input.missedHeartbeats) || 0)
|
|
323
|
+
);
|
|
324
|
+
const stale = missedHeartbeats > 0;
|
|
325
|
+
const timeoutReached = missedHeartbeats >= Math.max(1, input.missThreshold);
|
|
326
|
+
if (!input.exists) {
|
|
327
|
+
return {
|
|
328
|
+
state: "unavailable",
|
|
329
|
+
stale: true,
|
|
330
|
+
blocked: true,
|
|
331
|
+
reason: "missing"
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (timeoutReached) {
|
|
335
|
+
return {
|
|
336
|
+
state: "unavailable",
|
|
337
|
+
stale: true,
|
|
338
|
+
blocked: true,
|
|
339
|
+
reason: "heartbeat-timeout"
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (input.runtimeState === "unavailable" || !input.acceptingWork) {
|
|
343
|
+
return {
|
|
344
|
+
state: "unavailable",
|
|
345
|
+
stale,
|
|
346
|
+
blocked: true,
|
|
347
|
+
reason: "runtime-unavailable"
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (stale) {
|
|
351
|
+
return {
|
|
352
|
+
state: "degraded",
|
|
353
|
+
stale: true,
|
|
354
|
+
blocked: false,
|
|
355
|
+
reason: "heartbeat-stale"
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (input.runtimeState === "overloaded") {
|
|
359
|
+
return {
|
|
360
|
+
state: "overloaded",
|
|
361
|
+
stale: false,
|
|
362
|
+
blocked: false,
|
|
363
|
+
reason: "runtime-overloaded"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (input.runtimeState === "degraded") {
|
|
367
|
+
return {
|
|
368
|
+
state: "degraded",
|
|
369
|
+
stale: false,
|
|
370
|
+
blocked: false,
|
|
371
|
+
reason: "runtime-degraded"
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
state: "ready",
|
|
376
|
+
stale: false,
|
|
377
|
+
blocked: false,
|
|
378
|
+
reason: "runtime-healthy"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function summarizeDependencyReadiness(evaluations) {
|
|
382
|
+
const summary = {
|
|
383
|
+
total: evaluations.length,
|
|
384
|
+
ready: 0,
|
|
385
|
+
degraded: 0,
|
|
386
|
+
overloaded: 0,
|
|
387
|
+
unavailable: 0,
|
|
388
|
+
stale: 0
|
|
389
|
+
};
|
|
390
|
+
for (const evaluation of evaluations) {
|
|
391
|
+
if (evaluation.state === "ready") summary.ready++;
|
|
392
|
+
if (evaluation.state === "degraded") summary.degraded++;
|
|
393
|
+
if (evaluation.state === "overloaded") summary.overloaded++;
|
|
394
|
+
if (evaluation.state === "unavailable") summary.unavailable++;
|
|
395
|
+
if (evaluation.stale) summary.stale++;
|
|
396
|
+
}
|
|
397
|
+
return summary;
|
|
398
|
+
}
|
|
399
|
+
function resolveServiceReadinessState(localRuntimeState, localAcceptingWork, dependencySummary) {
|
|
400
|
+
if (localRuntimeState === "unavailable" || !localAcceptingWork) {
|
|
401
|
+
return "blocked";
|
|
402
|
+
}
|
|
403
|
+
if (dependencySummary.unavailable > 0) {
|
|
404
|
+
return "blocked";
|
|
405
|
+
}
|
|
406
|
+
if (dependencySummary.degraded > 0 || dependencySummary.overloaded > 0 || dependencySummary.stale > 0) {
|
|
407
|
+
return "degraded";
|
|
408
|
+
}
|
|
409
|
+
return "ready";
|
|
410
|
+
}
|
|
411
|
+
|
|
317
412
|
// src/utils/runtimeStatus.ts
|
|
318
413
|
function resolveRuntimeStatus(input) {
|
|
319
414
|
const numberOfRunningGraphs = Math.max(
|
|
@@ -395,6 +490,8 @@ var INTERNAL_RUNTIME_STATUS_TASK_NAMES = /* @__PURE__ */ new Set([
|
|
|
395
490
|
"Monitor dependee heartbeat freshness",
|
|
396
491
|
"Resolve runtime status fallback inquiry",
|
|
397
492
|
"Respond runtime status inquiry",
|
|
493
|
+
"Respond readiness inquiry",
|
|
494
|
+
"Collect distributed readiness",
|
|
398
495
|
"Get status"
|
|
399
496
|
]);
|
|
400
497
|
function readPositiveIntegerEnv(name, fallback) {
|
|
@@ -431,6 +528,8 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
431
528
|
this.remoteIntentDeputiesByTask = /* @__PURE__ */ new Map();
|
|
432
529
|
this.dependeesByService = /* @__PURE__ */ new Map();
|
|
433
530
|
this.dependeeByInstance = /* @__PURE__ */ new Map();
|
|
531
|
+
this.readinessDependeesByService = /* @__PURE__ */ new Map();
|
|
532
|
+
this.readinessDependeeByInstance = /* @__PURE__ */ new Map();
|
|
434
533
|
this.lastHeartbeatAtByInstance = /* @__PURE__ */ new Map();
|
|
435
534
|
this.missedHeartbeatsByInstance = /* @__PURE__ */ new Map();
|
|
436
535
|
this.runtimeStatusFallbackInFlightByInstance = /* @__PURE__ */ new Set();
|
|
@@ -546,6 +645,66 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
546
645
|
},
|
|
547
646
|
"Responds to runtime-status inquiries with local service instance status."
|
|
548
647
|
).respondsTo(META_RUNTIME_STATUS_INTENT);
|
|
648
|
+
CadenzaService.defineIntent({
|
|
649
|
+
name: META_READINESS_INTENT,
|
|
650
|
+
description: "Gather service readiness reports derived from local runtime status and required dependees.",
|
|
651
|
+
input: {
|
|
652
|
+
type: "object",
|
|
653
|
+
properties: {
|
|
654
|
+
detailLevel: {
|
|
655
|
+
type: "string",
|
|
656
|
+
constraints: {
|
|
657
|
+
oneOf: ["minimal", "full"]
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
includeDependencies: {
|
|
661
|
+
type: "boolean"
|
|
662
|
+
},
|
|
663
|
+
refreshStaleDependencies: {
|
|
664
|
+
type: "boolean"
|
|
665
|
+
},
|
|
666
|
+
targetServiceName: {
|
|
667
|
+
type: "string"
|
|
668
|
+
},
|
|
669
|
+
targetServiceInstanceId: {
|
|
670
|
+
type: "string"
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
output: {
|
|
675
|
+
type: "object",
|
|
676
|
+
properties: {
|
|
677
|
+
readinessReports: {
|
|
678
|
+
type: "array"
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
CadenzaService.createMetaTask(
|
|
684
|
+
"Respond readiness inquiry",
|
|
685
|
+
async (ctx) => {
|
|
686
|
+
const targetServiceName = ctx.targetServiceName;
|
|
687
|
+
const targetServiceInstanceId = ctx.targetServiceInstanceId;
|
|
688
|
+
const report = await this.buildLocalReadinessReport({
|
|
689
|
+
detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
|
|
690
|
+
includeDependencies: ctx.includeDependencies,
|
|
691
|
+
refreshStaleDependencies: ctx.refreshStaleDependencies
|
|
692
|
+
});
|
|
693
|
+
if (!report) {
|
|
694
|
+
return {};
|
|
695
|
+
}
|
|
696
|
+
if (targetServiceName && targetServiceName !== report.serviceName) {
|
|
697
|
+
return {};
|
|
698
|
+
}
|
|
699
|
+
if (targetServiceInstanceId && targetServiceInstanceId !== report.serviceInstanceId) {
|
|
700
|
+
return {};
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
readinessReports: [report]
|
|
704
|
+
};
|
|
705
|
+
},
|
|
706
|
+
"Responds to distributed readiness inquiries using required dependee health."
|
|
707
|
+
).respondsTo(META_READINESS_INTENT);
|
|
549
708
|
this.handleInstanceUpdateTask = CadenzaService.createMetaTask(
|
|
550
709
|
"Handle Instance Update",
|
|
551
710
|
(ctx, emit) => {
|
|
@@ -662,7 +821,11 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
662
821
|
if (!ctx.serviceName || !ctx.serviceInstanceId) {
|
|
663
822
|
return false;
|
|
664
823
|
}
|
|
665
|
-
this.registerDependee(ctx.serviceName, ctx.serviceInstanceId
|
|
824
|
+
this.registerDependee(ctx.serviceName, ctx.serviceInstanceId, {
|
|
825
|
+
requiredForReadiness: this.shouldRequireReadinessFromCommunicationTypes(
|
|
826
|
+
ctx.communicationTypes
|
|
827
|
+
)
|
|
828
|
+
});
|
|
666
829
|
return true;
|
|
667
830
|
},
|
|
668
831
|
"Tracks remote dependency instances for runtime heartbeat monitoring."
|
|
@@ -1347,36 +1510,20 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1347
1510
|
return false;
|
|
1348
1511
|
}
|
|
1349
1512
|
try {
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
targetServiceName: serviceName,
|
|
1354
|
-
targetServiceInstanceId: serviceInstanceId,
|
|
1355
|
-
detailLevel: ctx.detailLevel === "full" ? "full" : "minimal"
|
|
1356
|
-
},
|
|
1513
|
+
const { report, inquiryMeta } = await this.resolveRuntimeStatusFallbackInquiry(
|
|
1514
|
+
serviceName,
|
|
1515
|
+
serviceInstanceId,
|
|
1357
1516
|
{
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1517
|
+
detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
|
|
1518
|
+
overallTimeoutMs: ctx.overallTimeoutMs,
|
|
1519
|
+
perResponderTimeoutMs: ctx.perResponderTimeoutMs,
|
|
1520
|
+
requireComplete: ctx.requireComplete
|
|
1361
1521
|
}
|
|
1362
1522
|
);
|
|
1363
|
-
const report = this.selectRuntimeStatusReportForTarget(
|
|
1364
|
-
inquiryResult,
|
|
1365
|
-
serviceName,
|
|
1366
|
-
serviceInstanceId
|
|
1367
|
-
);
|
|
1368
|
-
if (!report) {
|
|
1369
|
-
throw new Error(
|
|
1370
|
-
`No runtime status report for ${serviceName}/${serviceInstanceId}`
|
|
1371
|
-
);
|
|
1372
|
-
}
|
|
1373
|
-
this.applyRuntimeStatusReport(report);
|
|
1374
|
-
this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
|
|
1375
|
-
this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
|
|
1376
1523
|
return {
|
|
1377
1524
|
...ctx,
|
|
1378
1525
|
runtimeStatusReport: report,
|
|
1379
|
-
__inquiryMeta:
|
|
1526
|
+
__inquiryMeta: inquiryMeta
|
|
1380
1527
|
};
|
|
1381
1528
|
} catch (error) {
|
|
1382
1529
|
const instance = this.getInstance(serviceName, serviceInstanceId);
|
|
@@ -1410,6 +1557,27 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1410
1557
|
},
|
|
1411
1558
|
"Runs runtime-status inquiry fallback for a dependee instance after missed heartbeats."
|
|
1412
1559
|
).doOn("meta.service_registry.runtime_status_fallback_requested").emits("meta.service_registry.runtime_status_fallback_resolved").emitsOnFail("meta.service_registry.runtime_status_fallback_failed");
|
|
1560
|
+
this.collectReadinessTask = CadenzaService.createMetaTask(
|
|
1561
|
+
"Collect distributed readiness",
|
|
1562
|
+
async (ctx) => {
|
|
1563
|
+
const inquiryResult = await CadenzaService.inquire(
|
|
1564
|
+
META_READINESS_INTENT,
|
|
1565
|
+
{
|
|
1566
|
+
detailLevel: ctx.detailLevel === "full" ? "full" : "minimal",
|
|
1567
|
+
includeDependencies: ctx.includeDependencies,
|
|
1568
|
+
refreshStaleDependencies: ctx.refreshStaleDependencies,
|
|
1569
|
+
targetServiceName: ctx.targetServiceName,
|
|
1570
|
+
targetServiceInstanceId: ctx.targetServiceInstanceId
|
|
1571
|
+
},
|
|
1572
|
+
ctx.inquiryOptions ?? ctx.__inquiryOptions ?? {}
|
|
1573
|
+
);
|
|
1574
|
+
return {
|
|
1575
|
+
...ctx,
|
|
1576
|
+
...inquiryResult
|
|
1577
|
+
};
|
|
1578
|
+
},
|
|
1579
|
+
"Collects distributed readiness reports from services."
|
|
1580
|
+
).doOn("meta.service_registry.readiness_requested").emits("meta.service_registry.readiness_collected").emitsOnFail("meta.service_registry.readiness_failed");
|
|
1413
1581
|
this.collectTransportDiagnosticsTask = CadenzaService.createMetaTask(
|
|
1414
1582
|
"Collect transport diagnostics",
|
|
1415
1583
|
async (ctx) => {
|
|
@@ -1758,7 +1926,7 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1758
1926
|
}
|
|
1759
1927
|
return this.getInstance(this.serviceName, this.serviceInstanceId);
|
|
1760
1928
|
}
|
|
1761
|
-
registerDependee(serviceName, serviceInstanceId) {
|
|
1929
|
+
registerDependee(serviceName, serviceInstanceId, options = {}) {
|
|
1762
1930
|
if (!serviceName || !serviceInstanceId) {
|
|
1763
1931
|
return;
|
|
1764
1932
|
}
|
|
@@ -1767,6 +1935,13 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1767
1935
|
}
|
|
1768
1936
|
this.dependeesByService.get(serviceName).add(serviceInstanceId);
|
|
1769
1937
|
this.dependeeByInstance.set(serviceInstanceId, serviceName);
|
|
1938
|
+
if (options.requiredForReadiness) {
|
|
1939
|
+
if (!this.readinessDependeesByService.has(serviceName)) {
|
|
1940
|
+
this.readinessDependeesByService.set(serviceName, /* @__PURE__ */ new Set());
|
|
1941
|
+
}
|
|
1942
|
+
this.readinessDependeesByService.get(serviceName).add(serviceInstanceId);
|
|
1943
|
+
this.readinessDependeeByInstance.set(serviceInstanceId, serviceName);
|
|
1944
|
+
}
|
|
1770
1945
|
this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
|
|
1771
1946
|
this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
|
|
1772
1947
|
}
|
|
@@ -1779,10 +1954,39 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1779
1954
|
}
|
|
1780
1955
|
}
|
|
1781
1956
|
this.dependeeByInstance.delete(serviceInstanceId);
|
|
1957
|
+
const readinessDependeeServiceName = serviceName ?? this.readinessDependeeByInstance.get(serviceInstanceId);
|
|
1958
|
+
if (readinessDependeeServiceName) {
|
|
1959
|
+
this.readinessDependeesByService.get(readinessDependeeServiceName)?.delete(serviceInstanceId);
|
|
1960
|
+
if (!this.readinessDependeesByService.get(readinessDependeeServiceName)?.size) {
|
|
1961
|
+
this.readinessDependeesByService.delete(readinessDependeeServiceName);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
this.readinessDependeeByInstance.delete(serviceInstanceId);
|
|
1782
1965
|
this.lastHeartbeatAtByInstance.delete(serviceInstanceId);
|
|
1783
1966
|
this.missedHeartbeatsByInstance.delete(serviceInstanceId);
|
|
1784
1967
|
this.runtimeStatusFallbackInFlightByInstance.delete(serviceInstanceId);
|
|
1785
1968
|
}
|
|
1969
|
+
getHeartbeatMisses(serviceInstanceId, now = Date.now()) {
|
|
1970
|
+
const observedMisses = this.missedHeartbeatsByInstance.get(serviceInstanceId) ?? 0;
|
|
1971
|
+
const lastHeartbeatAt = this.lastHeartbeatAtByInstance.get(serviceInstanceId) ?? 0;
|
|
1972
|
+
if (lastHeartbeatAt <= 0) {
|
|
1973
|
+
return Math.max(observedMisses, this.runtimeStatusMissThreshold);
|
|
1974
|
+
}
|
|
1975
|
+
const estimatedMisses = Math.max(
|
|
1976
|
+
0,
|
|
1977
|
+
Math.floor((now - lastHeartbeatAt) / this.runtimeStatusHeartbeatIntervalMs)
|
|
1978
|
+
);
|
|
1979
|
+
return Math.max(observedMisses, estimatedMisses);
|
|
1980
|
+
}
|
|
1981
|
+
shouldRequireReadinessFromCommunicationTypes(communicationTypes) {
|
|
1982
|
+
if (!Array.isArray(communicationTypes)) {
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
return communicationTypes.some((type) => {
|
|
1986
|
+
const normalized = String(type).toLowerCase();
|
|
1987
|
+
return normalized === "delegation" || normalized === "inquiry";
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1786
1990
|
resolveRuntimeStatusSnapshot(numberOfRunningGraphs, isActive, isNonResponsive, isBlocked) {
|
|
1787
1991
|
return resolveRuntimeStatus({
|
|
1788
1992
|
numberOfRunningGraphs,
|
|
@@ -1926,6 +2130,166 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1926
2130
|
}
|
|
1927
2131
|
return null;
|
|
1928
2132
|
}
|
|
2133
|
+
async resolveRuntimeStatusFallbackInquiry(serviceName, serviceInstanceId, options = {}) {
|
|
2134
|
+
const inquiryResult = await CadenzaService.inquire(
|
|
2135
|
+
META_RUNTIME_STATUS_INTENT,
|
|
2136
|
+
{
|
|
2137
|
+
targetServiceName: serviceName,
|
|
2138
|
+
targetServiceInstanceId: serviceInstanceId,
|
|
2139
|
+
detailLevel: options.detailLevel ?? "minimal"
|
|
2140
|
+
},
|
|
2141
|
+
{
|
|
2142
|
+
overallTimeoutMs: options.overallTimeoutMs ?? this.runtimeStatusFallbackTimeoutMs,
|
|
2143
|
+
perResponderTimeoutMs: options.perResponderTimeoutMs ?? Math.max(250, Math.floor(this.runtimeStatusFallbackTimeoutMs * 0.75)),
|
|
2144
|
+
requireComplete: options.requireComplete ?? false
|
|
2145
|
+
}
|
|
2146
|
+
);
|
|
2147
|
+
const report = this.selectRuntimeStatusReportForTarget(
|
|
2148
|
+
inquiryResult,
|
|
2149
|
+
serviceName,
|
|
2150
|
+
serviceInstanceId
|
|
2151
|
+
);
|
|
2152
|
+
if (!report) {
|
|
2153
|
+
throw new Error(
|
|
2154
|
+
`No runtime status report for ${serviceName}/${serviceInstanceId}`
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
if (!this.applyRuntimeStatusReport(report)) {
|
|
2158
|
+
throw new Error(
|
|
2159
|
+
`No tracked instance for runtime fallback ${serviceName}/${serviceInstanceId}`
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
this.lastHeartbeatAtByInstance.set(serviceInstanceId, Date.now());
|
|
2163
|
+
this.missedHeartbeatsByInstance.set(serviceInstanceId, 0);
|
|
2164
|
+
return {
|
|
2165
|
+
report,
|
|
2166
|
+
inquiryMeta: inquiryResult.__inquiryMeta ?? {}
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
evaluateDependencyReadinessDetail(serviceName, serviceInstanceId, now = Date.now()) {
|
|
2170
|
+
const instance = this.getInstance(serviceName, serviceInstanceId);
|
|
2171
|
+
const missedHeartbeats = this.getHeartbeatMisses(serviceInstanceId, now);
|
|
2172
|
+
const runtimeState = instance ? instance.runtimeState ?? this.resolveRuntimeStatusSnapshot(
|
|
2173
|
+
instance.numberOfRunningGraphs ?? 0,
|
|
2174
|
+
instance.isActive,
|
|
2175
|
+
instance.isNonResponsive,
|
|
2176
|
+
instance.isBlocked
|
|
2177
|
+
).state : "unavailable";
|
|
2178
|
+
const acceptingWork = instance ? typeof instance.acceptingWork === "boolean" ? instance.acceptingWork : this.resolveRuntimeStatusSnapshot(
|
|
2179
|
+
instance.numberOfRunningGraphs ?? 0,
|
|
2180
|
+
instance.isActive,
|
|
2181
|
+
instance.isNonResponsive,
|
|
2182
|
+
instance.isBlocked
|
|
2183
|
+
).acceptingWork : false;
|
|
2184
|
+
const evaluation = evaluateDependencyReadiness({
|
|
2185
|
+
exists: Boolean(instance),
|
|
2186
|
+
runtimeState,
|
|
2187
|
+
acceptingWork,
|
|
2188
|
+
missedHeartbeats,
|
|
2189
|
+
missThreshold: this.runtimeStatusMissThreshold
|
|
2190
|
+
});
|
|
2191
|
+
const lastHeartbeat = this.lastHeartbeatAtByInstance.get(serviceInstanceId);
|
|
2192
|
+
return {
|
|
2193
|
+
serviceName,
|
|
2194
|
+
serviceInstanceId,
|
|
2195
|
+
dependencyState: evaluation.state,
|
|
2196
|
+
runtimeState,
|
|
2197
|
+
acceptingWork,
|
|
2198
|
+
missedHeartbeats,
|
|
2199
|
+
stale: evaluation.stale,
|
|
2200
|
+
blocked: evaluation.blocked,
|
|
2201
|
+
reason: evaluation.reason,
|
|
2202
|
+
lastHeartbeatAt: lastHeartbeat ? new Date(lastHeartbeat).toISOString() : null,
|
|
2203
|
+
reportedAt: instance?.reportedAt ?? null
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
async buildLocalReadinessReport(options = {}) {
|
|
2207
|
+
const localRuntime = this.buildLocalRuntimeStatusReport("minimal");
|
|
2208
|
+
if (!localRuntime) {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
const detailLevel = options.detailLevel ?? "minimal";
|
|
2212
|
+
const includeDependencies = options.includeDependencies ?? detailLevel === "full";
|
|
2213
|
+
const refreshStaleDependencies = options.refreshStaleDependencies ?? true;
|
|
2214
|
+
const dependencyPairs = Array.from(this.readinessDependeesByService.entries()).flatMap(
|
|
2215
|
+
([serviceName, instanceIds]) => Array.from(instanceIds).map((serviceInstanceId) => ({
|
|
2216
|
+
serviceName,
|
|
2217
|
+
serviceInstanceId
|
|
2218
|
+
}))
|
|
2219
|
+
).sort((left, right) => {
|
|
2220
|
+
if (left.serviceName !== right.serviceName) {
|
|
2221
|
+
return left.serviceName.localeCompare(right.serviceName);
|
|
2222
|
+
}
|
|
2223
|
+
return left.serviceInstanceId.localeCompare(right.serviceInstanceId);
|
|
2224
|
+
});
|
|
2225
|
+
if (refreshStaleDependencies) {
|
|
2226
|
+
for (const dependency of dependencyPairs) {
|
|
2227
|
+
const misses = this.getHeartbeatMisses(dependency.serviceInstanceId);
|
|
2228
|
+
if (misses < this.runtimeStatusMissThreshold) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
if (this.runtimeStatusFallbackInFlightByInstance.has(
|
|
2232
|
+
dependency.serviceInstanceId
|
|
2233
|
+
)) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
this.runtimeStatusFallbackInFlightByInstance.add(
|
|
2237
|
+
dependency.serviceInstanceId
|
|
2238
|
+
);
|
|
2239
|
+
try {
|
|
2240
|
+
await this.resolveRuntimeStatusFallbackInquiry(
|
|
2241
|
+
dependency.serviceName,
|
|
2242
|
+
dependency.serviceInstanceId
|
|
2243
|
+
);
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
CadenzaService.log(
|
|
2246
|
+
"Readiness dependency fallback failed.",
|
|
2247
|
+
{
|
|
2248
|
+
serviceName: dependency.serviceName,
|
|
2249
|
+
serviceInstanceId: dependency.serviceInstanceId,
|
|
2250
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2251
|
+
},
|
|
2252
|
+
"warning"
|
|
2253
|
+
);
|
|
2254
|
+
} finally {
|
|
2255
|
+
this.runtimeStatusFallbackInFlightByInstance.delete(
|
|
2256
|
+
dependency.serviceInstanceId
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
const now = Date.now();
|
|
2262
|
+
const dependencyDetails = dependencyPairs.map(
|
|
2263
|
+
(dependency) => this.evaluateDependencyReadinessDetail(
|
|
2264
|
+
dependency.serviceName,
|
|
2265
|
+
dependency.serviceInstanceId,
|
|
2266
|
+
now
|
|
2267
|
+
)
|
|
2268
|
+
);
|
|
2269
|
+
const dependencySummary = summarizeDependencyReadiness(
|
|
2270
|
+
dependencyDetails.map((detail) => ({
|
|
2271
|
+
state: detail.dependencyState,
|
|
2272
|
+
stale: detail.stale,
|
|
2273
|
+
blocked: detail.blocked,
|
|
2274
|
+
reason: detail.reason
|
|
2275
|
+
}))
|
|
2276
|
+
);
|
|
2277
|
+
const readinessState = resolveServiceReadinessState(
|
|
2278
|
+
localRuntime.state,
|
|
2279
|
+
localRuntime.acceptingWork,
|
|
2280
|
+
dependencySummary
|
|
2281
|
+
);
|
|
2282
|
+
return {
|
|
2283
|
+
serviceName: localRuntime.serviceName,
|
|
2284
|
+
serviceInstanceId: localRuntime.serviceInstanceId,
|
|
2285
|
+
reportedAt: new Date(now).toISOString(),
|
|
2286
|
+
readinessState,
|
|
2287
|
+
runtimeState: localRuntime.state,
|
|
2288
|
+
acceptingWork: localRuntime.acceptingWork,
|
|
2289
|
+
dependencySummary,
|
|
2290
|
+
...includeDependencies ? { dependencies: dependencyDetails } : {}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
1929
2293
|
reset() {
|
|
1930
2294
|
this.instances.clear();
|
|
1931
2295
|
this.deputies.clear();
|
|
@@ -1935,6 +2299,8 @@ var ServiceRegistry = class _ServiceRegistry {
|
|
|
1935
2299
|
this.remoteIntentDeputiesByTask.clear();
|
|
1936
2300
|
this.dependeesByService.clear();
|
|
1937
2301
|
this.dependeeByInstance.clear();
|
|
2302
|
+
this.readinessDependeesByService.clear();
|
|
2303
|
+
this.readinessDependeeByInstance.clear();
|
|
1938
2304
|
this.lastHeartbeatAtByInstance.clear();
|
|
1939
2305
|
this.missedHeartbeatsByInstance.clear();
|
|
1940
2306
|
this.runtimeStatusFallbackInFlightByInstance.clear();
|