@camstack/addon-post-analysis 1.2.607 → 1.2.608
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/{clip-model-registry-VgOXFRFr.js → clip-model-registry-B11yS32V.js} +472 -47
- package/dist/{clip-model-registry-CTfe5v5_.mjs → clip-model-registry-BItfTZEx.mjs} +431 -48
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DUNQoBDI.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-LGLlz5Sk.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-iXmJsQ9Y.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Cm7pZeMH.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-B8mC_3oV.mjs → hostInit-Cko0y33G.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +457 -51
- package/dist/pipeline-analytics/index.mjs +457 -51
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-1AlitJg2.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DalpVM1q.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-B11yS32V.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -10281,6 +10281,7 @@ var NC_SYSTEM_STATE_LEGACY = "notification-center:system-state";
|
|
|
10281
10281
|
var NC_OCCUPANCY_LEGACY = "notification-center:occupancy";
|
|
10282
10282
|
var NC_DEVICE_PRUNES_LEGACY = "notification-center:device-prunes";
|
|
10283
10283
|
var NC_STATE_SUMMARY_STAMPS_LEGACY = "notification-center:state-summary-stamps";
|
|
10284
|
+
var NC_ALERT_EPISODES_LEGACY = "notification-center:alert-episodes";
|
|
10284
10285
|
var NC_META_LEGACY = "notification-center:meta";
|
|
10285
10286
|
var NC_SNOOZES_LEGACY = "notification-center:snoozes";
|
|
10286
10287
|
var NC_SNOOZE_SUPPRESSED_LEGACY = "notification-center:snooze-suppressed";
|
|
@@ -10301,6 +10302,7 @@ var ncDurableRecordKinds = require_clip_model_registry.defineRecordKinds({
|
|
|
10301
10302
|
liveness: ledger(NC_LIVENESS_LEGACY, "a liveness state TRANSITION the centre accepted (seed or flip); a no-flip writes nothing"),
|
|
10302
10303
|
"device-alerts": ledger(NC_DEVICE_ALERTS_LEGACY, "a sustained device alert episode the centre confirmed or cleared"),
|
|
10303
10304
|
"system-state": ledger(NC_SYSTEM_STATE_LEGACY, "a system-state subject flip the state summary accepted"),
|
|
10305
|
+
"alert-episodes": ledger(NC_ALERT_EPISODES_LEGACY, "an alert episode the centre notified (critical or error); its resolution deletes the row"),
|
|
10304
10306
|
occupancy: ledger(NC_OCCUPANCY_LEGACY, "a confirmed occupancy level write-through after the watcher decided an edge"),
|
|
10305
10307
|
"device-prunes": ledger(NC_DEVICE_PRUNES_LEGACY, "one row recording what a deleted-device prune removed"),
|
|
10306
10308
|
"state-summary-stamps": ledger(NC_STATE_SUMMARY_STAMPS_LEGACY, "digest delivery stamps per rule kind"),
|
|
@@ -10320,6 +10322,7 @@ var NC_PHASE3_RECORD_BACKED_LEGACY_COLLECTIONS = [
|
|
|
10320
10322
|
NC_LIVENESS_LEGACY,
|
|
10321
10323
|
NC_DEVICE_ALERTS_LEGACY,
|
|
10322
10324
|
NC_SYSTEM_STATE_LEGACY,
|
|
10325
|
+
NC_ALERT_EPISODES_LEGACY,
|
|
10323
10326
|
NC_OCCUPANCY_LEGACY,
|
|
10324
10327
|
NC_DEVICE_PRUNES_LEGACY,
|
|
10325
10328
|
NC_STATE_SUMMARY_STAMPS_LEGACY,
|
|
@@ -10741,6 +10744,120 @@ var NcAlertStore = class {
|
|
|
10741
10744
|
}
|
|
10742
10745
|
};
|
|
10743
10746
|
//#endregion
|
|
10747
|
+
//#region src/notification-center/alert-episode-ledger.ts
|
|
10748
|
+
/**
|
|
10749
|
+
* @durable class=ledger owner=notification-center
|
|
10750
|
+
* write="an alert episode the centre notified (alert-critical / alert-error); its resolution deletes the row"
|
|
10751
|
+
* retention="a row lives while its episode is open. A row whose alert was pruned by the alert centre's own retention is dropped after NC_ALERT_EPISODE_MAX_AGE_MS by the reconcile, never on absence alone (D49)."
|
|
10752
|
+
*/
|
|
10753
|
+
var NC_ALERT_EPISODES_COLLECTION = "notification-center:alert-episodes";
|
|
10754
|
+
var NC_ALERT_EPISODES_COLUMNS = [
|
|
10755
|
+
{
|
|
10756
|
+
name: "episodeId",
|
|
10757
|
+
type: "TEXT",
|
|
10758
|
+
primaryKey: true,
|
|
10759
|
+
notNull: true
|
|
10760
|
+
},
|
|
10761
|
+
(
|
|
10762
|
+
/** The kind that was notified: `alert-critical` or `alert-error`. */
|
|
10763
|
+
{
|
|
10764
|
+
name: "kind",
|
|
10765
|
+
type: "TEXT",
|
|
10766
|
+
notNull: true
|
|
10767
|
+
}),
|
|
10768
|
+
{
|
|
10769
|
+
name: "alertId",
|
|
10770
|
+
type: "TEXT",
|
|
10771
|
+
notNull: true
|
|
10772
|
+
},
|
|
10773
|
+
{
|
|
10774
|
+
name: "since",
|
|
10775
|
+
type: "INTEGER",
|
|
10776
|
+
notNull: true
|
|
10777
|
+
}
|
|
10778
|
+
];
|
|
10779
|
+
/** Longer than the alert centre's maximum retention (365 days). */
|
|
10780
|
+
var NC_ALERT_EPISODE_MAX_AGE_MS = 400 * 24 * 60 * 60 * 1e3;
|
|
10781
|
+
function isEpisodeKind(value) {
|
|
10782
|
+
return value === "alert-critical" || value === "alert-error";
|
|
10783
|
+
}
|
|
10784
|
+
var NC_ALERT_EPISODES_SPEC = {
|
|
10785
|
+
collection: NC_ALERT_EPISODES_COLLECTION,
|
|
10786
|
+
columns: NC_ALERT_EPISODES_COLUMNS,
|
|
10787
|
+
writeMode: "write-behind",
|
|
10788
|
+
keyOf: (row) => row.episodeId,
|
|
10789
|
+
toValue: (row) => ({
|
|
10790
|
+
kind: row.kind,
|
|
10791
|
+
alertId: row.alertId,
|
|
10792
|
+
since: row.since
|
|
10793
|
+
}),
|
|
10794
|
+
fromRecord: (key, data) => {
|
|
10795
|
+
const kind = data["kind"];
|
|
10796
|
+
const alertId = data["alertId"];
|
|
10797
|
+
const since = data["since"];
|
|
10798
|
+
if (!isEpisodeKind(kind) || typeof alertId !== "string" || typeof since !== "number") return null;
|
|
10799
|
+
return {
|
|
10800
|
+
episodeId: key,
|
|
10801
|
+
kind,
|
|
10802
|
+
alertId,
|
|
10803
|
+
since
|
|
10804
|
+
};
|
|
10805
|
+
}
|
|
10806
|
+
};
|
|
10807
|
+
NC_ALERT_EPISODES_SPEC.fromRecord, NC_ALERT_EPISODES_SPEC.toValue;
|
|
10808
|
+
var NcAlertEpisodeLedger = class {
|
|
10809
|
+
ledger;
|
|
10810
|
+
constructor(deps) {
|
|
10811
|
+
this.ledger = new DurableLedger({
|
|
10812
|
+
spec: NC_ALERT_EPISODES_SPEC,
|
|
10813
|
+
kind: resolveDurableKind(NC_ALERT_EPISODES_SPEC, ncDurableRecordKinds["alert-episodes"], deps),
|
|
10814
|
+
logger: deps.logger
|
|
10815
|
+
});
|
|
10816
|
+
}
|
|
10817
|
+
static declare(store) {
|
|
10818
|
+
return declareDurableSpecLegacy(store, NC_ALERT_EPISODES_SPEC);
|
|
10819
|
+
}
|
|
10820
|
+
async load() {
|
|
10821
|
+
return (await this.ledger.load()).length;
|
|
10822
|
+
}
|
|
10823
|
+
/** Has a load READ the store once? Until then the mirror is not the truth. */
|
|
10824
|
+
hasAnswered() {
|
|
10825
|
+
return this.ledger.hasAnswered();
|
|
10826
|
+
}
|
|
10827
|
+
has(episodeId) {
|
|
10828
|
+
return this.ledger.has(episodeId);
|
|
10829
|
+
}
|
|
10830
|
+
snapshot() {
|
|
10831
|
+
return this.ledger.snapshot();
|
|
10832
|
+
}
|
|
10833
|
+
/**
|
|
10834
|
+
* Judge one announced edge and advance the ledger. Synchronous: the verdict
|
|
10835
|
+
* is a function of the mirror (D49); the durable write trails it.
|
|
10836
|
+
*/
|
|
10837
|
+
admit(edge, now) {
|
|
10838
|
+
const held = this.ledger.get(edge.episodeId);
|
|
10839
|
+
if (edge.phase === "raised") {
|
|
10840
|
+
if (edge.kind !== "alert-critical" && edge.kind !== "alert-error") return "duplicate";
|
|
10841
|
+
if (held !== void 0 && (held.kind === "alert-critical" || edge.kind === "alert-error")) return "duplicate";
|
|
10842
|
+
this.ledger.put({
|
|
10843
|
+
episodeId: edge.episodeId,
|
|
10844
|
+
kind: edge.kind,
|
|
10845
|
+
alertId: edge.alertId,
|
|
10846
|
+
since: held?.since ?? now
|
|
10847
|
+
});
|
|
10848
|
+
return "notify";
|
|
10849
|
+
}
|
|
10850
|
+
if (held === void 0) return "unknown-episode";
|
|
10851
|
+
this.ledger.forget(edge.episodeId);
|
|
10852
|
+
return held.kind === "alert-critical" && edge.kind === "alert-critical-resolved" ? "notify" : "closed-silently";
|
|
10853
|
+
}
|
|
10854
|
+
/** Drop rows older than the alert centre could still hold the alert. */
|
|
10855
|
+
async pruneOlderThan(cutoffMs) {
|
|
10856
|
+
const keep = new Set(this.ledger.snapshot().filter((row) => row.since >= cutoffMs).map((row) => row.episodeId));
|
|
10857
|
+
return this.ledger.pruneExcept(keep);
|
|
10858
|
+
}
|
|
10859
|
+
};
|
|
10860
|
+
//#endregion
|
|
10744
10861
|
//#region src/notification-center/device-prune-ledger.ts
|
|
10745
10862
|
/**
|
|
10746
10863
|
* @durable class=ledger owner=notification-center
|
|
@@ -16026,6 +16143,34 @@ function bestMatchingKindPreference(signal, ownerKind) {
|
|
|
16026
16143
|
}
|
|
16027
16144
|
//#endregion
|
|
16028
16145
|
//#region src/notification-center/event-intake.ts
|
|
16146
|
+
/**
|
|
16147
|
+
* The notification path's INTAKE SEAM — one event in, no knowledge of who
|
|
16148
|
+
* produced it.
|
|
16149
|
+
*
|
|
16150
|
+
* Why this file exists at all: until now every producer reached the centre
|
|
16151
|
+
* through its own record-shaped door (`onObjectEventPersisted(ObjectEvent)`,
|
|
16152
|
+
* `onSensorEventPersisted(SensorEvent)`, …). The doors did nothing but build a
|
|
16153
|
+
* subject and schedule an evaluation, yet their SIGNATURES made the notification
|
|
16154
|
+
* path look like a function of the pipeline's row types — so "notify about this"
|
|
16155
|
+
* was not expressible without first having a persisted row to hand.
|
|
16156
|
+
*
|
|
16157
|
+
* The split this file draws is between PRODUCING an event and CONSUMING one:
|
|
16158
|
+
*
|
|
16159
|
+
* - The producer owns persistence. The tracker persists its tracks, the sensor
|
|
16160
|
+
* ingest persists its rows, and a synthetic producer (the rule tester)
|
|
16161
|
+
* persists NOTHING. Whether a row exists is the producer's business and
|
|
16162
|
+
* never the consumer's.
|
|
16163
|
+
* - The consumer owns match → media → delivery. It receives an
|
|
16164
|
+
* {@link NcIncomingEvent} and cannot tell which producer built it, because
|
|
16165
|
+
* the only thing the matcher can read is `subject`, and `subject` carries no
|
|
16166
|
+
* provenance beyond the detection `source` a real record would carry anyway.
|
|
16167
|
+
*
|
|
16168
|
+
* {@link NcIncomingEvent.origin} exists for LOGS, and for the outbox row's
|
|
16169
|
+
* marker. It is deliberately NOT part of `NcRuleSubject`: a condition cannot
|
|
16170
|
+
* reach it, so no rule can be authored that behaves differently for a test. That
|
|
16171
|
+
* is the property the tester is worth nothing without — a test that takes a
|
|
16172
|
+
* different branch is a test of the branch, not of the rule.
|
|
16173
|
+
*/
|
|
16029
16174
|
/** Object-event insert (movement-state transitions + zone crossings). */
|
|
16030
16175
|
function incomingFromObjectEvent(event) {
|
|
16031
16176
|
return {
|
|
@@ -16869,9 +17014,45 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
16869
17014
|
case "system.restarting": return incomingFromNodeRestarting(facts, data);
|
|
16870
17015
|
case "recording.export.completed": return incomingFromExportCompleted(facts, data);
|
|
16871
17016
|
case "backup.run-completed": return incomingFromBackupCompleted(facts, data);
|
|
17017
|
+
case "alert.episode": return incomingFromAlertEpisode(facts);
|
|
16872
17018
|
default: return null;
|
|
16873
17019
|
}
|
|
16874
17020
|
}
|
|
17021
|
+
/**
|
|
17022
|
+
* An alert episode edge (`alert-critical`, `alert-error`, `alert-critical-resolved`).
|
|
17023
|
+
*
|
|
17024
|
+
* The WORDS are the alert's own: the alert centre composed the title and the
|
|
17025
|
+
* message when it opened the alert, and a rule's template may reword them with
|
|
17026
|
+
* `{{title}}`, `{{message}}`, `{{severity}}`, `{{category}}`, `{{source}}`,
|
|
17027
|
+
* `{{node}}` and `{{actionUrl}}`. The subject is the EPISODE, so the dedup slot
|
|
17028
|
+
* of a new episode is never the one a previous, resolved, episode held.
|
|
17029
|
+
*
|
|
17030
|
+
* Whether an edge is notified at all (once per episode, reconciled from the
|
|
17031
|
+
* alerts store) is decided before this point, by `NcAlertEpisodeLedger`.
|
|
17032
|
+
*/
|
|
17033
|
+
function incomingFromAlertEpisode(facts) {
|
|
17034
|
+
const payload = require_clip_model_registry.parseAlertEpisodePayload(facts.data);
|
|
17035
|
+
if (payload === null) return null;
|
|
17036
|
+
const kind = require_clip_model_registry.alertEpisodeKind(payload.severity, payload.phase);
|
|
17037
|
+
if (kind === null) return null;
|
|
17038
|
+
return systemSubject({
|
|
17039
|
+
facts,
|
|
17040
|
+
kind,
|
|
17041
|
+
subject: `alert:${payload.episodeId}`,
|
|
17042
|
+
titleKey: `system.${kind}.title`,
|
|
17043
|
+
bodyKey: `system.${kind}.body`,
|
|
17044
|
+
textParams: {
|
|
17045
|
+
title: payload.title,
|
|
17046
|
+
message: payload.message,
|
|
17047
|
+
severity: payload.severity,
|
|
17048
|
+
category: payload.category,
|
|
17049
|
+
source: payload.sourceId ?? "",
|
|
17050
|
+
nodeId: payload.nodeId ?? "",
|
|
17051
|
+
actionUrl: payload.actionUrl ?? ""
|
|
17052
|
+
},
|
|
17053
|
+
...payload.nodeId !== void 0 ? { nodeId: payload.nodeId } : {}
|
|
17054
|
+
});
|
|
17055
|
+
}
|
|
16875
17056
|
/** The shells whose update has its own sentence. Anything else gets the plain
|
|
16876
17057
|
* one: a body that names a shell nobody observed is worse than a vague body. */
|
|
16877
17058
|
var WRAPPER_BODY_KINDS = new Set([
|
|
@@ -19354,6 +19535,7 @@ async function declareNcPhase3SettingsStoreLegacyCollections(store) {
|
|
|
19354
19535
|
async function declareNcPhase3RecordBackedLegacyCollections(store) {
|
|
19355
19536
|
await OccupancyStore.declare(store);
|
|
19356
19537
|
await NcLivenessLedger.declare(store);
|
|
19538
|
+
await NcAlertEpisodeLedger.declare(store);
|
|
19357
19539
|
await NcAlertStore.declare(store);
|
|
19358
19540
|
await NcStateSummaryService.declare(store);
|
|
19359
19541
|
await NcSnoozeStore.declare(store);
|
|
@@ -32881,6 +33063,12 @@ var en_default = {
|
|
|
32881
33063
|
"system.backup-completed.title": "Backup completed",
|
|
32882
33064
|
"system.backup-completed.body": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations ({{sizeMB}} MB)",
|
|
32883
33065
|
"system.backup-completed.body.partial": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations — {{destinationsFailed}} failed",
|
|
33066
|
+
"system.alert-critical.title": "{{title}}",
|
|
33067
|
+
"system.alert-critical.body": "{{message}}",
|
|
33068
|
+
"system.alert-error.title": "{{title}}",
|
|
33069
|
+
"system.alert-error.body": "{{message}}",
|
|
33070
|
+
"system.alert-critical-resolved.title": "Resolved: {{title}}",
|
|
33071
|
+
"system.alert-critical-resolved.body": "{{message}}",
|
|
32884
33072
|
"system.device-consumable-low.title": "Consumable low — {{deviceName}}",
|
|
32885
33073
|
"system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
|
|
32886
33074
|
"system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
|
|
@@ -33261,6 +33449,12 @@ var it_default = {
|
|
|
33261
33449
|
"system.backup-completed.title": "Backup completato",
|
|
33262
33450
|
"system.backup-completed.body": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} ({{sizeMB}} MB)",
|
|
33263
33451
|
"system.backup-completed.body.partial": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} — {{destinationsFailed}} fallite",
|
|
33452
|
+
"system.alert-critical.title": "{{title}}",
|
|
33453
|
+
"system.alert-critical.body": "{{message}}",
|
|
33454
|
+
"system.alert-error.title": "{{title}}",
|
|
33455
|
+
"system.alert-error.body": "{{message}}",
|
|
33456
|
+
"system.alert-critical-resolved.title": "Risolto: {{title}}",
|
|
33457
|
+
"system.alert-critical-resolved.body": "{{message}}",
|
|
33264
33458
|
"system.device-consumable-low.title": "Consumabile basso — {{deviceName}}",
|
|
33265
33459
|
"system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
|
|
33266
33460
|
"system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
|
|
@@ -35236,6 +35430,7 @@ function systemEventSubject(kind, scope) {
|
|
|
35236
35430
|
if (kind === "node-updating") return `node-updating:${scope.nodeId ?? "hub"}:1.0.1`;
|
|
35237
35431
|
if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
|
|
35238
35432
|
if (kind === "backup-completed") return "backup:test";
|
|
35433
|
+
if (kind.startsWith("alert-")) return "alert:test";
|
|
35239
35434
|
return `alarm:${scope.deviceId ?? 0}`;
|
|
35240
35435
|
}
|
|
35241
35436
|
/**
|
|
@@ -35376,7 +35571,12 @@ function planRuleTest(input) {
|
|
|
35376
35571
|
nodes: "hub",
|
|
35377
35572
|
fromVersion: "1.0.0",
|
|
35378
35573
|
toVersion: "1.0.1",
|
|
35379
|
-
title: "Export",
|
|
35574
|
+
title: systemEventKind.startsWith("alert-") ? "Recording location full" : "Export",
|
|
35575
|
+
message: "No free space left on the recording location",
|
|
35576
|
+
severity: systemEventKind === "alert-error" ? "error" : "critical",
|
|
35577
|
+
category: "recorder.placement",
|
|
35578
|
+
source: "recorder",
|
|
35579
|
+
actionUrl: "/system/addons",
|
|
35380
35580
|
count: "1"
|
|
35381
35581
|
},
|
|
35382
35582
|
...systemScope.deviceId !== void 0 ? { deviceId: systemScope.deviceId } : {},
|
|
@@ -51962,6 +52162,10 @@ function groupedAlarmPhaseKey(ruleId, trackId) {
|
|
|
51962
52162
|
var NO_MATCH_REPORT_INTERVAL_MS = 6e4;
|
|
51963
52163
|
var DEFAULT_DRAIN_INTERVAL_MS = 2e3;
|
|
51964
52164
|
var DEFAULT_RULE_RELOAD_INTERVAL_MS = 3e4;
|
|
52165
|
+
/** How often open critical/error alerts are reconciled against the notified episodes. */
|
|
52166
|
+
var NC_ALERT_RECONCILE_INTERVAL_MS = 5 * 6e4;
|
|
52167
|
+
/** The most alerts one reconcile reads (the alert centre's own ceiling). */
|
|
52168
|
+
var NC_ALERT_RECONCILE_LIMIT = 5e3;
|
|
51965
52169
|
var DEFAULT_RECONCILE_WINDOW_MS = 15 * 6e4;
|
|
51966
52170
|
/** Overlap subtracted from the watermark on boot (idempotent, cheap). */
|
|
51967
52171
|
var RECONCILE_OVERLAP_MS = 6e4;
|
|
@@ -52534,6 +52738,15 @@ var NotificationCenter = class {
|
|
|
52534
52738
|
* seeds, only a real flip notifies.
|
|
52535
52739
|
*/
|
|
52536
52740
|
liveness;
|
|
52741
|
+
/**
|
|
52742
|
+
* Which alert episodes were already notified (`alert-episode-ledger.ts`): the
|
|
52743
|
+
* alert centre sends nothing of its own, so this is what makes its bus
|
|
52744
|
+
* announcements and the reconcile from the alerts store safe to run together.
|
|
52745
|
+
*/
|
|
52746
|
+
alertEpisodes;
|
|
52747
|
+
/** The boot load of {@link alertEpisodes} has been ATTEMPTED (bus edges before it are the reconcile's). */
|
|
52748
|
+
alertEpisodesLoaded = false;
|
|
52749
|
+
alertReconcileTimer = null;
|
|
52537
52750
|
/** Batches `addon.updated` (version-bearing) into one notification per 30 s. */
|
|
52538
52751
|
addonUpdates;
|
|
52539
52752
|
/** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
|
|
@@ -53235,6 +53448,10 @@ var NotificationCenter = class {
|
|
|
53235
53448
|
...durableStore,
|
|
53236
53449
|
logger: this.logger.child("liveness")
|
|
53237
53450
|
});
|
|
53451
|
+
this.alertEpisodes = new NcAlertEpisodeLedger({
|
|
53452
|
+
...durableStore,
|
|
53453
|
+
logger: this.logger.child("alert-episodes")
|
|
53454
|
+
});
|
|
53238
53455
|
this.addonUpdates = new AddonUpdateCoalescer((updates) => {
|
|
53239
53456
|
const incoming = incomingFromAddonUpdates({
|
|
53240
53457
|
eventId: `addon-updated:${updates.map((u) => u.packageName).join(",")}`,
|
|
@@ -53631,6 +53848,10 @@ var NotificationCenter = class {
|
|
|
53631
53848
|
this.reloadTimer = setInterval(() => {
|
|
53632
53849
|
this.reloadRules();
|
|
53633
53850
|
}, this.deps.ruleReloadIntervalMs ?? DEFAULT_RULE_RELOAD_INTERVAL_MS);
|
|
53851
|
+
this.alertReconcileTimer = setInterval(() => {
|
|
53852
|
+
this.reconcileAlertEpisodes();
|
|
53853
|
+
}, this.deps.alertReconcileIntervalMs ?? NC_ALERT_RECONCILE_INTERVAL_MS);
|
|
53854
|
+
this.alertReconcileTimer.unref?.();
|
|
53634
53855
|
const timelapseScheduler = this.timelapseScheduler;
|
|
53635
53856
|
if (timelapseScheduler !== null) await timed("timelapseProducer", () => timelapseScheduler.start()).catch((err) => {
|
|
53636
53857
|
this.logger.warn("timelapse producer did not start", { meta: { error: String(err) } });
|
|
@@ -53746,6 +53967,7 @@ var NotificationCenter = class {
|
|
|
53746
53967
|
await this.deviceDirectory.refresh();
|
|
53747
53968
|
})) return;
|
|
53748
53969
|
if (!await phase("liveness", () => this.hydrateLiveness())) return;
|
|
53970
|
+
if (!await phase("alertEpisodes", () => this.hydrateAlertEpisodes())) return;
|
|
53749
53971
|
if (!await phase("deviceAlerts", async () => {
|
|
53750
53972
|
const rows = await this.alertStore.load();
|
|
53751
53973
|
this.logger.info("device alert ledger reseeded", { meta: { rows } });
|
|
@@ -53788,6 +54010,10 @@ var NotificationCenter = class {
|
|
|
53788
54010
|
clearInterval(this.reloadTimer);
|
|
53789
54011
|
this.reloadTimer = null;
|
|
53790
54012
|
}
|
|
54013
|
+
if (this.alertReconcileTimer !== null) {
|
|
54014
|
+
clearInterval(this.alertReconcileTimer);
|
|
54015
|
+
this.alertReconcileTimer = null;
|
|
54016
|
+
}
|
|
53791
54017
|
this.timelapseScheduler?.stop();
|
|
53792
54018
|
this.summaryProducer?.stop();
|
|
53793
54019
|
await this.closeGroupedWindowsOnStop();
|
|
@@ -54799,6 +55025,10 @@ var NotificationCenter = class {
|
|
|
54799
55025
|
return;
|
|
54800
55026
|
}
|
|
54801
55027
|
this.stateSummary.observeRawEvent(event.category, event.data, this.now(), event.source.nodeId);
|
|
55028
|
+
if (event.category === "alert.episode") {
|
|
55029
|
+
if (this.alertEpisodesLoaded) this.admitAlertEpisode(event, "bus");
|
|
55030
|
+
return;
|
|
55031
|
+
}
|
|
54802
55032
|
if (event.category === "addon.started" || event.category === "addon.restarted") return;
|
|
54803
55033
|
if (event.category === "pipeline.camera-assigned") return;
|
|
54804
55034
|
const battery = batterySliceOf(event);
|
|
@@ -54832,10 +55062,6 @@ var NotificationCenter = class {
|
|
|
54832
55062
|
const system = incoming.subject.systemEvent;
|
|
54833
55063
|
if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
|
|
54834
55064
|
if (system !== void 0) this.updateLandings.observe(system, this.now());
|
|
54835
|
-
if (system !== void 0 && system.kind === "addon-crash-loop") {
|
|
54836
|
-
this.logger.debug("addon-crash-loop is pushed by the hub alert centre, not by a rule", { meta: { subject: system.subject } });
|
|
54837
|
-
return;
|
|
54838
|
-
}
|
|
54839
55065
|
const observation = system !== void 0 ? ledgeredLiveness(system) : null;
|
|
54840
55066
|
if (observation !== null) {
|
|
54841
55067
|
const previous = this.liveness.stateOf(observation.subject);
|
|
@@ -57487,6 +57713,89 @@ var NotificationCenter = class {
|
|
|
57487
57713
|
await this.occupancyStore.pruneExcept(this.activeOccupancyKeys());
|
|
57488
57714
|
}
|
|
57489
57715
|
/**
|
|
57716
|
+
* One edge of an alert episode, from the bus or from the reconcile. The
|
|
57717
|
+
* ledger decides whether it is news; only news reaches the rules.
|
|
57718
|
+
*/
|
|
57719
|
+
admitAlertEpisode(event, via) {
|
|
57720
|
+
const payload = require_clip_model_registry.parseAlertEpisodePayload(event.data);
|
|
57721
|
+
if (payload === null) {
|
|
57722
|
+
this.logger.warn("alert episode event with an unreadable payload ignored", { meta: {
|
|
57723
|
+
via,
|
|
57724
|
+
eventId: event.id
|
|
57725
|
+
} });
|
|
57726
|
+
return false;
|
|
57727
|
+
}
|
|
57728
|
+
const verdict = this.alertEpisodes.admit({
|
|
57729
|
+
kind: require_clip_model_registry.alertEpisodeKind(payload.severity, payload.phase),
|
|
57730
|
+
phase: payload.phase,
|
|
57731
|
+
episodeId: payload.episodeId,
|
|
57732
|
+
alertId: payload.alertId
|
|
57733
|
+
}, this.now());
|
|
57734
|
+
const meta = {
|
|
57735
|
+
via,
|
|
57736
|
+
alertId: payload.alertId,
|
|
57737
|
+
episodeId: payload.episodeId,
|
|
57738
|
+
phase: payload.phase,
|
|
57739
|
+
severity: payload.severity,
|
|
57740
|
+
verdict
|
|
57741
|
+
};
|
|
57742
|
+
if (verdict !== "notify") {
|
|
57743
|
+
this.logger.debug("alert episode edge not notified", { meta });
|
|
57744
|
+
return false;
|
|
57745
|
+
}
|
|
57746
|
+
const incoming = incomingFromSystemEvent(event, this.deviceDirectory.lookup());
|
|
57747
|
+
if (incoming === null) return false;
|
|
57748
|
+
this.logger.info("alert episode notified to the rules", { meta });
|
|
57749
|
+
this.consumeEvent(incoming);
|
|
57750
|
+
return true;
|
|
57751
|
+
}
|
|
57752
|
+
async hydrateAlertEpisodes() {
|
|
57753
|
+
try {
|
|
57754
|
+
const rows = await this.alertEpisodes.load();
|
|
57755
|
+
this.logger.info("alert episode ledger reseeded", { meta: { rows } });
|
|
57756
|
+
} finally {
|
|
57757
|
+
this.alertEpisodesLoaded = true;
|
|
57758
|
+
}
|
|
57759
|
+
await this.reconcileAlertEpisodes();
|
|
57760
|
+
}
|
|
57761
|
+
/**
|
|
57762
|
+
* The alerts store against the notified episodes (D11, D49): every OPEN
|
|
57763
|
+
* critical/error alert not yet notified is notified, and every notified
|
|
57764
|
+
* episode whose alert is CLOSED gets its resolution. Never runs on a ledger
|
|
57765
|
+
* the store has not answered — an empty, unread mirror would re-notify every
|
|
57766
|
+
* open alert — and never reads absence as closure: an alert that is not in
|
|
57767
|
+
* the answer is left alone (rows age out by {@link NC_ALERT_EPISODE_MAX_AGE_MS}).
|
|
57768
|
+
*/
|
|
57769
|
+
async reconcileAlertEpisodes() {
|
|
57770
|
+
const read = this.deps.readAlerts;
|
|
57771
|
+
if (read === void 0 || !this.evaluationActive || !this.alertEpisodes.hasAnswered()) return;
|
|
57772
|
+
let alerts;
|
|
57773
|
+
try {
|
|
57774
|
+
alerts = await read();
|
|
57775
|
+
} catch (err) {
|
|
57776
|
+
this.logger.warn("alert episode reconcile could not read the alerts", { meta: { error: require_clip_model_registry.errMsg(err) } });
|
|
57777
|
+
return;
|
|
57778
|
+
}
|
|
57779
|
+
let notified = 0;
|
|
57780
|
+
for (const alert of alerts) {
|
|
57781
|
+
const episodeId = require_clip_model_registry.alertEpisodeId(alert);
|
|
57782
|
+
const open = require_clip_model_registry.isAlertEpisodeOpen(alert.status);
|
|
57783
|
+
if (open && !require_clip_model_registry.isAlertEpisodeSeverity(alert.severity)) continue;
|
|
57784
|
+
if (!open && !this.alertEpisodes.has(episodeId)) continue;
|
|
57785
|
+
const phase = open ? "raised" : "resolved";
|
|
57786
|
+
if (this.admitAlertEpisode(require_clip_model_registry.createEvent(require_clip_model_registry.EventCategory.AlertEpisode, {
|
|
57787
|
+
type: "addon",
|
|
57788
|
+
id: "notification-center"
|
|
57789
|
+
}, require_clip_model_registry.alertEpisodePayload(alert, phase, this.now())), "reconcile")) notified += 1;
|
|
57790
|
+
}
|
|
57791
|
+
const pruned = alerts.length < NC_ALERT_RECONCILE_LIMIT ? await this.alertEpisodes.pruneOlderThan(this.now() - NC_ALERT_EPISODE_MAX_AGE_MS) : 0;
|
|
57792
|
+
if (notified > 0 || pruned > 0) this.logger.info("alert episodes reconciled", { meta: {
|
|
57793
|
+
alerts: alerts.length,
|
|
57794
|
+
notified,
|
|
57795
|
+
pruned
|
|
57796
|
+
} });
|
|
57797
|
+
}
|
|
57798
|
+
/**
|
|
57490
57799
|
* Boot reseed of the durable liveness ledger.
|
|
57491
57800
|
*
|
|
57492
57801
|
* The count is said OUT LOUD at info, because it is the one line that
|
|
@@ -94641,6 +94950,62 @@ function pickWritableMediaLocation(locations, stickyId) {
|
|
|
94641
94950
|
return writable.toSorted((a, b) => b.headroomBytes - a.headroomBytes || a.id.localeCompare(b.id))[0]?.id ?? null;
|
|
94642
94951
|
}
|
|
94643
94952
|
//#endregion
|
|
94953
|
+
//#region src/pipeline-analytics/event-media-write-location.ts
|
|
94954
|
+
var EventMediaWriteLocator = class {
|
|
94955
|
+
deps;
|
|
94956
|
+
cached = null;
|
|
94957
|
+
/**
|
|
94958
|
+
* The last location actually picked. Survives {@link invalidate}, so a ring
|
|
94959
|
+
* keeps writing where it was writing while that stays writable — the
|
|
94960
|
+
* stickiness the class default used to provide (D383).
|
|
94961
|
+
*/
|
|
94962
|
+
lastPick = null;
|
|
94963
|
+
constructor(deps) {
|
|
94964
|
+
this.deps = deps;
|
|
94965
|
+
}
|
|
94966
|
+
/** Drop the cached pick: the next write re-reads the location set. */
|
|
94967
|
+
invalidate() {
|
|
94968
|
+
this.cached = null;
|
|
94969
|
+
}
|
|
94970
|
+
async resolve(logger, deviceId) {
|
|
94971
|
+
if (this.cached !== null) return this.cached;
|
|
94972
|
+
let locations;
|
|
94973
|
+
try {
|
|
94974
|
+
locations = await this.deps.list();
|
|
94975
|
+
} catch (err) {
|
|
94976
|
+
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: require_clip_model_registry.errMsg(err) } });
|
|
94977
|
+
this.cached = DEFAULT_MEDIA_LOCATION;
|
|
94978
|
+
return DEFAULT_MEDIA_LOCATION;
|
|
94979
|
+
}
|
|
94980
|
+
const picked = pickWritableMediaLocation(locations.map((l) => ({
|
|
94981
|
+
id: l.id,
|
|
94982
|
+
mode: require_clip_model_registry.resolveLocationMode(l),
|
|
94983
|
+
headroomBytes: l.capacity?.availableBytes ?? 0
|
|
94984
|
+
})), this.lastPick ?? void 0);
|
|
94985
|
+
if (picked === null) {
|
|
94986
|
+
const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
|
|
94987
|
+
logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
|
|
94988
|
+
tags: { deviceId },
|
|
94989
|
+
meta: { locationsSeen: seen }
|
|
94990
|
+
});
|
|
94991
|
+
throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
|
|
94992
|
+
}
|
|
94993
|
+
this.cached = picked;
|
|
94994
|
+
this.lastPick = picked;
|
|
94995
|
+
return picked;
|
|
94996
|
+
}
|
|
94997
|
+
};
|
|
94998
|
+
/**
|
|
94999
|
+
* Ring the locator from the location-set doorbell. `onRing` lets the caller drop
|
|
95000
|
+
* its other location-derived caches in the same breath.
|
|
95001
|
+
*/
|
|
95002
|
+
function subscribeEventMediaWriteLocator(eventBus, locator, onRing) {
|
|
95003
|
+
return eventBus.subscribe({ category: require_clip_model_registry.EventCategory.StorageLocationsChanged }, () => {
|
|
95004
|
+
locator.invalidate();
|
|
95005
|
+
onRing();
|
|
95006
|
+
});
|
|
95007
|
+
}
|
|
95008
|
+
//#endregion
|
|
94644
95009
|
//#region src/pipeline-analytics/media-relocate-engine.ts
|
|
94645
95010
|
/**
|
|
94646
95011
|
* Event-media relocation engine (entity-routing spec, Phase 4) — the
|
|
@@ -104634,7 +104999,8 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
|
|
|
104634
104999
|
require_clip_model_registry.EventCategory.SystemRestartCompleted,
|
|
104635
105000
|
require_clip_model_registry.EventCategory.SystemRestarting,
|
|
104636
105001
|
require_clip_model_registry.EventCategory.RecordingExportCompleted,
|
|
104637
|
-
require_clip_model_registry.EventCategory.BackupRunCompleted
|
|
105002
|
+
require_clip_model_registry.EventCategory.BackupRunCompleted,
|
|
105003
|
+
require_clip_model_registry.EventCategory.AlertEpisode
|
|
104638
105004
|
];
|
|
104639
105005
|
/**
|
|
104640
105006
|
* Install the addon's bus subscriptions. Identical set + relative order to the
|
|
@@ -107522,6 +107888,15 @@ Object.freeze({
|
|
|
107522
107888
|
"valueType": "boolean"
|
|
107523
107889
|
}],
|
|
107524
107890
|
"dishwasher": [
|
|
107891
|
+
{
|
|
107892
|
+
"path": "availability",
|
|
107893
|
+
"valueType": "enum",
|
|
107894
|
+
"enumValues": [
|
|
107895
|
+
"available",
|
|
107896
|
+
"offline",
|
|
107897
|
+
"description-missing"
|
|
107898
|
+
]
|
|
107899
|
+
},
|
|
107525
107900
|
{
|
|
107526
107901
|
"path": "state",
|
|
107527
107902
|
"valueType": "enum",
|
|
@@ -107534,12 +107909,35 @@ Object.freeze({
|
|
|
107534
107909
|
"unavailable"
|
|
107535
107910
|
]
|
|
107536
107911
|
},
|
|
107912
|
+
{
|
|
107913
|
+
"path": "operationState",
|
|
107914
|
+
"valueType": "enum",
|
|
107915
|
+
"enumValues": [
|
|
107916
|
+
"Inactive",
|
|
107917
|
+
"Ready",
|
|
107918
|
+
"DelayedStart",
|
|
107919
|
+
"Run",
|
|
107920
|
+
"Pause",
|
|
107921
|
+
"ActionRequired",
|
|
107922
|
+
"Finished",
|
|
107923
|
+
"Error",
|
|
107924
|
+
"Aborting"
|
|
107925
|
+
]
|
|
107926
|
+
},
|
|
107537
107927
|
{
|
|
107538
107928
|
"path": "program",
|
|
107539
107929
|
"valueType": "string"
|
|
107540
107930
|
},
|
|
107541
107931
|
{
|
|
107542
|
-
"path": "
|
|
107932
|
+
"path": "selectedProgram",
|
|
107933
|
+
"valueType": "string"
|
|
107934
|
+
},
|
|
107935
|
+
{
|
|
107936
|
+
"path": "remainingSeconds",
|
|
107937
|
+
"valueType": "number"
|
|
107938
|
+
},
|
|
107939
|
+
{
|
|
107940
|
+
"path": "delayedStartSeconds",
|
|
107543
107941
|
"valueType": "number"
|
|
107544
107942
|
},
|
|
107545
107943
|
{
|
|
@@ -107550,14 +107948,30 @@ Object.freeze({
|
|
|
107550
107948
|
"path": "doorOpen",
|
|
107551
107949
|
"valueType": "boolean"
|
|
107552
107950
|
},
|
|
107951
|
+
{
|
|
107952
|
+
"path": "doorLocked",
|
|
107953
|
+
"valueType": "boolean"
|
|
107954
|
+
},
|
|
107553
107955
|
{
|
|
107554
107956
|
"path": "remoteStartAllowed",
|
|
107555
107957
|
"valueType": "boolean"
|
|
107556
107958
|
},
|
|
107959
|
+
{
|
|
107960
|
+
"path": "remoteControlActive",
|
|
107961
|
+
"valueType": "boolean"
|
|
107962
|
+
},
|
|
107963
|
+
{
|
|
107964
|
+
"path": "localControlActive",
|
|
107965
|
+
"valueType": "boolean"
|
|
107966
|
+
},
|
|
107557
107967
|
{
|
|
107558
107968
|
"path": "powerOn",
|
|
107559
107969
|
"valueType": "boolean"
|
|
107560
107970
|
},
|
|
107971
|
+
{
|
|
107972
|
+
"path": "powerState",
|
|
107973
|
+
"valueType": "string"
|
|
107974
|
+
},
|
|
107561
107975
|
{
|
|
107562
107976
|
"path": "saltLevel",
|
|
107563
107977
|
"valueType": "enum",
|
|
@@ -113879,20 +114293,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
113879
114293
|
mediaRelocate = null;
|
|
113880
114294
|
mediaLocationStorage = null;
|
|
113881
114295
|
/**
|
|
113882
|
-
*
|
|
113883
|
-
*
|
|
113884
|
-
* `
|
|
113885
|
-
*
|
|
113886
|
-
* `enabled` flags only change via operator reconfig.
|
|
113887
|
-
*/
|
|
113888
|
-
eventMediaWriteLocationCache = null;
|
|
113889
|
-
/**
|
|
113890
|
-
* The last location this process actually picked for an `eventMedia` write.
|
|
113891
|
-
* Survives the cache invalidation a migration performs, so a refresh keeps
|
|
113892
|
-
* writing where it was writing while that stays writable — the stickiness the
|
|
113893
|
-
* class default used to provide before it was deleted (D383).
|
|
114296
|
+
* Where NEW `eventMedia` writes land. Its pick is cached per location-set
|
|
114297
|
+
* generation and dropped by the `StorageLocationsChanged` doorbell (D387) and
|
|
114298
|
+
* by `refreshStorageLocationsForMigration`, so a mode change or a repoint takes
|
|
114299
|
+
* effect without a restart.
|
|
113894
114300
|
*/
|
|
113895
|
-
|
|
114301
|
+
eventMediaWriteLocator = null;
|
|
114302
|
+
eventMediaLocationDoorbellUnsub = null;
|
|
113896
114303
|
mediaWriteGate = new MediaWriteGate();
|
|
113897
114304
|
storageMigrationLeaseId = null;
|
|
113898
114305
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
@@ -115932,33 +116339,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
115932
116339
|
}
|
|
115933
116340
|
/** One line per process: this repeats on every occupancy refresh otherwise. */
|
|
115934
116341
|
mediaOccupancyUnattributedLogged = false;
|
|
115935
|
-
|
|
115936
|
-
|
|
115937
|
-
|
|
115938
|
-
let locations;
|
|
115939
|
-
try {
|
|
115940
|
-
locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
|
|
115941
|
-
} catch (err) {
|
|
115942
|
-
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: require_clip_model_registry.errMsg(err) } });
|
|
115943
|
-
this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
|
|
115944
|
-
return DEFAULT_MEDIA_LOCATION;
|
|
115945
|
-
}
|
|
115946
|
-
const picked = pickWritableMediaLocation(locations.map((l) => ({
|
|
115947
|
-
id: l.id,
|
|
115948
|
-
mode: require_clip_model_registry.resolveLocationMode(l),
|
|
115949
|
-
headroomBytes: l.capacity?.availableBytes ?? 0
|
|
115950
|
-
})), this.lastEventMediaWritePick ?? void 0);
|
|
115951
|
-
if (picked === null) {
|
|
115952
|
-
const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
|
|
115953
|
-
logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
|
|
115954
|
-
tags: { deviceId },
|
|
115955
|
-
meta: { locationsSeen: seen }
|
|
115956
|
-
});
|
|
115957
|
-
throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
|
|
115958
|
-
}
|
|
115959
|
-
this.eventMediaWriteLocationCache = picked;
|
|
115960
|
-
this.lastEventMediaWritePick = picked;
|
|
115961
|
-
return picked;
|
|
116342
|
+
resolveEventMediaWriteLocation(api, logger, deviceId) {
|
|
116343
|
+
this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
|
|
116344
|
+
return this.eventMediaWriteLocator.resolve(logger, deviceId);
|
|
115962
116345
|
}
|
|
115963
116346
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
115964
116347
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
@@ -116059,6 +116442,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
116059
116442
|
resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
|
|
116060
116443
|
});
|
|
116061
116444
|
this.mediaStore = mediaStore;
|
|
116445
|
+
this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
|
|
116446
|
+
this.eventMediaLocationDoorbellUnsub?.();
|
|
116447
|
+
this.eventMediaLocationDoorbellUnsub = subscribeEventMediaWriteLocator(this.ctx.eventBus, this.eventMediaWriteLocator, () => this.mediaLocationStorage?.invalidateRoots());
|
|
116062
116448
|
const eventLedgerWiring = this.eventLedgerWiring(api.settingsStore);
|
|
116063
116449
|
const retrainFrames = new RetrainFrameStore({
|
|
116064
116450
|
store: api.settingsStore,
|
|
@@ -116641,6 +117027,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
116641
117027
|
coreBlocks: async () => (await api.coreBlocks.list.query({})).blocks,
|
|
116642
117028
|
nodeAddons: (nodeId) => api.nodes.getNodeAddons.query({ nodeId })
|
|
116643
117029
|
},
|
|
117030
|
+
readAlerts: () => api.alerts.list.query({ limit: 5e3 }),
|
|
116644
117031
|
getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
|
|
116645
117032
|
recognitionOutlook: ({ deviceId, trackId, className, labelKind, now }) => recognitionOutlook({
|
|
116646
117033
|
className,
|
|
@@ -118654,6 +119041,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
118654
119041
|
this.alarmPanelConvergence?.stop();
|
|
118655
119042
|
this.alarmPanelConvergence = null;
|
|
118656
119043
|
for (const unsub of this.subscriptionUnsubs) unsub();
|
|
119044
|
+
this.eventMediaLocationDoorbellUnsub?.();
|
|
119045
|
+
this.eventMediaLocationDoorbellUnsub = null;
|
|
118657
119046
|
this.subscriptionUnsubs = [];
|
|
118658
119047
|
await Promise.all([this.drainSensorHandlers(), this.motionEpisodes?.closeAll("addon-shutdown")]);
|
|
118659
119048
|
await this.settleTrackFusions();
|
|
@@ -125908,7 +126297,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
125908
126297
|
async refreshStorageLocationsForMigration(input) {
|
|
125909
126298
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
125910
126299
|
this.mediaLocationStorage?.invalidateRoots();
|
|
125911
|
-
this.
|
|
126300
|
+
this.eventMediaWriteLocator?.invalidate();
|
|
125912
126301
|
return { refreshed: true };
|
|
125913
126302
|
}
|
|
125914
126303
|
/**
|
|
@@ -126271,7 +126660,24 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
126271
126660
|
async startStorageMigrationMove(input) {
|
|
126272
126661
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
126273
126662
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
126274
|
-
return { jobId: this.
|
|
126663
|
+
return { jobId: await this.startMediaMover(input) };
|
|
126664
|
+
}
|
|
126665
|
+
/**
|
|
126666
|
+
* Start a media move only while no single-location repoint holds an overlapping
|
|
126667
|
+
* physical root (the storage orchestrator owns that claim). A shared recordings
|
|
126668
|
+
* root is the case: a repoint of another class there must not run beside us.
|
|
126669
|
+
*/
|
|
126670
|
+
startMediaMover(input) {
|
|
126671
|
+
const engine = this.mediaRelocate;
|
|
126672
|
+
if (!engine) throw new Error("media relocation unavailable");
|
|
126673
|
+
return require_clip_model_registry.startMoverUnderClearance({
|
|
126674
|
+
clearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
|
|
126675
|
+
start: () => engine.start(input),
|
|
126676
|
+
cancel: (jobId) => {
|
|
126677
|
+
engine.cancel(jobId);
|
|
126678
|
+
},
|
|
126679
|
+
locationIds: [input.fromLocationId ?? "*", input.toLocationId]
|
|
126680
|
+
});
|
|
126275
126681
|
}
|
|
126276
126682
|
async getStorageMigrationMoveStatus(input) {
|
|
126277
126683
|
return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
|
|
@@ -126289,7 +126695,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
126289
126695
|
*/
|
|
126290
126696
|
async relocateMedia(input) {
|
|
126291
126697
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
126292
|
-
const jobId = this.
|
|
126698
|
+
const jobId = await this.startMediaMover(input);
|
|
126293
126699
|
this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
|
|
126294
126700
|
jobId,
|
|
126295
126701
|
mode: input.mode ?? "move",
|