@camstack/system 1.2.31 → 1.2.32
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.d.ts +10 -0
- package/dist/builtins/alerts/alerts.addon.js +60 -1
- package/dist/builtins/alerts/alerts.addon.mjs +60 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/index.d.ts +2 -0
- package/dist/builtins/liveness-monitor/index.js +6 -0
- package/dist/builtins/liveness-monitor/index.mjs +2 -0
- package/dist/builtins/liveness-monitor/liveness-checks.d.ts +67 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.d.ts +32 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +216 -0
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +212 -0
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-CNmXITJ-.mjs → dist-BL_FHWPP.mjs} +21 -1
- package/dist/{dist-CDv5YTHQ.js → dist-CSs1A90r.js} +21 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/index.mjs +4 -2
- package/package.json +10 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mt as EventCategory, at as errMsg, ot as BaseAddon, p as addonPagesCapability } from "../../dist-
|
|
1
|
+
import { Mt as EventCategory, at as errMsg, ot as BaseAddon, p as addonPagesCapability } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mt as EventCategory, at as errMsg, h as addonWidgetsCapability, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { Mt as EventCategory, at as errMsg, h as addonWidgetsCapability, ot as BaseAddon } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -57,6 +57,16 @@ export declare class AlertCenterAddon extends BaseAddon<AlertCenterConfig> {
|
|
|
57
57
|
* Dismiss the matching alert so the operator's badge count drops.
|
|
58
58
|
*/
|
|
59
59
|
private handleAddonLoadRecovered;
|
|
60
|
+
/**
|
|
61
|
+
* SystemLivenessFailed — a liveness invariant of this node is false.
|
|
62
|
+
*
|
|
63
|
+
* `findingId` is already stable across ticks (`liveness:no-devices`,
|
|
64
|
+
* `liveness:no-footage:<deviceId>`, …), so it IS the alert id: an ongoing
|
|
65
|
+
* fault updates one alert instead of producing one every five minutes.
|
|
66
|
+
*/
|
|
67
|
+
private handleLivenessFailed;
|
|
68
|
+
/** SystemLivenessRecovered — the invariant holds again; drop the alert. */
|
|
69
|
+
private handleLivenessRecovered;
|
|
60
70
|
private buildMessage;
|
|
61
71
|
private emitAlert;
|
|
62
72
|
private updateAlert;
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
//#region src/builtins/alerts/prune-policy.ts
|
|
8
8
|
/**
|
|
9
9
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -93,6 +93,10 @@ var CATEGORY_MAPPINGS = {
|
|
|
93
93
|
severity: "error",
|
|
94
94
|
titleTemplate: "Addon failed to load"
|
|
95
95
|
},
|
|
96
|
+
"system.liveness-failed": {
|
|
97
|
+
severity: "error",
|
|
98
|
+
titleTemplate: "Node liveness"
|
|
99
|
+
},
|
|
96
100
|
"addon.load-recovered": {
|
|
97
101
|
severity: "info",
|
|
98
102
|
titleTemplate: "Addon recovered"
|
|
@@ -289,6 +293,14 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
|
|
|
289
293
|
await this.handleAddonLoadRecovered(event);
|
|
290
294
|
return;
|
|
291
295
|
}
|
|
296
|
+
if (category === require_dist.EventCategory.SystemLivenessFailed) {
|
|
297
|
+
await this.handleLivenessFailed(event);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (category === require_dist.EventCategory.SystemLivenessRecovered) {
|
|
301
|
+
await this.handleLivenessRecovered(event);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
292
304
|
const mapping = CATEGORY_MAPPINGS[category];
|
|
293
305
|
if (!mapping) return;
|
|
294
306
|
if (this.shouldSuppressByRate(category, event)) return;
|
|
@@ -480,6 +492,53 @@ var AlertCenterAddon = class extends require_dist.BaseAddon {
|
|
|
480
492
|
} });
|
|
481
493
|
}
|
|
482
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* SystemLivenessFailed — a liveness invariant of this node is false.
|
|
497
|
+
*
|
|
498
|
+
* `findingId` is already stable across ticks (`liveness:no-devices`,
|
|
499
|
+
* `liveness:no-footage:<deviceId>`, …), so it IS the alert id: an ongoing
|
|
500
|
+
* fault updates one alert instead of producing one every five minutes.
|
|
501
|
+
*/
|
|
502
|
+
async handleLivenessFailed(event) {
|
|
503
|
+
const data = event.data;
|
|
504
|
+
const findingId = String(data["findingId"] ?? "");
|
|
505
|
+
if (!findingId) return;
|
|
506
|
+
const deviceId = typeof data["deviceId"] === "number" ? data["deviceId"] : void 0;
|
|
507
|
+
const severity = data["severity"] === "warning" ? "warning" : "error";
|
|
508
|
+
const alert = {
|
|
509
|
+
id: findingId,
|
|
510
|
+
category: require_dist.EventCategory.SystemLivenessFailed,
|
|
511
|
+
severity,
|
|
512
|
+
title: String(data["title"] ?? "Node liveness"),
|
|
513
|
+
message: String(data["message"] ?? ""),
|
|
514
|
+
status: "active",
|
|
515
|
+
read: false,
|
|
516
|
+
createdAt: Date.now(),
|
|
517
|
+
updatedAt: Date.now(),
|
|
518
|
+
source: {
|
|
519
|
+
type: "liveness",
|
|
520
|
+
id: findingId
|
|
521
|
+
},
|
|
522
|
+
metadata: {
|
|
523
|
+
findingId,
|
|
524
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
await this.emitAlert(alert);
|
|
528
|
+
}
|
|
529
|
+
/** SystemLivenessRecovered — the invariant holds again; drop the alert. */
|
|
530
|
+
async handleLivenessRecovered(event) {
|
|
531
|
+
const findingId = String(event.data["findingId"] ?? "");
|
|
532
|
+
if (!findingId) return;
|
|
533
|
+
try {
|
|
534
|
+
await this.dismiss(findingId);
|
|
535
|
+
} catch (err) {
|
|
536
|
+
this.ctx.logger?.debug("LivenessRecovered: dismiss failed (likely already dismissed)", { meta: {
|
|
537
|
+
findingId,
|
|
538
|
+
error: require_dist.errMsg(err)
|
|
539
|
+
} });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
483
542
|
buildMessage(category, data, sourceId) {
|
|
484
543
|
const addonId = data.addonId ?? sourceId;
|
|
485
544
|
const deviceId = data.deviceId;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mt as EventCategory, at as errMsg, g as alertsCapability, n as AlertSchema, ot as BaseAddon, yt as createEvent } from "../../dist-
|
|
1
|
+
import { Mt as EventCategory, at as errMsg, g as alertsCapability, n as AlertSchema, ot as BaseAddon, yt as createEvent } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
//#region src/builtins/alerts/prune-policy.ts
|
|
3
3
|
/**
|
|
4
4
|
* Ids older than `cutoffMs`, oldest first.
|
|
@@ -88,6 +88,10 @@ var CATEGORY_MAPPINGS = {
|
|
|
88
88
|
severity: "error",
|
|
89
89
|
titleTemplate: "Addon failed to load"
|
|
90
90
|
},
|
|
91
|
+
"system.liveness-failed": {
|
|
92
|
+
severity: "error",
|
|
93
|
+
titleTemplate: "Node liveness"
|
|
94
|
+
},
|
|
91
95
|
"addon.load-recovered": {
|
|
92
96
|
severity: "info",
|
|
93
97
|
titleTemplate: "Addon recovered"
|
|
@@ -284,6 +288,14 @@ var AlertCenterAddon = class extends BaseAddon {
|
|
|
284
288
|
await this.handleAddonLoadRecovered(event);
|
|
285
289
|
return;
|
|
286
290
|
}
|
|
291
|
+
if (category === EventCategory.SystemLivenessFailed) {
|
|
292
|
+
await this.handleLivenessFailed(event);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (category === EventCategory.SystemLivenessRecovered) {
|
|
296
|
+
await this.handleLivenessRecovered(event);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
287
299
|
const mapping = CATEGORY_MAPPINGS[category];
|
|
288
300
|
if (!mapping) return;
|
|
289
301
|
if (this.shouldSuppressByRate(category, event)) return;
|
|
@@ -475,6 +487,53 @@ var AlertCenterAddon = class extends BaseAddon {
|
|
|
475
487
|
} });
|
|
476
488
|
}
|
|
477
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* SystemLivenessFailed — a liveness invariant of this node is false.
|
|
492
|
+
*
|
|
493
|
+
* `findingId` is already stable across ticks (`liveness:no-devices`,
|
|
494
|
+
* `liveness:no-footage:<deviceId>`, …), so it IS the alert id: an ongoing
|
|
495
|
+
* fault updates one alert instead of producing one every five minutes.
|
|
496
|
+
*/
|
|
497
|
+
async handleLivenessFailed(event) {
|
|
498
|
+
const data = event.data;
|
|
499
|
+
const findingId = String(data["findingId"] ?? "");
|
|
500
|
+
if (!findingId) return;
|
|
501
|
+
const deviceId = typeof data["deviceId"] === "number" ? data["deviceId"] : void 0;
|
|
502
|
+
const severity = data["severity"] === "warning" ? "warning" : "error";
|
|
503
|
+
const alert = {
|
|
504
|
+
id: findingId,
|
|
505
|
+
category: EventCategory.SystemLivenessFailed,
|
|
506
|
+
severity,
|
|
507
|
+
title: String(data["title"] ?? "Node liveness"),
|
|
508
|
+
message: String(data["message"] ?? ""),
|
|
509
|
+
status: "active",
|
|
510
|
+
read: false,
|
|
511
|
+
createdAt: Date.now(),
|
|
512
|
+
updatedAt: Date.now(),
|
|
513
|
+
source: {
|
|
514
|
+
type: "liveness",
|
|
515
|
+
id: findingId
|
|
516
|
+
},
|
|
517
|
+
metadata: {
|
|
518
|
+
findingId,
|
|
519
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
await this.emitAlert(alert);
|
|
523
|
+
}
|
|
524
|
+
/** SystemLivenessRecovered — the invariant holds again; drop the alert. */
|
|
525
|
+
async handleLivenessRecovered(event) {
|
|
526
|
+
const findingId = String(event.data["findingId"] ?? "");
|
|
527
|
+
if (!findingId) return;
|
|
528
|
+
try {
|
|
529
|
+
await this.dismiss(findingId);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
this.ctx.logger?.debug("LivenessRecovered: dismiss failed (likely already dismissed)", { meta: {
|
|
532
|
+
findingId,
|
|
533
|
+
error: errMsg(err)
|
|
534
|
+
} });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
478
537
|
buildMessage(category, data, sourceId) {
|
|
479
538
|
const addonId = data.addonId ?? sourceId;
|
|
480
539
|
const deviceId = data.deviceId;
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mt as EventCategory, ot as BaseAddon, y as backupCapability } from "../../dist-
|
|
1
|
+
import { Mt as EventCategory, ot as BaseAddon, y as backupCapability } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import Kt from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
let _camstack_types_node = require("@camstack/types/node");
|
|
8
8
|
let zod = require("zod");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as deviceStateCapability, D as enumerateItemArrayFields, K as parseStreamParamsFormPatch, M as getByPath, Mt as EventCategory, O as enumerateSchemaFields, S as deviceManagerCapability, W as normalizeUnit, X as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as errMsg, b as buildStreamParamsConfigSchema, dt as DeviceRole, ft as DeviceType, ht as WELL_KNOWN_TAB_MAP, it as validateExpressionSource, jt as sleep, k as evaluateLinkExpression, l as STREAM_PROFILE_META, nt as toExpressionValue, o as DeviceStatusSchema, ot as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, ut as DeviceFeature, w as deviceStatusCapability, wt as isDeviceConfigCap } from "../../dist-
|
|
1
|
+
import { C as deviceStateCapability, D as enumerateItemArrayFields, K as parseStreamParamsFormPatch, M as getByPath, Mt as EventCategory, O as enumerateSchemaFields, S as deviceManagerCapability, W as normalizeUnit, X as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as errMsg, b as buildStreamParamsConfigSchema, dt as DeviceRole, ft as DeviceType, ht as WELL_KNOWN_TAB_MAP, it as validateExpressionSource, jt as sleep, k as evaluateLinkExpression, l as STREAM_PROFILE_META, nt as toExpressionValue, o as DeviceStatusSchema, ot as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, ut as DeviceFeature, w as deviceStatusCapability, wt as isDeviceConfigCap } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Mt as EventCategory, T as doorbellCapability, at as errMsg, ft as DeviceType, ot as BaseAddon, yt as createEvent } from "../../dist-
|
|
1
|
+
import { Mt as EventCategory, T as doorbellCapability, at as errMsg, ft as DeviceType, ot as BaseAddon, yt as createEvent } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-BL_FHWPP.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../../chunk-Cek0wNdY.js");
|
|
3
|
+
const require_builtins_liveness_monitor_liveness_monitor_addon = require("./liveness-monitor.addon.js");
|
|
4
|
+
exports.LivenessMonitorAddon = require_builtins_liveness_monitor_liveness_monitor_addon.LivenessMonitorAddon;
|
|
5
|
+
exports.evaluateLiveness = require_builtins_liveness_monitor_liveness_monitor_addon.evaluateLiveness;
|
|
6
|
+
exports.isLivenessFindingId = require_builtins_liveness_monitor_liveness_monitor_addon.isLivenessFindingId;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The liveness invariants, as pure functions.
|
|
3
|
+
*
|
|
4
|
+
* On 2026-08-03 the hub ran for four hours with zero devices, zero published
|
|
5
|
+
* streams and zero recordings, and nothing said a word. Three separate defects
|
|
6
|
+
* caused it — optional chaining that no-oped, an `if` with no `else`, a JSON
|
|
7
|
+
* parse that returned `{}` — and each was silent at the point of failure. All
|
|
8
|
+
* three were fixed and a log line added at each. This module exists for the
|
|
9
|
+
* CLASS rather than the instances: assert what must be true of a working node,
|
|
10
|
+
* and report when it is not.
|
|
11
|
+
*
|
|
12
|
+
* **Every check here is unambiguous, on purpose.** An alert that fires on a
|
|
13
|
+
* quiet night is one an operator learns to ignore, and an ignored alert is
|
|
14
|
+
* worse than no alert. So "the pipeline emitted no tracks recently" is NOT a
|
|
15
|
+
* check — a camera can legitimately see nothing for hours — while "a camera
|
|
16
|
+
* that has been recording all day stopped producing segments" is: continuous
|
|
17
|
+
* recording produces segments whether or not anything moves.
|
|
18
|
+
*
|
|
19
|
+
* I/O lives in the addon; everything here is a function of facts, so the
|
|
20
|
+
* outage can be replayed in a test.
|
|
21
|
+
*/
|
|
22
|
+
/** What the monitor learned about one camera this tick. */
|
|
23
|
+
export interface CameraFootage {
|
|
24
|
+
readonly deviceId: number;
|
|
25
|
+
readonly online: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* ms-epoch of the newest recorded segment within the lookback window, or
|
|
28
|
+
* `null` when the camera has produced NO footage in it.
|
|
29
|
+
*
|
|
30
|
+
* `null` means "this camera does not record" and is deliberately not a
|
|
31
|
+
* finding — it is what makes the check self-calibrating. Nothing has to be
|
|
32
|
+
* told which cameras are supposed to record: a camera that was recording and
|
|
33
|
+
* stopped is a fault, a camera that never records is a configuration.
|
|
34
|
+
*/
|
|
35
|
+
readonly lastSegmentMs: number | null;
|
|
36
|
+
}
|
|
37
|
+
export interface LivenessFacts {
|
|
38
|
+
readonly nowMs: number;
|
|
39
|
+
/** Every device the node knows about, cameras and accessories alike. */
|
|
40
|
+
readonly deviceCount: number;
|
|
41
|
+
readonly cameras: readonly CameraFootage[];
|
|
42
|
+
/** How long a recording camera may stay silent before it is a fault. */
|
|
43
|
+
readonly staleAfterMs: number;
|
|
44
|
+
}
|
|
45
|
+
export interface LivenessFinding {
|
|
46
|
+
/**
|
|
47
|
+
* Stable across ticks, so an ongoing fault UPDATES one alert instead of
|
|
48
|
+
* producing a new one every minute. Same contract as the kernel's
|
|
49
|
+
* `addon-load-failed:<packageName>` ids.
|
|
50
|
+
*/
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly severity: 'error' | 'warning';
|
|
53
|
+
readonly title: string;
|
|
54
|
+
readonly message: string;
|
|
55
|
+
/** Present on per-camera findings. Always the numeric id. */
|
|
56
|
+
readonly deviceId?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Evaluate every invariant against one tick's facts.
|
|
60
|
+
*
|
|
61
|
+
* Ordered most-fundamental first, and it STOPS at "no devices": when the node
|
|
62
|
+
* has nothing, every camera is also silent, and reporting twelve consequences
|
|
63
|
+
* of one cause is how an operator ends up reading none of them.
|
|
64
|
+
*/
|
|
65
|
+
export declare function evaluateLiveness(facts: LivenessFacts): readonly LivenessFinding[];
|
|
66
|
+
/** Every finding id this module can produce, for the recovery sweep. */
|
|
67
|
+
export declare function isLivenessFindingId(id: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseAddon, ProviderRegistration } from '@camstack/types';
|
|
2
|
+
import { LivenessFinding } from './liveness-checks.js';
|
|
3
|
+
interface LivenessMonitorConfig extends Record<string, unknown> {
|
|
4
|
+
readonly staleAfterMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class LivenessMonitorAddon extends BaseAddon<LivenessMonitorConfig> {
|
|
7
|
+
private tickTimer;
|
|
8
|
+
private startedAtMs;
|
|
9
|
+
/** Finding ids currently reported, so recovery can be detected and announced. */
|
|
10
|
+
private active;
|
|
11
|
+
constructor();
|
|
12
|
+
protected onInitialize(): Promise<ProviderRegistration[]>;
|
|
13
|
+
protected onShutdown(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* One evaluation. Public so a test or an operator-facing action can force it
|
|
16
|
+
* without waiting out the interval.
|
|
17
|
+
*/
|
|
18
|
+
tick(): Promise<readonly LivenessFinding[]>;
|
|
19
|
+
/** Read the facts through the cap surface. Changes nothing. */
|
|
20
|
+
private gather;
|
|
21
|
+
/**
|
|
22
|
+
* Newest segment for a camera, or null when it has produced none in a day.
|
|
23
|
+
*
|
|
24
|
+
* Two steps on purpose: the healthy case asks only for the recent window,
|
|
25
|
+
* which is a handful of ranges, and only a camera that looks silent pays for
|
|
26
|
+
* the 24-hour lookback that decides whether it records at all.
|
|
27
|
+
*/
|
|
28
|
+
private lastSegmentMs;
|
|
29
|
+
/** Announce new faults and recoveries; say nothing about an unchanged fault. */
|
|
30
|
+
private report;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../../chunk-Cek0wNdY.js");
|
|
3
|
+
const require_dist = require("../../dist-CSs1A90r.js");
|
|
4
|
+
//#region src/builtins/liveness-monitor/liveness-checks.ts
|
|
5
|
+
var NO_DEVICES = "liveness:no-devices";
|
|
6
|
+
var ALL_OFFLINE = "liveness:all-devices-offline";
|
|
7
|
+
var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
|
|
8
|
+
function minutes(ms) {
|
|
9
|
+
return Math.round(ms / 6e4);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate every invariant against one tick's facts.
|
|
13
|
+
*
|
|
14
|
+
* Ordered most-fundamental first, and it STOPS at "no devices": when the node
|
|
15
|
+
* has nothing, every camera is also silent, and reporting twelve consequences
|
|
16
|
+
* of one cause is how an operator ends up reading none of them.
|
|
17
|
+
*/
|
|
18
|
+
function evaluateLiveness(facts) {
|
|
19
|
+
if (facts.deviceCount === 0) return [{
|
|
20
|
+
id: NO_DEVICES,
|
|
21
|
+
severity: "error",
|
|
22
|
+
title: "No devices",
|
|
23
|
+
message: "This node has no devices. Nothing can publish a stream, record, or detect. The persisted device ledger is either unreachable or empty — check the boot log for \"SettingsStore not initialized\" and for \"[worker-restore] no persisted devices\"."
|
|
24
|
+
}];
|
|
25
|
+
const findings = [];
|
|
26
|
+
if (facts.cameras.length > 0 && facts.cameras.every((c) => !c.online)) findings.push({
|
|
27
|
+
id: ALL_OFFLINE,
|
|
28
|
+
severity: "error",
|
|
29
|
+
title: "Every camera offline",
|
|
30
|
+
message: `All ${facts.cameras.length} cameras report offline. This is a provider or network fault, not a per-camera one.`
|
|
31
|
+
});
|
|
32
|
+
for (const cam of facts.cameras) {
|
|
33
|
+
if (cam.lastSegmentMs === null) continue;
|
|
34
|
+
if (!cam.online) continue;
|
|
35
|
+
const silentFor = facts.nowMs - cam.lastSegmentMs;
|
|
36
|
+
if (silentFor <= facts.staleAfterMs) continue;
|
|
37
|
+
findings.push({
|
|
38
|
+
id: `${NO_FOOTAGE_PREFIX}${cam.deviceId}`,
|
|
39
|
+
severity: "error",
|
|
40
|
+
title: "Recording stopped",
|
|
41
|
+
message: `Camera ${cam.deviceId} is online and was recording, but has produced no segment for ${minutes(silentFor)} minutes.`,
|
|
42
|
+
deviceId: cam.deviceId
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return findings;
|
|
46
|
+
}
|
|
47
|
+
/** Every finding id this module can produce, for the recovery sweep. */
|
|
48
|
+
function isLivenessFindingId(id) {
|
|
49
|
+
return id === NO_DEVICES || id === ALL_OFFLINE || id.startsWith(NO_FOOTAGE_PREFIX);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/builtins/liveness-monitor/liveness-monitor.addon.ts
|
|
53
|
+
/**
|
|
54
|
+
* liveness-monitor — the node states, out loud, when it has stopped working.
|
|
55
|
+
*
|
|
56
|
+
* On 2026-08-03 the hub ran for four hours with zero devices, zero published
|
|
57
|
+
* streams and zero recordings. Three separate defects caused it, each silent at
|
|
58
|
+
* the point of failure, and the operator found out by opening the viewer. Every
|
|
59
|
+
* one of those defects has since been fixed and given a log line — but a log
|
|
60
|
+
* line per known bug does not cover the next one. This addon covers the class:
|
|
61
|
+
* it asserts what must be true of a working node and reports when it is not.
|
|
62
|
+
*
|
|
63
|
+
* It emits `SystemLivenessFailed` / `SystemLivenessRecovered` on the event bus
|
|
64
|
+
* with a STABLE finding id, exactly like the kernel's addon-health monitor, so
|
|
65
|
+
* AlertCenter raises one persistent alert per fault and dismisses it on
|
|
66
|
+
* recovery instead of producing a stream of them. The same lines go to the log
|
|
67
|
+
* at ERROR, which is what makes them alertable in Loki.
|
|
68
|
+
*
|
|
69
|
+
* Deliberately hub-only and deliberately read-only: it observes through
|
|
70
|
+
* `ctx.api` and changes nothing. A monitor that can act is a monitor that can
|
|
71
|
+
* cause the outage it was meant to report — this repo has already stopped a
|
|
72
|
+
* hub that way once (the remedial-action watcher, 2026-07-30).
|
|
73
|
+
*/
|
|
74
|
+
/** Event source stamped on every emission — the addon that observed it. */
|
|
75
|
+
var LIVENESS_SOURCE = {
|
|
76
|
+
type: "addon",
|
|
77
|
+
id: "liveness-monitor"
|
|
78
|
+
};
|
|
79
|
+
/** How often the invariants are checked. */
|
|
80
|
+
var TICK_INTERVAL_MS = 5 * 6e4;
|
|
81
|
+
/**
|
|
82
|
+
* Grace period after boot before anything is reported.
|
|
83
|
+
*
|
|
84
|
+
* Every check would be true during startup — devices restore, providers
|
|
85
|
+
* connect, the first segment takes a minute — and a monitor that cries at every
|
|
86
|
+
* restart is one nobody reads. The kernel's addon-health monitor takes the same
|
|
87
|
+
* position for the same reason.
|
|
88
|
+
*/
|
|
89
|
+
var BOOT_GRACE_MS = 6 * 6e4;
|
|
90
|
+
/** How long a recording camera may stay silent before it is a fault. */
|
|
91
|
+
var DEFAULT_STALE_AFTER_MS = 15 * 6e4;
|
|
92
|
+
/**
|
|
93
|
+
* How far back to look for "does this camera record at all". A camera with no
|
|
94
|
+
* footage in this window is treated as a non-recording camera and never
|
|
95
|
+
* reported — which is what keeps the check free of any configuration coupling.
|
|
96
|
+
*/
|
|
97
|
+
var RECORDING_LOOKBACK_MS = 1440 * 6e4;
|
|
98
|
+
var LivenessMonitorAddon = class extends require_dist.BaseAddon {
|
|
99
|
+
tickTimer = null;
|
|
100
|
+
startedAtMs = 0;
|
|
101
|
+
/** Finding ids currently reported, so recovery can be detected and announced. */
|
|
102
|
+
active = /* @__PURE__ */ new Set();
|
|
103
|
+
constructor() {
|
|
104
|
+
super({});
|
|
105
|
+
}
|
|
106
|
+
async onInitialize() {
|
|
107
|
+
this.startedAtMs = Date.now();
|
|
108
|
+
this.tickTimer = setInterval(() => {
|
|
109
|
+
this.tick();
|
|
110
|
+
}, TICK_INTERVAL_MS);
|
|
111
|
+
this.tickTimer.unref?.();
|
|
112
|
+
this.ctx.logger.info("liveness-monitor started", { meta: {
|
|
113
|
+
tickIntervalMs: TICK_INTERVAL_MS,
|
|
114
|
+
bootGraceMs: BOOT_GRACE_MS
|
|
115
|
+
} });
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
async onShutdown() {
|
|
119
|
+
if (this.tickTimer) clearInterval(this.tickTimer);
|
|
120
|
+
this.tickTimer = null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* One evaluation. Public so a test or an operator-facing action can force it
|
|
124
|
+
* without waiting out the interval.
|
|
125
|
+
*/
|
|
126
|
+
async tick() {
|
|
127
|
+
if (Date.now() - this.startedAtMs < BOOT_GRACE_MS) return [];
|
|
128
|
+
try {
|
|
129
|
+
const findings = evaluateLiveness(await this.gather());
|
|
130
|
+
this.report(findings);
|
|
131
|
+
return findings;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this.ctx.logger.error("liveness-monitor: check failed — node health is UNKNOWN this tick", { meta: { error: require_dist.errMsg(err) } });
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Read the facts through the cap surface. Changes nothing. */
|
|
138
|
+
async gather() {
|
|
139
|
+
const nowMs = Date.now();
|
|
140
|
+
const staleAfterMs = this.config.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
141
|
+
const devices = await this.ctx.api.deviceManager.listAll.query({});
|
|
142
|
+
if (devices.length === 0) return {
|
|
143
|
+
nowMs,
|
|
144
|
+
deviceCount: 0,
|
|
145
|
+
cameras: [],
|
|
146
|
+
staleAfterMs
|
|
147
|
+
};
|
|
148
|
+
const cameraDevices = devices.filter((d) => d.isCamera === true);
|
|
149
|
+
const cameras = [];
|
|
150
|
+
for (const device of cameraDevices) cameras.push({
|
|
151
|
+
deviceId: device.id,
|
|
152
|
+
online: device.online === true,
|
|
153
|
+
lastSegmentMs: await this.lastSegmentMs(device.id, nowMs, staleAfterMs)
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
nowMs,
|
|
157
|
+
deviceCount: devices.length,
|
|
158
|
+
cameras,
|
|
159
|
+
staleAfterMs
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Newest segment for a camera, or null when it has produced none in a day.
|
|
164
|
+
*
|
|
165
|
+
* Two steps on purpose: the healthy case asks only for the recent window,
|
|
166
|
+
* which is a handful of ranges, and only a camera that looks silent pays for
|
|
167
|
+
* the 24-hour lookback that decides whether it records at all.
|
|
168
|
+
*/
|
|
169
|
+
async lastSegmentMs(deviceId, nowMs, staleAfterMs) {
|
|
170
|
+
const recent = await this.ctx.api.recording.getAvailability.query({
|
|
171
|
+
deviceId,
|
|
172
|
+
fromMs: nowMs - staleAfterMs,
|
|
173
|
+
toMs: nowMs
|
|
174
|
+
});
|
|
175
|
+
if (recent.ranges.length > 0) return Math.max(...recent.ranges.map((r) => r.endMs));
|
|
176
|
+
const day = await this.ctx.api.recording.getAvailability.query({
|
|
177
|
+
deviceId,
|
|
178
|
+
fromMs: nowMs - RECORDING_LOOKBACK_MS,
|
|
179
|
+
toMs: nowMs
|
|
180
|
+
});
|
|
181
|
+
if (day.ranges.length === 0) return null;
|
|
182
|
+
return Math.max(...day.ranges.map((r) => r.endMs));
|
|
183
|
+
}
|
|
184
|
+
/** Announce new faults and recoveries; say nothing about an unchanged fault. */
|
|
185
|
+
report(findings) {
|
|
186
|
+
const current = new Set(findings.map((f) => f.id));
|
|
187
|
+
for (const finding of findings) {
|
|
188
|
+
if (this.active.has(finding.id)) continue;
|
|
189
|
+
this.active.add(finding.id);
|
|
190
|
+
this.ctx.logger.error(`liveness: ${finding.title} — ${finding.message}`, {
|
|
191
|
+
...finding.deviceId !== void 0 ? { tags: { deviceId: finding.deviceId } } : {},
|
|
192
|
+
meta: {
|
|
193
|
+
findingId: finding.id,
|
|
194
|
+
severity: finding.severity
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessFailed, LIVENESS_SOURCE, {
|
|
198
|
+
findingId: finding.id,
|
|
199
|
+
severity: finding.severity,
|
|
200
|
+
title: finding.title,
|
|
201
|
+
message: finding.message,
|
|
202
|
+
...finding.deviceId !== void 0 ? { deviceId: finding.deviceId } : {}
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
for (const id of [...this.active]) {
|
|
206
|
+
if (current.has(id)) continue;
|
|
207
|
+
this.active.delete(id);
|
|
208
|
+
this.ctx.logger.info(`liveness: recovered — ${id}`, { meta: { findingId: id } });
|
|
209
|
+
this.ctx.eventBus.emit(require_dist.createEvent(require_dist.EventCategory.SystemLivenessRecovered, LIVENESS_SOURCE, { findingId: id }));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
exports.LivenessMonitorAddon = LivenessMonitorAddon;
|
|
215
|
+
exports.evaluateLiveness = evaluateLiveness;
|
|
216
|
+
exports.isLivenessFindingId = isLivenessFindingId;
|