@camstack/addon-post-analysis 1.2.277 → 1.2.279
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/{dist-MPbaDp6L.js → dist-BUtk8EU2.js} +105 -4
- package/dist/{dist-D8G7jV1C.mjs → dist-D0jvZ8dq.mjs} +105 -4
- 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-CHNFeWfB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CxprcSXe.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BwQ85CFd.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-B62fofgN.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-CN-6SOYG.mjs → hostInit-Bhp_DANc.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +310 -120
- package/dist/pipeline-analytics/index.mjs +310 -120
- 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-B7oKeWXX.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-BZetaopl.mjs +0 -26
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.253",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.203",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BUtk8EU2.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -8219,6 +8219,99 @@ var NcAlarmLiveReadLog = class {
|
|
|
8219
8219
|
}
|
|
8220
8220
|
};
|
|
8221
8221
|
//#endregion
|
|
8222
|
+
//#region src/notification-center/alerts/alert-episode.ts
|
|
8223
|
+
function decideAlert(input) {
|
|
8224
|
+
const previous = input.previous;
|
|
8225
|
+
if (previous === void 0) return input.alerting ? { kind: "raise" } : { kind: "seeded" };
|
|
8226
|
+
if (previous.alerting !== input.alerting) {
|
|
8227
|
+
if (input.alerting) return { kind: "raise" };
|
|
8228
|
+
if (previous.lastNotifiedAt <= 0) return { kind: "silent" };
|
|
8229
|
+
return input.notifyRecovery ? { kind: "recover" } : { kind: "silent" };
|
|
8230
|
+
}
|
|
8231
|
+
if (!input.alerting) return { kind: "silent" };
|
|
8232
|
+
if (input.renotifyMs <= 0) return { kind: "silent" };
|
|
8233
|
+
if (previous.lastNotifiedAt <= 0) return { kind: "silent" };
|
|
8234
|
+
if (input.nowMs - previous.lastNotifiedAt < input.renotifyMs) return { kind: "silent" };
|
|
8235
|
+
return {
|
|
8236
|
+
kind: "remind",
|
|
8237
|
+
reminderIndex: previous.reminders + 1
|
|
8238
|
+
};
|
|
8239
|
+
}
|
|
8240
|
+
/**
|
|
8241
|
+
* The row that survives a verdict.
|
|
8242
|
+
*
|
|
8243
|
+
* Split from {@link decideAlert} rather than returned with it because the
|
|
8244
|
+
* caller must be able to persist a row for a verdict it did NOT deliver — the
|
|
8245
|
+
* suppressed recovery is the whole reason (rule 3), and so is the silent seed.
|
|
8246
|
+
*/
|
|
8247
|
+
function nextAlertRow(subject, previous, alerting, verdict, nowMs) {
|
|
8248
|
+
switch (verdict.kind) {
|
|
8249
|
+
case "seeded": return {
|
|
8250
|
+
subject,
|
|
8251
|
+
alerting,
|
|
8252
|
+
since: nowMs,
|
|
8253
|
+
lastNotifiedAt: 0,
|
|
8254
|
+
reminders: 0
|
|
8255
|
+
};
|
|
8256
|
+
case "raise":
|
|
8257
|
+
case "recover": return {
|
|
8258
|
+
subject,
|
|
8259
|
+
alerting,
|
|
8260
|
+
since: nowMs,
|
|
8261
|
+
lastNotifiedAt: nowMs,
|
|
8262
|
+
reminders: 0
|
|
8263
|
+
};
|
|
8264
|
+
case "remind": return {
|
|
8265
|
+
subject,
|
|
8266
|
+
alerting,
|
|
8267
|
+
since: previous?.since ?? nowMs,
|
|
8268
|
+
lastNotifiedAt: nowMs,
|
|
8269
|
+
reminders: verdict.reminderIndex
|
|
8270
|
+
};
|
|
8271
|
+
case "silent":
|
|
8272
|
+
if (previous === void 0) return {
|
|
8273
|
+
subject,
|
|
8274
|
+
alerting,
|
|
8275
|
+
since: nowMs,
|
|
8276
|
+
lastNotifiedAt: 0,
|
|
8277
|
+
reminders: 0
|
|
8278
|
+
};
|
|
8279
|
+
if (previous.alerting !== alerting) return {
|
|
8280
|
+
subject,
|
|
8281
|
+
alerting,
|
|
8282
|
+
since: nowMs,
|
|
8283
|
+
lastNotifiedAt: previous.lastNotifiedAt,
|
|
8284
|
+
reminders: 0
|
|
8285
|
+
};
|
|
8286
|
+
return previous;
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
/**
|
|
8290
|
+
* The `recordId` a notification for this episode carries — and therefore, via
|
|
8291
|
+
* the outbox's fallback, its `dedupRef`.
|
|
8292
|
+
*
|
|
8293
|
+
* `reminderIndex: 0` is the opening notification (and the recovery, which
|
|
8294
|
+
* opens the `normal` episode). Every reminder appends `:r<n>`, which is what
|
|
8295
|
+
* stops the 7-day outbox dedup swallowing it.
|
|
8296
|
+
*/
|
|
8297
|
+
function alertRecordId(subject, since, reminderIndex) {
|
|
8298
|
+
const base = `${subject}:${since}`;
|
|
8299
|
+
return reminderIndex > 0 ? `${base}:r${reminderIndex}` : base;
|
|
8300
|
+
}
|
|
8301
|
+
/**
|
|
8302
|
+
* The device a subject is about: `battery:3602` → 3602, and
|
|
8303
|
+
* `consumable:4385:sensor` → 4385. The id is the SECOND segment, never the
|
|
8304
|
+
* last — reading the last one turned every consumable subject into "no
|
|
8305
|
+
* device", which lost the `deviceId` tag on its log lines and the indexed
|
|
8306
|
+
* column on its durable row. `undefined` for a subject that names no device.
|
|
8307
|
+
*/
|
|
8308
|
+
function alertSubjectDeviceId(subject) {
|
|
8309
|
+
const raw = subject.split(":")[1];
|
|
8310
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
8311
|
+
const parsed = Number(raw);
|
|
8312
|
+
return Number.isInteger(parsed) ? parsed : void 0;
|
|
8313
|
+
}
|
|
8314
|
+
//#endregion
|
|
8222
8315
|
//#region src/notification-center/alerts/alert-store.ts
|
|
8223
8316
|
/**
|
|
8224
8317
|
* @durable class=ledger owner=notification-center
|
|
@@ -8276,9 +8369,9 @@ var NC_ALERTS_INDEXES = [{
|
|
|
8276
8369
|
name: "idx_nc_alerts_device",
|
|
8277
8370
|
columns: ["deviceId"]
|
|
8278
8371
|
}];
|
|
8372
|
+
/** The device a subject is about — see {@link alertSubjectDeviceId}. */
|
|
8279
8373
|
function deviceIdOf(subject) {
|
|
8280
|
-
|
|
8281
|
-
return Number.isInteger(parsed) ? parsed : void 0;
|
|
8374
|
+
return alertSubjectDeviceId(subject);
|
|
8282
8375
|
}
|
|
8283
8376
|
var NC_ALERTS_SPEC = {
|
|
8284
8377
|
collection: NC_ALERTS_COLLECTION,
|
|
@@ -8338,7 +8431,8 @@ var NcAlertStore = class {
|
|
|
8338
8431
|
put: (row) => {
|
|
8339
8432
|
this.ledger.put(row).catch(() => void 0);
|
|
8340
8433
|
},
|
|
8341
|
-
all: () => this.ledger.snapshot()
|
|
8434
|
+
all: () => this.ledger.snapshot(),
|
|
8435
|
+
remove: (subject) => this.ledger.forget(subject)
|
|
8342
8436
|
};
|
|
8343
8437
|
}
|
|
8344
8438
|
};
|
|
@@ -9949,6 +10043,10 @@ function systemEventExclusion(condition, subject) {
|
|
|
9949
10043
|
if (condition.deviceIds !== void 0 && applies("deviceIds")) {
|
|
9950
10044
|
if (subject.deviceId === void 0 || !condition.deviceIds.includes(subject.deviceId)) return "deviceIds";
|
|
9951
10045
|
}
|
|
10046
|
+
const excluded = condition.excludeDeviceIds;
|
|
10047
|
+
if (excluded !== void 0 && applies("deviceIds")) {
|
|
10048
|
+
if (subject.deviceId !== void 0 && excluded.includes(subject.deviceId)) return "excludeDeviceIds";
|
|
10049
|
+
}
|
|
9952
10050
|
if (condition.deviceTypes !== void 0 && applies("deviceTypes")) {
|
|
9953
10051
|
if (subject.deviceType === void 0 || !condition.deviceTypes.includes(subject.deviceType)) return "deviceTypes";
|
|
9954
10052
|
}
|
|
@@ -11511,85 +11609,6 @@ function deliveryForKind(kind) {
|
|
|
11511
11609
|
return kind;
|
|
11512
11610
|
}
|
|
11513
11611
|
//#endregion
|
|
11514
|
-
//#region src/notification-center/alerts/alert-episode.ts
|
|
11515
|
-
function decideAlert(input) {
|
|
11516
|
-
const previous = input.previous;
|
|
11517
|
-
if (previous === void 0) return input.alerting ? { kind: "raise" } : { kind: "seeded" };
|
|
11518
|
-
if (previous.alerting !== input.alerting) {
|
|
11519
|
-
if (input.alerting) return { kind: "raise" };
|
|
11520
|
-
return input.notifyRecovery ? { kind: "recover" } : { kind: "silent" };
|
|
11521
|
-
}
|
|
11522
|
-
if (!input.alerting) return { kind: "silent" };
|
|
11523
|
-
if (input.renotifyMs <= 0) return { kind: "silent" };
|
|
11524
|
-
if (previous.lastNotifiedAt <= 0) return { kind: "silent" };
|
|
11525
|
-
if (input.nowMs - previous.lastNotifiedAt < input.renotifyMs) return { kind: "silent" };
|
|
11526
|
-
return {
|
|
11527
|
-
kind: "remind",
|
|
11528
|
-
reminderIndex: previous.reminders + 1
|
|
11529
|
-
};
|
|
11530
|
-
}
|
|
11531
|
-
/**
|
|
11532
|
-
* The row that survives a verdict.
|
|
11533
|
-
*
|
|
11534
|
-
* Split from {@link decideAlert} rather than returned with it because the
|
|
11535
|
-
* caller must be able to persist a row for a verdict it did NOT deliver — the
|
|
11536
|
-
* suppressed recovery is the whole reason (rule 3), and so is the silent seed.
|
|
11537
|
-
*/
|
|
11538
|
-
function nextAlertRow(subject, previous, alerting, verdict, nowMs) {
|
|
11539
|
-
switch (verdict.kind) {
|
|
11540
|
-
case "seeded": return {
|
|
11541
|
-
subject,
|
|
11542
|
-
alerting,
|
|
11543
|
-
since: nowMs,
|
|
11544
|
-
lastNotifiedAt: 0,
|
|
11545
|
-
reminders: 0
|
|
11546
|
-
};
|
|
11547
|
-
case "raise":
|
|
11548
|
-
case "recover": return {
|
|
11549
|
-
subject,
|
|
11550
|
-
alerting,
|
|
11551
|
-
since: nowMs,
|
|
11552
|
-
lastNotifiedAt: nowMs,
|
|
11553
|
-
reminders: 0
|
|
11554
|
-
};
|
|
11555
|
-
case "remind": return {
|
|
11556
|
-
subject,
|
|
11557
|
-
alerting,
|
|
11558
|
-
since: previous?.since ?? nowMs,
|
|
11559
|
-
lastNotifiedAt: nowMs,
|
|
11560
|
-
reminders: verdict.reminderIndex
|
|
11561
|
-
};
|
|
11562
|
-
case "silent":
|
|
11563
|
-
if (previous === void 0) return {
|
|
11564
|
-
subject,
|
|
11565
|
-
alerting,
|
|
11566
|
-
since: nowMs,
|
|
11567
|
-
lastNotifiedAt: 0,
|
|
11568
|
-
reminders: 0
|
|
11569
|
-
};
|
|
11570
|
-
if (previous.alerting !== alerting) return {
|
|
11571
|
-
subject,
|
|
11572
|
-
alerting,
|
|
11573
|
-
since: nowMs,
|
|
11574
|
-
lastNotifiedAt: previous.lastNotifiedAt,
|
|
11575
|
-
reminders: 0
|
|
11576
|
-
};
|
|
11577
|
-
return previous;
|
|
11578
|
-
}
|
|
11579
|
-
}
|
|
11580
|
-
/**
|
|
11581
|
-
* The `recordId` a notification for this episode carries — and therefore, via
|
|
11582
|
-
* the outbox's fallback, its `dedupRef`.
|
|
11583
|
-
*
|
|
11584
|
-
* `reminderIndex: 0` is the opening notification (and the recovery, which
|
|
11585
|
-
* opens the `normal` episode). Every reminder appends `:r<n>`, which is what
|
|
11586
|
-
* stops the 7-day outbox dedup swallowing it.
|
|
11587
|
-
*/
|
|
11588
|
-
function alertRecordId(subject, since, reminderIndex) {
|
|
11589
|
-
const base = `${subject}:${since}`;
|
|
11590
|
-
return reminderIndex > 0 ? `${base}:r${reminderIndex}` : base;
|
|
11591
|
-
}
|
|
11592
|
-
//#endregion
|
|
11593
11612
|
//#region src/notification-center/alerts/battery-band.ts
|
|
11594
11613
|
/**
|
|
11595
11614
|
* The stand-in a provider writes for a binary NORMAL. Anything below it, on a
|
|
@@ -11651,6 +11670,18 @@ function deriveBatteryBand(input) {
|
|
|
11651
11670
|
* that is already a FAULT — see `alert-episode.ts` rule 2 for why that is D141
|
|
11652
11671
|
* rather than a loophole.
|
|
11653
11672
|
*/
|
|
11673
|
+
/** One hour — the longest a pending raise waits before it is offered again. */
|
|
11674
|
+
var PENDING_CADENCE_MAX_MS = 36e5;
|
|
11675
|
+
/**
|
|
11676
|
+
* How often a pending raise is offered again with the rules unchanged: the
|
|
11677
|
+
* re-notify interval, capped at one hour. The cap matters because the
|
|
11678
|
+
* cadence counts from the raise: with a 12 h interval, a rule whose schedule
|
|
11679
|
+
* opens for one hour a day could keep being missed. Reminders off ("say it
|
|
11680
|
+
* once") still has to say it once, hourly.
|
|
11681
|
+
*/
|
|
11682
|
+
function pendingCadenceMs(settings) {
|
|
11683
|
+
return settings.renotifyMinutes > 0 ? Math.min(settings.renotifyMinutes * 6e4, PENDING_CADENCE_MAX_MS) : PENDING_CADENCE_MAX_MS;
|
|
11684
|
+
}
|
|
11654
11685
|
var NcDeviceAlerts = class {
|
|
11655
11686
|
deps;
|
|
11656
11687
|
now;
|
|
@@ -11664,6 +11695,13 @@ var NcDeviceAlerts = class {
|
|
|
11664
11695
|
lastReading = /* @__PURE__ */ new Map();
|
|
11665
11696
|
/** The last reading per consumable SUBJECT, for the reminder sweep. */
|
|
11666
11697
|
lastConsumable = /* @__PURE__ */ new Map();
|
|
11698
|
+
/**
|
|
11699
|
+
* Subject → WHEN its PENDING raise was last offered, and to which
|
|
11700
|
+
* {@link NcDeviceAlertsDeps.rulesVersion}. RAM only, on purpose: after a
|
|
11701
|
+
* restart every pending raise is offered once more, which is bounded by the
|
|
11702
|
+
* number of open episodes.
|
|
11703
|
+
*/
|
|
11704
|
+
offeredAt = /* @__PURE__ */ new Map();
|
|
11667
11705
|
constructor(deps) {
|
|
11668
11706
|
this.deps = deps;
|
|
11669
11707
|
this.now = deps.now ?? Date.now;
|
|
@@ -11749,34 +11787,94 @@ var NcDeviceAlerts = class {
|
|
|
11749
11787
|
*/
|
|
11750
11788
|
tick() {
|
|
11751
11789
|
const settings = this.deps.settings();
|
|
11790
|
+
this.retryPending(settings);
|
|
11752
11791
|
if (settings.renotifyMinutes <= 0) return;
|
|
11753
11792
|
for (const row of this.deps.rows.all()) {
|
|
11754
|
-
if (!row.alerting) continue;
|
|
11755
|
-
const
|
|
11756
|
-
if (
|
|
11757
|
-
|
|
11758
|
-
facts,
|
|
11759
|
-
deviceId: consumable.deviceId,
|
|
11760
|
-
band: "low",
|
|
11761
|
-
item: consumable.item,
|
|
11762
|
-
identity: this.deps.lookup(consumable.deviceId)
|
|
11763
|
-
}));
|
|
11764
|
-
continue;
|
|
11765
|
-
}
|
|
11766
|
-
const restatement = this.lastReading.get(row.subject);
|
|
11767
|
-
if (restatement === void 0) continue;
|
|
11768
|
-
this.apply(row.subject, true, settings, (facts) => incomingFromBatteryBand({
|
|
11769
|
-
facts,
|
|
11770
|
-
deviceId: restatement.deviceId,
|
|
11771
|
-
band: "low",
|
|
11772
|
-
reading: restatement.reading,
|
|
11773
|
-
identity: this.deps.lookup(restatement.deviceId)
|
|
11774
|
-
}));
|
|
11793
|
+
if (!row.alerting || isPending(row)) continue;
|
|
11794
|
+
const build = this.restatementOf(row.subject);
|
|
11795
|
+
if (build === null) continue;
|
|
11796
|
+
this.apply(row.subject, true, settings, build);
|
|
11775
11797
|
}
|
|
11776
11798
|
}
|
|
11777
|
-
/**
|
|
11778
|
-
|
|
11799
|
+
/**
|
|
11800
|
+
* Every device that holds an alert row — what the deleted-device reconcile
|
|
11801
|
+
* must look at besides the devices a rule names (a battery device no rule
|
|
11802
|
+
* names still has a row).
|
|
11803
|
+
*/
|
|
11804
|
+
deviceIds() {
|
|
11805
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11806
|
+
for (const row of this.deps.rows.all()) {
|
|
11807
|
+
const id = alertSubjectDeviceId(row.subject);
|
|
11808
|
+
if (id !== void 0) ids.add(id);
|
|
11809
|
+
}
|
|
11810
|
+
return [...ids];
|
|
11811
|
+
}
|
|
11812
|
+
/**
|
|
11813
|
+
* A DELETED device: drop its battery row and every consumable row, durable
|
|
11814
|
+
* and in RAM, so nothing restates it and a re-adoption starts a fresh
|
|
11815
|
+
* episode. Called only by the deleted-device prune (D633), after the second
|
|
11816
|
+
* read agreed the device is gone. Returns how many rows went.
|
|
11817
|
+
*/
|
|
11818
|
+
async forget(deviceId) {
|
|
11819
|
+
const subjects = this.deps.rows.all().map((row) => row.subject).filter((subject) => alertSubjectDeviceId(subject) === deviceId);
|
|
11820
|
+
for (const subject of subjects) {
|
|
11821
|
+
this.lastReading.delete(subject);
|
|
11822
|
+
this.lastConsumable.delete(subject);
|
|
11823
|
+
this.offeredAt.delete(subject);
|
|
11824
|
+
await this.deps.rows.remove(subject);
|
|
11825
|
+
}
|
|
11779
11826
|
this.lastReading.delete(`battery:${deviceId}`);
|
|
11827
|
+
return subjects.length;
|
|
11828
|
+
}
|
|
11829
|
+
/**
|
|
11830
|
+
* Offer every PENDING raise once more — when the rule set changed since its
|
|
11831
|
+
* last offer, OR when {@link pendingCadenceMs} has passed since it. The
|
|
11832
|
+
* cadence is what delivers a raise refused only by a rule's SCHEDULE (or any
|
|
11833
|
+
* other time-dependent gate): the rules do not change when the window
|
|
11834
|
+
* opens. The restatement is RAM: a row whose reading has not arrived since
|
|
11835
|
+
* the restart waits for it (the slice arrives on its own).
|
|
11836
|
+
*/
|
|
11837
|
+
retryPending(settings) {
|
|
11838
|
+
const version = this.deps.rulesVersion();
|
|
11839
|
+
const nowMs = this.now();
|
|
11840
|
+
const cadenceMs = pendingCadenceMs(settings);
|
|
11841
|
+
for (const row of this.deps.rows.all()) {
|
|
11842
|
+
if (!isPending(row)) continue;
|
|
11843
|
+
const last = this.offeredAt.get(row.subject);
|
|
11844
|
+
const changed = last === void 0 || last.version !== version;
|
|
11845
|
+
if (!changed && nowMs - last.at < cadenceMs) continue;
|
|
11846
|
+
const build = this.restatementOf(row.subject);
|
|
11847
|
+
if (build === null) continue;
|
|
11848
|
+
this.logger()?.debug("device alert re-offered — no rule has matched it yet", {
|
|
11849
|
+
...tagsOf(row.subject),
|
|
11850
|
+
meta: {
|
|
11851
|
+
subject: row.subject,
|
|
11852
|
+
since: row.since,
|
|
11853
|
+
reason: changed ? "rules" : "cadence"
|
|
11854
|
+
}
|
|
11855
|
+
});
|
|
11856
|
+
this.speak(row.subject, row, { kind: "raise" }, build);
|
|
11857
|
+
}
|
|
11858
|
+
}
|
|
11859
|
+
/** How to re-word a subject's alert from the last reading, or `null`. */
|
|
11860
|
+
restatementOf(subject) {
|
|
11861
|
+
const consumable = this.lastConsumable.get(subject);
|
|
11862
|
+
if (consumable !== void 0) return (facts) => incomingFromConsumableBand({
|
|
11863
|
+
facts,
|
|
11864
|
+
deviceId: consumable.deviceId,
|
|
11865
|
+
band: "low",
|
|
11866
|
+
item: consumable.item,
|
|
11867
|
+
identity: this.deps.lookup(consumable.deviceId)
|
|
11868
|
+
});
|
|
11869
|
+
const battery = this.lastReading.get(subject);
|
|
11870
|
+
if (battery !== void 0) return (facts) => incomingFromBatteryBand({
|
|
11871
|
+
facts,
|
|
11872
|
+
deviceId: battery.deviceId,
|
|
11873
|
+
band: "low",
|
|
11874
|
+
reading: battery.reading,
|
|
11875
|
+
identity: this.deps.lookup(battery.deviceId)
|
|
11876
|
+
});
|
|
11877
|
+
return null;
|
|
11780
11878
|
}
|
|
11781
11879
|
/**
|
|
11782
11880
|
* The shared half: decide, persist, and speak only when the verdict says to.
|
|
@@ -11801,16 +11899,63 @@ var NcDeviceAlerts = class {
|
|
|
11801
11899
|
this.report(subject, verdict, alerting);
|
|
11802
11900
|
return;
|
|
11803
11901
|
}
|
|
11902
|
+
this.speak(subject, next, verdict, (facts) => build(facts, alerting));
|
|
11903
|
+
}
|
|
11904
|
+
/**
|
|
11905
|
+
* Emit one verdict through the door, and — for a RAISE — hear whether any
|
|
11906
|
+
* rule matched it. A raise no rule matched is put back to PENDING
|
|
11907
|
+
* (`lastNotifiedAt: 0`), unless the row moved meanwhile; it is re-offered
|
|
11908
|
+
* by {@link retryPending} when the rules change. A reminder or a recovery is
|
|
11909
|
+
* not re-offered: the episode was already announced (or is over).
|
|
11910
|
+
*/
|
|
11911
|
+
speak(subject, row, verdict, build) {
|
|
11912
|
+
const nowMs = this.now();
|
|
11804
11913
|
const reminderIndex = verdict.kind === "remind" ? verdict.reminderIndex : 0;
|
|
11805
|
-
|
|
11806
|
-
eventId: alertRecordId(subject,
|
|
11914
|
+
const event = build({
|
|
11915
|
+
eventId: alertRecordId(subject, row.since, reminderIndex),
|
|
11807
11916
|
timestamp: nowMs,
|
|
11808
11917
|
category: require_dist.EventCategory.DeviceStateChanged,
|
|
11809
11918
|
data: {
|
|
11810
11919
|
alertVerdict: verdict.kind,
|
|
11811
11920
|
reminderIndex
|
|
11812
11921
|
}
|
|
11813
|
-
}
|
|
11922
|
+
});
|
|
11923
|
+
if (verdict.kind !== "raise") {
|
|
11924
|
+
this.deps.emit(event).catch(() => void 0);
|
|
11925
|
+
return;
|
|
11926
|
+
}
|
|
11927
|
+
this.offeredAt.set(subject, {
|
|
11928
|
+
version: this.deps.rulesVersion(),
|
|
11929
|
+
at: nowMs
|
|
11930
|
+
});
|
|
11931
|
+
this.deps.emit(event).then((heard) => this.settleRaise(subject, row.since, heard, nowMs)).catch(() => void 0);
|
|
11932
|
+
}
|
|
11933
|
+
/** The door answered a raise. */
|
|
11934
|
+
settleRaise(subject, since, heard, nowMs) {
|
|
11935
|
+
const current = this.deps.rows.get(subject);
|
|
11936
|
+
if (current === void 0 || !current.alerting || current.since !== since) return;
|
|
11937
|
+
if (heard) {
|
|
11938
|
+
this.offeredAt.delete(subject);
|
|
11939
|
+
if (current.lastNotifiedAt !== nowMs) this.deps.rows.put({
|
|
11940
|
+
...current,
|
|
11941
|
+
lastNotifiedAt: nowMs
|
|
11942
|
+
});
|
|
11943
|
+
return;
|
|
11944
|
+
}
|
|
11945
|
+
if (current.lastNotifiedAt !== 0) this.deps.rows.put({
|
|
11946
|
+
...current,
|
|
11947
|
+
lastNotifiedAt: 0
|
|
11948
|
+
});
|
|
11949
|
+
this.logger()?.info("device alert matched NO rule — kept pending, offered again on a rule change or within the hour", {
|
|
11950
|
+
...tagsOf(subject),
|
|
11951
|
+
meta: {
|
|
11952
|
+
subject,
|
|
11953
|
+
since
|
|
11954
|
+
}
|
|
11955
|
+
});
|
|
11956
|
+
}
|
|
11957
|
+
logger() {
|
|
11958
|
+
return this.deps.logger;
|
|
11814
11959
|
}
|
|
11815
11960
|
/**
|
|
11816
11961
|
* A dropped branch is never silent. The suppressed recovery in particular:
|
|
@@ -11820,7 +11965,7 @@ var NcDeviceAlerts = class {
|
|
|
11820
11965
|
report(subject, verdict, alerting) {
|
|
11821
11966
|
const logger = this.deps.logger;
|
|
11822
11967
|
if (logger === void 0) return;
|
|
11823
|
-
const deviceId =
|
|
11968
|
+
const deviceId = alertSubjectDeviceId(subject);
|
|
11824
11969
|
const tags = deviceId !== void 0 ? { deviceId } : {};
|
|
11825
11970
|
if (verdict.kind === "silent" && !alerting) {
|
|
11826
11971
|
logger.debug("device alert cleared without a notification", {
|
|
@@ -11844,11 +11989,16 @@ var NcDeviceAlerts = class {
|
|
|
11844
11989
|
function speaks(verdict) {
|
|
11845
11990
|
return verdict.kind === "raise" || verdict.kind === "recover" || verdict.kind === "remind";
|
|
11846
11991
|
}
|
|
11847
|
-
/**
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11992
|
+
/**
|
|
11993
|
+
* A raise no rule matched: alerting, and never announced. A row in the
|
|
11994
|
+
* alerting state is otherwise always stamped, because it was raised.
|
|
11995
|
+
*/
|
|
11996
|
+
function isPending(row) {
|
|
11997
|
+
return row.alerting && row.lastNotifiedAt <= 0;
|
|
11998
|
+
}
|
|
11999
|
+
function tagsOf(subject) {
|
|
12000
|
+
const deviceId = alertSubjectDeviceId(subject);
|
|
12001
|
+
return deviceId !== void 0 ? { tags: { deviceId } } : {};
|
|
11852
12002
|
}
|
|
11853
12003
|
//#endregion
|
|
11854
12004
|
//#region src/notification-center/cap-caller.ts
|
|
@@ -19084,6 +19234,7 @@ var NcDeletedSubjectPruner = class {
|
|
|
19084
19234
|
return this.serialise(async () => {
|
|
19085
19235
|
if (!this.ready("event", deviceId)) return;
|
|
19086
19236
|
if (!await this.confirmDeleted(deviceId, "event")) return;
|
|
19237
|
+
await this.forgetAlerts(deviceId, "event");
|
|
19087
19238
|
const deleted = new Set([deviceId]);
|
|
19088
19239
|
await this.apply(planDevicePrune(this.snapshot(), deleted, this.guard()), "event", (fresh) => planDevicePrune(fresh, deleted, this.guard()));
|
|
19089
19240
|
}, deviceId);
|
|
@@ -19093,15 +19244,17 @@ var NcDeletedSubjectPruner = class {
|
|
|
19093
19244
|
return this.serialise(async () => {
|
|
19094
19245
|
if (!this.ready("reconcile")) return;
|
|
19095
19246
|
const directory = this.deps.directory;
|
|
19096
|
-
const
|
|
19247
|
+
const alerting = new Set(this.deps.alerts?.deviceIds() ?? []);
|
|
19248
|
+
const candidates = [...new Set([...referencedDeviceIds(this.snapshot()), ...alerting])].filter((id) => directory.absentFromLastTwoAnswers(id));
|
|
19097
19249
|
if (candidates.length === 0) return;
|
|
19098
19250
|
const first = planDevicePrune(this.snapshot(), new Set(candidates), this.guard());
|
|
19099
|
-
|
|
19100
|
-
|
|
19251
|
+
const subjects = [...new Set([...[...planSubjects(first)].map(Number), ...candidates.filter((id) => alerting.has(id))])];
|
|
19252
|
+
if (subjects.length === 0) return;
|
|
19101
19253
|
if (!this.withinBound("device", subjects.map(String))) return;
|
|
19102
19254
|
const confirmed = /* @__PURE__ */ new Set();
|
|
19103
19255
|
for (const id of subjects) if (await this.confirmDeleted(id, "reconcile")) confirmed.add(id);
|
|
19104
19256
|
if (confirmed.size === 0) return;
|
|
19257
|
+
for (const id of confirmed) if (alerting.has(id)) await this.forgetAlerts(id, "reconcile");
|
|
19105
19258
|
await this.apply(planDevicePrune(this.snapshot(), confirmed, this.guard()), "reconcile", (fresh) => planDevicePrune(fresh, confirmed, this.guard()));
|
|
19106
19259
|
});
|
|
19107
19260
|
}
|
|
@@ -19179,6 +19332,33 @@ var NcDeletedSubjectPruner = class {
|
|
|
19179
19332
|
return false;
|
|
19180
19333
|
}
|
|
19181
19334
|
}
|
|
19335
|
+
/**
|
|
19336
|
+
* Drop a CONFIRMED-deleted device's alert rows. A failure is logged and
|
|
19337
|
+
* retried by the next reconcile, which still sees the row.
|
|
19338
|
+
*/
|
|
19339
|
+
async forgetAlerts(deviceId, source) {
|
|
19340
|
+
const alerts = this.deps.alerts;
|
|
19341
|
+
if (alerts === void 0) return;
|
|
19342
|
+
try {
|
|
19343
|
+
const forgotten = await alerts.forget(deviceId);
|
|
19344
|
+
if (forgotten === 0) return;
|
|
19345
|
+
this.deps.logger.info("deleted device alerts forgotten", {
|
|
19346
|
+
tags: { deviceId },
|
|
19347
|
+
meta: {
|
|
19348
|
+
source,
|
|
19349
|
+
rows: forgotten
|
|
19350
|
+
}
|
|
19351
|
+
});
|
|
19352
|
+
} catch (err) {
|
|
19353
|
+
this.deps.logger.warn("deleted device alerts NOT forgotten — retried next tick", {
|
|
19354
|
+
tags: { deviceId },
|
|
19355
|
+
meta: {
|
|
19356
|
+
source,
|
|
19357
|
+
error: String(err)
|
|
19358
|
+
}
|
|
19359
|
+
});
|
|
19360
|
+
}
|
|
19361
|
+
}
|
|
19182
19362
|
withinBound(subjectKind, subjects) {
|
|
19183
19363
|
if (subjects.length <= 16) {
|
|
19184
19364
|
this.lastAbort.delete(subjectKind);
|
|
@@ -28268,9 +28448,10 @@ var NotificationCenter = class NotificationCenter {
|
|
|
28268
28448
|
logger: this.logger.child("device-alerts")
|
|
28269
28449
|
});
|
|
28270
28450
|
this.deviceAlerts = new NcDeviceAlerts({
|
|
28271
|
-
emit: (event) => {
|
|
28272
|
-
this.
|
|
28451
|
+
emit: async (event) => {
|
|
28452
|
+
return (await this.consumeEventAndReport(event)).outcomes.some((outcome) => outcome.matched);
|
|
28273
28453
|
},
|
|
28454
|
+
rulesVersion: () => this.rulesFingerprint(),
|
|
28274
28455
|
settings: () => this.deviceAlertSettings(),
|
|
28275
28456
|
lookup: (deviceId) => this.deviceDirectory.get(deviceId),
|
|
28276
28457
|
rows: this.alertStore.port(),
|
|
@@ -28372,6 +28553,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
28372
28553
|
summary: this.summaryRules,
|
|
28373
28554
|
mutes: this.deviceMutes,
|
|
28374
28555
|
alarm: () => this.alarmPanel,
|
|
28556
|
+
alerts: this.deviceAlerts,
|
|
28375
28557
|
directory: this.deviceDirectory,
|
|
28376
28558
|
isDeviceDeleted: deps.isDeviceDeleted,
|
|
28377
28559
|
targetCatalog: () => this.dispatcher.catalogSnapshot()
|
|
@@ -31077,6 +31259,14 @@ var NotificationCenter = class NotificationCenter {
|
|
|
31077
31259
|
* wiring that silently disabled the feature would be indistinguishable from
|
|
31078
31260
|
* a hub with no low batteries on it.
|
|
31079
31261
|
*/
|
|
31262
|
+
/**
|
|
31263
|
+
* Which rules exist, and at which revision — the question "did anything
|
|
31264
|
+
* change that could make an undelivered alert deliverable now?". Cheap: the
|
|
31265
|
+
* rule mirror is in RAM and holds tens of rows.
|
|
31266
|
+
*/
|
|
31267
|
+
rulesFingerprint() {
|
|
31268
|
+
return this.rules.list().map((rule) => `${rule.id}@${String(rule.updatedAt)}`).join("|");
|
|
31269
|
+
}
|
|
31080
31270
|
deviceAlertSettings() {
|
|
31081
31271
|
return this.deps.readDeviceAlertSettings?.() ?? {
|
|
31082
31272
|
batteryLowPercent: NC_DEVICE_ALERT_FALLBACK.batteryLowPercent,
|