@camstack/addon-post-analysis 1.2.281 → 1.2.283

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.
@@ -5391,7 +5391,7 @@ var ZodIssueCode = {
5391
5391
  var ZodFirstPartyTypeKind;
5392
5392
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5393
5393
  //#endregion
5394
- //#region ../types/dist/sleep-D_4W7xuQ.mjs
5394
+ //#region ../types/dist/sleep-DU7mu4wb.mjs
5395
5395
  /**
5396
5396
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5397
5397
  * window to float samples (D455).
@@ -17956,6 +17956,9 @@ var NcSystemEventKindSchema = _enum([
17956
17956
  "device-battery-normal",
17957
17957
  "device-consumable-low",
17958
17958
  "device-consumable-normal",
17959
+ "device-tank-low",
17960
+ "device-tank-full",
17961
+ "device-tank-normal",
17959
17962
  "stream-online",
17960
17963
  "stream-offline",
17961
17964
  "node-online",
@@ -18016,6 +18019,18 @@ var NC_ALARM_SYSTEM_EVENT_KINDS = [
18016
18019
  "alarm-disarmed"
18017
18020
  ];
18018
18021
  /**
18022
+ * The two kinds a RULE's consumable threshold speaks about (D642). Named once:
18023
+ * the filter scoping (`systemEventFilterApplies`), the hub's threshold census
18024
+ * and both editors read it.
18025
+ */
18026
+ var NC_CONSUMABLE_SYSTEM_EVENT_KINDS = ["device-consumable-low", "device-consumable-normal"];
18027
+ /** A vacuum's tank alerts (D642) — raised on the provider's own verdict. */
18028
+ var NC_TANK_SYSTEM_EVENT_KINDS = [
18029
+ "device-tank-low",
18030
+ "device-tank-full",
18031
+ "device-tank-normal"
18032
+ ];
18033
+ /**
18019
18034
  * One coherent system-event condition. `kinds` is the required opt-in safety
18020
18035
  * gate; the remaining lists are optional narrowing filters relevant to the
18021
18036
  * selected kinds.
@@ -18049,8 +18064,27 @@ var NcSystemEventConditionSchema = object({
18049
18064
  */
18050
18065
  excludeDeviceIds: array(number().int()).min(1).optional(),
18051
18066
  nodeIds: array(string().min(1)).min(1).optional(),
18052
- packageNames: array(string().min(1)).min(1).optional()
18067
+ packageNames: array(string().min(1)).min(1).optional(),
18068
+ /**
18069
+ * At or below this remaining-life %, a consumable is LOW for THIS rule
18070
+ * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
18071
+ * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
18072
+ * consumable kinds (`systemEventFilterApplies`) and to the device-events
18073
+ * family; ignored on every other kind.
18074
+ *
18075
+ * It is a property of the RULE because the operator decided so: a rule at
18076
+ * 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
18077
+ * keeps one episode per (threshold, device, item) — its own hysteresis, its
18078
+ * own reminder — and a rule hears only the episode judged at ITS threshold,
18079
+ * exactly as an occupancy rule hears only the edge of its own count. A
18080
+ * provider's own `replace` verdict is low at every threshold.
18081
+ */
18082
+ consumableLowPercent: number().int().min(1).max(95).optional()
18053
18083
  });
18084
+ /** The consumable threshold a system-event condition judges at (D642). */
18085
+ function consumableLowPercentOf(condition) {
18086
+ return condition.consumableLowPercent ?? 15;
18087
+ }
18054
18088
  /** Hard ceiling on a window (24h). A snooze that could not expire would be an
18055
18089
  * outage the operator asked for once and forgot. */
18056
18090
  var NC_SNOOZE_MAX_MINUTES = 1440;
@@ -18209,6 +18243,16 @@ var NcCrossingSchema = _enum([
18209
18243
  "exit",
18210
18244
  "any"
18211
18245
  ]);
18246
+ /**
18247
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
18248
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
18249
+ * - `cleared` — it went back: dry, clear, closed.
18250
+ * - absent — both edges, which is what every rule authored before this did.
18251
+ *
18252
+ * There is no third member for "both": absence already says it, and a value
18253
+ * that means the same as absence is a second way to write one rule.
18254
+ */
18255
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
18212
18256
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
18213
18257
  var NcZoneConditionSchema = object({
18214
18258
  ids: array(string().min(1)).min(1),
@@ -18398,6 +18442,20 @@ var NcConditionsSchema = object({
18398
18442
  scene: NcSceneConditionSchema.optional(),
18399
18443
  /** Device scope — absent = all devices. */
18400
18444
  devices: array(number()).optional(),
18445
+ /**
18446
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
18447
+ * subject ids {@link devices} is judged on (the camera, the sensor that
18448
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
18449
+ * linked camera is excluded exactly where an include would have tested it.
18450
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
18451
+ *
18452
+ * Not for `system-event` rules — those carry their own
18453
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
18454
+ * Editors present the two lists as ONE field (All | Only these | All except
18455
+ * these); the engine evaluates each independently, so a legacy rule
18456
+ * carrying both still means "these, minus those".
18457
+ */
18458
+ devicesExclude: array(number()).min(1).optional(),
18401
18459
  /** Detector class names (any overlap with the record's class set). */
18402
18460
  classes: array(string().min(1)).optional(),
18403
18461
  /** Veto classes — any overlap fails the rule. */
@@ -18531,6 +18589,16 @@ var NcConditionsSchema = object({
18531
18589
  */
18532
18590
  sensorKinds: array(string().min(1)).optional(),
18533
18591
  /**
18592
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
18593
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
18594
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
18595
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
18596
+ * the condition fails open towards notifying, never towards silence. Editors
18597
+ * offer it only when every chosen {@link sensorKinds} member has a raise
18598
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
18599
+ */
18600
+ sensorTransition: NcSensorTransitionSchema.optional(),
18601
+ /**
18534
18602
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
18535
18603
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
18536
18604
  * when the subject's phase does not match (a subject always carries a phase
@@ -19149,6 +19217,7 @@ var NcConditionDescriptorSchema = object({
19149
19217
  /** Editor widget the UI renders — never hardcode per-condition forms. */
19150
19218
  valueType: _enum([
19151
19219
  "deviceIdList",
19220
+ "deviceIdExcludeList",
19152
19221
  "stringList",
19153
19222
  "number01",
19154
19223
  "number",
@@ -19159,6 +19228,7 @@ var NcConditionDescriptorSchema = object({
19159
19228
  "plateMatcher",
19160
19229
  "packagePhase",
19161
19230
  "crossingSelect",
19231
+ "sensorTransition",
19162
19232
  "polygonDraw",
19163
19233
  "occupancy",
19164
19234
  "audio",
@@ -19292,6 +19362,18 @@ var NC_CONDITION_CATALOG = [
19292
19362
  value: "device-consumable-normal",
19293
19363
  label: "Consumable replaced"
19294
19364
  },
19365
+ {
19366
+ value: "device-tank-low",
19367
+ label: "Tank low (water, detergent)"
19368
+ },
19369
+ {
19370
+ value: "device-tank-full",
19371
+ label: "Tank full (dirty water, dust)"
19372
+ },
19373
+ {
19374
+ value: "device-tank-normal",
19375
+ label: "Tank back to normal"
19376
+ },
19295
19377
  {
19296
19378
  value: "export-completed",
19297
19379
  label: "Export completed"
@@ -19357,6 +19439,21 @@ var NC_CONDITION_CATALOG = [
19357
19439
  phase: "P1",
19358
19440
  description: "Restrict the rule to these devices; absent = all devices. A sensor event matches either the SENSOR that fired or the camera it is attributed to, so a rule may name either one."
19359
19441
  },
19442
+ {
19443
+ id: "devicesExclude",
19444
+ group: "scope",
19445
+ label: "Excluded devices",
19446
+ valueType: "deviceIdExcludeList",
19447
+ operator: "notIn",
19448
+ appliesTo: [
19449
+ "immediate",
19450
+ "track-end",
19451
+ "device-event",
19452
+ "package-event"
19453
+ ],
19454
+ phase: "P1",
19455
+ description: "Every device except these. Judged on the same device ids as Devices: a sensor event is excluded by the SENSOR that fired or by the camera it is attributed to."
19456
+ },
19360
19457
  {
19361
19458
  id: "classes",
19362
19459
  group: "class",
@@ -19613,6 +19710,33 @@ var NC_CONDITION_CATALOG = [
19613
19710
  phase: "P1",
19614
19711
  description: "Raw device event-type tokens (e.g. doorbell press / press_long) from the event-emitter slice; absent on doorbell-pulse / passive sensors."
19615
19712
  },
19713
+ {
19714
+ id: "sensorTransition",
19715
+ group: "device",
19716
+ label: "Notify when",
19717
+ valueType: "sensorTransition",
19718
+ options: [
19719
+ {
19720
+ value: "",
19721
+ label: "It triggers and when it clears",
19722
+ hint: "Every change: water detected, and back to dry."
19723
+ },
19724
+ {
19725
+ value: "raised",
19726
+ label: "Only when it triggers",
19727
+ hint: "Water detected, smoke, a contact opening — never the return to normal."
19728
+ },
19729
+ {
19730
+ value: "cleared",
19731
+ label: "Only when it clears",
19732
+ hint: "Back to dry, smoke gone, a contact closing — never the alert itself."
19733
+ }
19734
+ ],
19735
+ operator: "in",
19736
+ appliesTo: ["device-event"],
19737
+ phase: "P2",
19738
+ description: "Fire on one edge of a boolean sensor. Offered only when every chosen sensor kind raises and clears; a reading that cannot be placed still notifies."
19739
+ },
19616
19740
  {
19617
19741
  id: "packagePhase",
19618
19742
  group: "package",
@@ -19796,6 +19920,28 @@ var NcHistoryFilterSchema = object({
19796
19920
  limit: number().int().min(1).max(500).default(100)
19797
19921
  });
19798
19922
  /**
19923
+ * Every rule's last run, in one answer.
19924
+ *
19925
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19926
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19927
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19928
+ * its row bound: the answer never claims a window it did not read. `null` means
19929
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19930
+ * means "never on record". A UI derives its "not in the last N days" from this
19931
+ * value, never from a constant — and only when the call ANSWERED (D315).
19932
+ */
19933
+ var NcRuleLastRunsSchema = object({
19934
+ runs: array(object({
19935
+ ruleId: string(),
19936
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19937
+ lastAt: number(),
19938
+ status: NcHistoryStatusSchema,
19939
+ /** The camera/device of that row (a digest row files under its first camera). */
19940
+ deviceId: number()
19941
+ })),
19942
+ horizonMs: number().nonnegative().nullable()
19943
+ });
19944
+ /**
19799
19945
  * What a snooze covers. Broader scopes win when several overlap, so one window
19800
19946
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
19801
19947
  * summarising the same silence.
@@ -19977,6 +20123,14 @@ var NcAlarmModeCoverageSchema = object({
19977
20123
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19978
20124
  deviceIds: array(number().int()),
19979
20125
  /**
20126
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
20127
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
20128
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
20129
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
20130
+ * Defaulted so an answer computed before this field parses as "none".
20131
+ */
20132
+ allDevicesExcept: array(number().int()).default([]),
20133
+ /**
19980
20134
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19981
20135
  * excludes it.
19982
20136
  *
@@ -20200,6 +20354,15 @@ var notificationRulesCapability = {
20200
20354
  */
20201
20355
  getHistory: method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }),
20202
20356
  /**
20357
+ * The newest delivery of every rule, in ONE bounded read of the outbox —
20358
+ * the "Last run" column. Never a `getHistory` per rule. A rule with no
20359
+ * delivery inside `horizonMs` is absent from `runs`; see
20360
+ * {@link NcRuleLastRunsSchema} for what absent means. A store that cannot
20361
+ * answer FAILS the call rather than answering "nothing ran". Admin-only,
20362
+ * like `getHistory`, of which it is a projection.
20363
+ */
20364
+ getRuleLastRuns: method(object({}), NcRuleLastRunsSchema, { auth: "admin" }),
20365
+ /**
20203
20366
  * Mint a fresh, externally-reachable URL for one artefact a history row
20204
20367
  * named in {@link NcHistoryEntrySchema.shape.artifactIds} — the SAME
20205
20368
  * signed-link mechanism the dispatcher uses to attach media to an
@@ -29606,12 +29769,20 @@ reset: method(object({
29606
29769
  },
29607
29770
  runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: number() }),
29608
29771
  /**
29609
- * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
29772
+ * Runtime-state durability: **restored** — nothing else holds the value
29773
+ * (D642). A consumable can often be read only in a device's wake window (the
29774
+ * Dreame mower's CMS counters answer only while it charges), so a restart
29775
+ * that wiped the slice left the panel empty and the alerts blind until the
29776
+ * next wake — 2026-09-26, mower 503 idle with `items: []`. A restored item is
29777
+ * the previous observation, not a claim about now; the provider overwrites it
29778
+ * the moment it reads the device again.
29610
29779
  *
29611
29780
  * See `RuntimeStateDurability`. Enforced by
29612
29781
  * `scripts/check-runtime-state-durability.ts`.
29613
29782
  */
29614
- durability: "session"
29783
+ durability: "restored",
29784
+ /** Clocks, not content: a re-read that changed no item costs no commit. */
29785
+ volatileStateFields: ["lastChangedAt", "lastFetchedAt"]
29615
29786
  };
29616
29787
  /**
29617
29788
  * Door / window / opening / garage / valve contact sensor. Boolean
@@ -44977,6 +45148,12 @@ Object.freeze({
44977
45148
  addonId: null,
44978
45149
  access: "view"
44979
45150
  },
45151
+ "notificationRules.getRuleLastRuns": {
45152
+ capName: "notification-rules",
45153
+ capScope: "system",
45154
+ addonId: null,
45155
+ access: "view"
45156
+ },
44980
45157
  "notificationRules.getTemplateCatalog": {
44981
45158
  capName: "notification-rules",
44982
45159
  capScope: "system",
@@ -50593,12 +50770,55 @@ function audioModeOf(value) {
50593
50770
  return null;
50594
50771
  }
50595
50772
  new Set(["devices", "classes"]);
50596
- /** Every filter, for the callers that must cover all four (guards, tests). */
50773
+ /**
50774
+ * WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
50775
+ * consumed by the rule ENGINE and by every rule EDITOR.
50776
+ *
50777
+ * ── The failure this file exists for ───────────────────────────────────────
50778
+ * `NcSystemEventConditionSchema` carries four optional narrowing lists
50779
+ * (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
50780
+ * `kinds` gate. They are not four filters over one vocabulary: each belongs to
50781
+ * a FAMILY of kinds and is meaningless outside it. A node going away has no
50782
+ * device type; a completed export has no package; `stream-offline` deliberately
50783
+ * carries no device type at all (a stream belongs to a camera by construction).
50784
+ *
50785
+ * The engine applied all four to every kind, so a filter from one family failed
50786
+ * a kind from another family CLOSED. The operator's live rule "Eventi di
50787
+ * sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
50788
+ * way to say "only my cameras" about the two liveness kinds — and NINE of the
50789
+ * eleven silently stopped being deliverable. Four days of history contained
50790
+ * `device-online` and `device-offline` and nothing else, across a window in
50791
+ * which agents were restarted and addons were updated.
50792
+ *
50793
+ * Both editors already computed this scoping, to decide which filter boxes to
50794
+ * render; the cap's own docblock already promised it ("Narrow a `device-*` kind
50795
+ * to these device TYPES"). Only the engine disagreed, and it disagreed in the
50796
+ * destructive direction. So the rule is stated ONCE, here, and the engine and
50797
+ * the editors are readers of it rather than three independent authors.
50798
+ *
50799
+ * ── The direction ─────────────────────────────────────────────────────────
50800
+ * Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
50801
+ * hides a box has already told the operator this filter does not speak about
50802
+ * these kinds, so honouring it as an exclusion contradicts the only
50803
+ * explanation the operator was ever given. Inside its family the filter still
50804
+ * narrows, and still fails CLOSED on a subject that cannot produce the identity
50805
+ * it tests (a device the mirror cannot name is not evidence of a camera).
50806
+ */
50807
+ /**
50808
+ * Every filter, for the callers that must cover all of them (guards, tests).
50809
+ *
50810
+ * `consumableLowPercent` (D642) is not a list but it is a filter in exactly
50811
+ * this sense: it belongs to a family of kinds (the consumable pair) and the
50812
+ * engine consults it only there. It differs in one way the readers must know —
50813
+ * its ABSENCE is not "no narrowing" but the default threshold, because a
50814
+ * consumable episode is always judged at SOME threshold.
50815
+ */
50597
50816
  var NC_SYSTEM_EVENT_FILTER_KEYS = [
50598
50817
  "deviceIds",
50599
50818
  "deviceTypes",
50600
50819
  "nodeIds",
50601
- "packageNames"
50820
+ "packageNames",
50821
+ "consumableLowPercent"
50602
50822
  ];
50603
50823
  /**
50604
50824
  * Does `filter` say anything about `kind`?
@@ -50612,6 +50832,7 @@ function systemEventFilterApplies(kind, filter) {
50612
50832
  case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
50613
50833
  case "nodeIds": return kind.startsWith("node-") || kind === "addon-crash-loop" || kind === "addon-update-available" || kind === "addon-updated" || kind === "server-update-available" || kind === "wrapper-update-available" || kind === "server-updated";
50614
50834
  case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
50835
+ case "consumableLowPercent": return NC_CONSUMABLE_SYSTEM_EVENT_KINDS.includes(kind);
50615
50836
  }
50616
50837
  }
50617
50838
  /**
@@ -50647,6 +50868,9 @@ var NC_SYSTEM_EVENT_FAMILY = {
50647
50868
  "device-battery-normal": "device-events",
50648
50869
  "device-consumable-low": "device-events",
50649
50870
  "device-consumable-normal": "device-events",
50871
+ "device-tank-low": "device-events",
50872
+ "device-tank-full": "device-events",
50873
+ "device-tank-normal": "device-events",
50650
50874
  "stream-online": "device-events",
50651
50875
  "stream-offline": "device-events",
50652
50876
  "detection-blind": "device-events",
@@ -50678,7 +50902,7 @@ var NC_SYSTEM_EVENT_FAMILY = {
50678
50902
  new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
50679
50903
  var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
50680
50904
  /**
50681
- * The filters a family may carry. The four base filters are the union of
50905
+ * The filters a family may carry. The base filters are the union of
50682
50906
  * `systemEventFilterApplies` over the family's kinds (the spec pins that
50683
50907
  * equality). `excludeDeviceIds` is a device-events filter only: an alarm is
50684
50908
  * narrowed to the camera that triggered it and excludes nothing.
@@ -50687,7 +50911,8 @@ var NC_SYSTEM_EVENT_FAMILY_FILTERS = {
50687
50911
  "device-events": [
50688
50912
  "deviceIds",
50689
50913
  "excludeDeviceIds",
50690
- "deviceTypes"
50914
+ "deviceTypes",
50915
+ "consumableLowPercent"
50691
50916
  ],
50692
50917
  system: ["nodeIds", "packageNames"],
50693
50918
  alarm: ["deviceIds"]
@@ -50722,6 +50947,16 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
50722
50947
  label: "Consumables",
50723
50948
  kinds: ["device-consumable-low", "device-consumable-normal"]
50724
50949
  },
50950
+ {
50951
+ id: "tanks",
50952
+ family: "device-events",
50953
+ label: "Tanks",
50954
+ kinds: [
50955
+ "device-tank-low",
50956
+ "device-tank-full",
50957
+ "device-tank-normal"
50958
+ ]
50959
+ },
50725
50960
  {
50726
50961
  id: "detection-blind",
50727
50962
  family: "device-events",
@@ -50901,10 +51136,75 @@ function isScheduleActive(schedule, atMs) {
50901
51136
  }
50902
51137
  return schedule.invert === true ? !inside : inside;
50903
51138
  }
51139
+ var ARMED_WORD_PREFIX = "armed_";
51140
+ var ALARM_PANEL_CAP = "alarm-panel";
51141
+ var ALARM_PANEL_TRIGGER_METHOD = "trigger";
51142
+ /**
51143
+ * A rule belongs to the alarm when its `deviceState` gate names ANY device's
51144
+ * `armed_*` word, or an ENABLED `onTrigger` sequence pulls the alarm panel's
51145
+ * trigger (D630).
51146
+ */
51147
+ function ruleBelongsToAlarm(rule) {
51148
+ if ((rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX))) return true;
51149
+ return (rule.onTrigger ?? []).some((sequence) => sequence.enabled && sequence.actions.some((step) => step.kind === "cap" && step.cap === ALARM_PANEL_CAP && step.method === ALARM_PANEL_TRIGGER_METHOD));
51150
+ }
51151
+ /**
51152
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
51153
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
51154
+ *
51155
+ * A device-event rule over a flood sensor notified on BOTH edges: water
51156
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
51157
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
51158
+ *
51159
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
51160
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
51161
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
51162
+ * table), and a sensor event names its kind, not its cap. So the table here is
51163
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
51164
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
51165
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
51166
+ * light, and offering it there would be a knob with a misleading label.
51167
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
51168
+ * absent from the source table, so it is absent here.
51169
+ *
51170
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
51171
+ * button, a cover), or a slice whose field is missing or not a boolean. The
51172
+ * engine then does NOT filter — a flood alert must not go silent because one
51173
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
51174
+ *
51175
+ * Pure, RN-safe: the viewer imports the support rule from here.
51176
+ */
51177
+ /** Event kind → the boolean field of its cap slice that means "raised". */
51178
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
51179
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
51180
+ if (field === void 0 || descriptor.category !== "sensor") return [];
51181
+ return [[descriptor.kind, field]];
51182
+ })));
51183
+ /** The sensor kinds `sensorTransition` means something for. */
51184
+ var NC_SENSOR_TRANSITION_KINDS = Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
51185
+ function normaliseKind(kind) {
51186
+ return kind.trim().toLowerCase();
51187
+ }
51188
+ /**
51189
+ * true = the sensor is raised (water, smoke, open, tampered…), false = it
51190
+ * cleared, undefined = this kind has no raise or the slice cannot say.
51191
+ */
51192
+ function sensorRaisedOf(kind, value) {
51193
+ if (value === null) return void 0;
51194
+ const field = NC_SENSOR_RAISED_FIELD_BY_KIND[normaliseKind(kind)];
51195
+ if (field === void 0) return void 0;
51196
+ const raw = Object.entries(value).find(([key]) => key === field)?.[1];
51197
+ return typeof raw === "boolean" ? raw : void 0;
51198
+ }
50904
51199
  var RULE_BODY_ONLY = ["body"];
50905
51200
  var RULE_TEXT = ["title", "body"];
50906
51201
  var BATTERY = ["device-battery-low", "device-battery-normal"];
50907
51202
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
51203
+ var TANK = [
51204
+ "device-tank-low",
51205
+ "device-tank-full",
51206
+ "device-tank-normal"
51207
+ ];
50908
51208
  var UPDATES = [
50909
51209
  "addon-update-available",
50910
51210
  "server-update-available",
@@ -50930,6 +51230,7 @@ var DEVICE_EVENTS = [
50930
51230
  "device-enabled",
50931
51231
  ...BATTERY,
50932
51232
  ...CONSUMABLE,
51233
+ ...TANK,
50933
51234
  "stream-online",
50934
51235
  "stream-offline",
50935
51236
  "detection-blind",
@@ -51227,6 +51528,17 @@ var NC_TEMPLATE_VARS = [
51227
51528
  kinds: ["system"],
51228
51529
  systemEventKinds: [...CONSUMABLE]
51229
51530
  },
51531
+ {
51532
+ name: "tank",
51533
+ label: "Tank",
51534
+ description: "Which tank: clean water, dirty water, detergent or dust bin.",
51535
+ example: "clean water",
51536
+ group: "system",
51537
+ families: ["rule"],
51538
+ fields: [...RULE_TEXT],
51539
+ kinds: ["system"],
51540
+ systemEventKinds: [...TANK]
51541
+ },
51230
51542
  {
51231
51543
  name: "profile",
51232
51544
  label: "Stream profile",
@@ -51619,6 +51931,38 @@ var NC_TEMPLATE_VARS = [
51619
51931
  fields: [...RULE_TEXT]
51620
51932
  }
51621
51933
  ];
51934
+ /**
51935
+ * System-event kinds the SERVED catalog holds back (D642): the kinds the hub
51936
+ * produces before the installed viewers can parse them.
51937
+ *
51938
+ * The viewer parses `getTemplateCatalog` WHOLE, with its own strict
51939
+ * `NcSystemEventKindSchema` on every descriptor's `systemEventKinds`, so one
51940
+ * kind it does not know — in `{{deviceName}}`, which every device event names —
51941
+ * empties every {{var}} picker on every phone (the same trap
51942
+ * `NcTemplateVarKindSchema` froze the rule kinds for). The in-process catalog
51943
+ * keeps them: the producer parity and the preview render read it.
51944
+ *
51945
+ * SELF-EXPIRING: `scripts/check-nc-mirrors-in-sync.ts` reads this list as the
51946
+ * viewer's allowance and fails the day the viewer mirrors one of these kinds —
51947
+ * delete it from here then, and the served catalog names it again.
51948
+ */
51949
+ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
51950
+ "device-tank-low",
51951
+ "device-tank-full",
51952
+ "device-tank-normal"
51953
+ ];
51954
+ /**
51955
+ * The catalog as `getTemplateCatalog` SERVES it: every descriptor, with the
51956
+ * held-back kinds removed from its `systemEventKinds`. A descriptor that named
51957
+ * only held-back kinds is served with an empty list — offered to no system
51958
+ * rule an installed viewer can author, which is exactly true.
51959
+ */
51960
+ function servedTemplateVars() {
51961
+ return NC_TEMPLATE_VARS.map((d) => d.systemEventKinds === void 0 ? d : {
51962
+ ...d,
51963
+ systemEventKinds: d.systemEventKinds.filter((kind) => !NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS.includes(kind))
51964
+ });
51965
+ }
51622
51966
  /** The caption fields see a fixed, smaller vocabulary (`timelapsePreviewText`,
51623
51967
  * `summaryCaptionText`). Declared here rather than as `fields` on every
51624
51968
  * descriptor so the whole rule stays in one line of code. */
@@ -52885,18 +53229,36 @@ Object.defineProperty(exports, "NC_CONDITION_CATALOG", {
52885
53229
  return NC_CONDITION_CATALOG;
52886
53230
  }
52887
53231
  });
53232
+ Object.defineProperty(exports, "NC_CONSUMABLE_SYSTEM_EVENT_KINDS", {
53233
+ enumerable: true,
53234
+ get: function() {
53235
+ return NC_CONSUMABLE_SYSTEM_EVENT_KINDS;
53236
+ }
53237
+ });
52888
53238
  Object.defineProperty(exports, "NC_DEFAULT_SNOOZE_MINUTES", {
52889
53239
  enumerable: true,
52890
53240
  get: function() {
52891
53241
  return NC_DEFAULT_SNOOZE_MINUTES;
52892
53242
  }
52893
53243
  });
53244
+ Object.defineProperty(exports, "NC_SENSOR_TRANSITION_KINDS", {
53245
+ enumerable: true,
53246
+ get: function() {
53247
+ return NC_SENSOR_TRANSITION_KINDS;
53248
+ }
53249
+ });
52894
53250
  Object.defineProperty(exports, "NC_SNOOZE_MAX_MINUTES", {
52895
53251
  enumerable: true,
52896
53252
  get: function() {
52897
53253
  return NC_SNOOZE_MAX_MINUTES;
52898
53254
  }
52899
53255
  });
53256
+ Object.defineProperty(exports, "NC_TANK_SYSTEM_EVENT_KINDS", {
53257
+ enumerable: true,
53258
+ get: function() {
53259
+ return NC_TANK_SYSTEM_EVENT_KINDS;
53260
+ }
53261
+ });
52900
53262
  Object.defineProperty(exports, "NC_TAXONOMY", {
52901
53263
  enumerable: true,
52902
53264
  get: function() {
@@ -53179,6 +53541,12 @@ Object.defineProperty(exports, "canonicalHash", {
53179
53541
  return canonicalHash;
53180
53542
  }
53181
53543
  });
53544
+ Object.defineProperty(exports, "consumableLowPercentOf", {
53545
+ enumerable: true,
53546
+ get: function() {
53547
+ return consumableLowPercentOf;
53548
+ }
53549
+ });
53182
53550
  Object.defineProperty(exports, "cosineSimilarity", {
53183
53551
  enumerable: true,
53184
53552
  get: function() {
@@ -53431,12 +53799,30 @@ Object.defineProperty(exports, "resolvePoolMemoryPolicy", {
53431
53799
  return resolvePoolMemoryPolicy;
53432
53800
  }
53433
53801
  });
53802
+ Object.defineProperty(exports, "ruleBelongsToAlarm", {
53803
+ enumerable: true,
53804
+ get: function() {
53805
+ return ruleBelongsToAlarm;
53806
+ }
53807
+ });
53434
53808
  Object.defineProperty(exports, "sceneMonitorCapability", {
53435
53809
  enumerable: true,
53436
53810
  get: function() {
53437
53811
  return sceneMonitorCapability;
53438
53812
  }
53439
53813
  });
53814
+ Object.defineProperty(exports, "sensorRaisedOf", {
53815
+ enumerable: true,
53816
+ get: function() {
53817
+ return sensorRaisedOf;
53818
+ }
53819
+ });
53820
+ Object.defineProperty(exports, "servedTemplateVars", {
53821
+ enumerable: true,
53822
+ get: function() {
53823
+ return servedTemplateVars;
53824
+ }
53825
+ });
53440
53826
  Object.defineProperty(exports, "sleep", {
53441
53827
  enumerable: true,
53442
53828
  get: function() {