@camstack/types 1.2.255 → 1.2.257

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BAgDk7I9.js");
2
+ const require_sleep = require("./sleep-DtomDrRW.js");
3
3
  const require_event_category = require("./event-category-BVfsrBYA.js");
4
4
  const require_canonical_hash = require("./canonical-hash-CSE4ioRi.js");
5
5
  const require_enums = require("./enums.js");
@@ -17196,6 +17196,9 @@ var NcSystemEventKindSchema = zod.z.enum([
17196
17196
  "device-battery-normal",
17197
17197
  "device-consumable-low",
17198
17198
  "device-consumable-normal",
17199
+ "device-tank-low",
17200
+ "device-tank-full",
17201
+ "device-tank-normal",
17199
17202
  "stream-online",
17200
17203
  "stream-offline",
17201
17204
  "node-online",
@@ -17276,6 +17279,31 @@ var NC_BATTERY_CONSUMABLE_SYSTEM_EVENT_KINDS = [
17276
17279
  "device-consumable-normal"
17277
17280
  ];
17278
17281
  /**
17282
+ * The two kinds a RULE's consumable threshold speaks about (D642). Named once:
17283
+ * the filter scoping (`systemEventFilterApplies`), the hub's threshold census
17284
+ * and both editors read it.
17285
+ */
17286
+ var NC_CONSUMABLE_SYSTEM_EVENT_KINDS = ["device-consumable-low", "device-consumable-normal"];
17287
+ /** A vacuum's tank alerts (D642) — raised on the provider's own verdict. */
17288
+ var NC_TANK_SYSTEM_EVENT_KINDS = [
17289
+ "device-tank-low",
17290
+ "device-tank-full",
17291
+ "device-tank-normal"
17292
+ ];
17293
+ /**
17294
+ * The consumable threshold a rule that does not state one judges at (D642).
17295
+ * The operator's number ("default 15 %, configurable on the rule"), and the
17296
+ * value the cluster-wide setting it replaces shipped with.
17297
+ */
17298
+ var NC_CONSUMABLE_LOW_PERCENT_DEFAULT = 15;
17299
+ /**
17300
+ * The highest consumable threshold a rule may state (D642 review M1): 100 minus
17301
+ * the band's 5-point recovery hysteresis (`consumable-band.ts`). Above it the
17302
+ * recovery point passes 100 % and an episode could never clear, even for a
17303
+ * part just replaced. A spec in the notification centre pins the sum.
17304
+ */
17305
+ var NC_CONSUMABLE_LOW_PERCENT_MAX = 95;
17306
+ /**
17279
17307
  * One coherent system-event condition. `kinds` is the required opt-in safety
17280
17308
  * gate; the remaining lists are optional narrowing filters relevant to the
17281
17309
  * selected kinds.
@@ -17309,8 +17337,27 @@ var NcSystemEventConditionSchema = zod.z.object({
17309
17337
  */
17310
17338
  excludeDeviceIds: zod.z.array(zod.z.number().int()).min(1).optional(),
17311
17339
  nodeIds: zod.z.array(zod.z.string().min(1)).min(1).optional(),
17312
- packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
17313
- });
17340
+ packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional(),
17341
+ /**
17342
+ * At or below this remaining-life %, a consumable is LOW for THIS rule
17343
+ * (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
17344
+ * {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
17345
+ * consumable kinds (`systemEventFilterApplies`) and to the device-events
17346
+ * family; ignored on every other kind.
17347
+ *
17348
+ * It is a property of the RULE because the operator decided so: a rule at
17349
+ * 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
17350
+ * keeps one episode per (threshold, device, item) — its own hysteresis, its
17351
+ * own reminder — and a rule hears only the episode judged at ITS threshold,
17352
+ * exactly as an occupancy rule hears only the edge of its own count. A
17353
+ * provider's own `replace` verdict is low at every threshold.
17354
+ */
17355
+ consumableLowPercent: zod.z.number().int().min(1).max(95).optional()
17356
+ });
17357
+ /** The consumable threshold a system-event condition judges at (D642). */
17358
+ function consumableLowPercentOf(condition) {
17359
+ return condition.consumableLowPercent ?? 15;
17360
+ }
17314
17361
  /**
17315
17362
  * `maxPerTrack` for `immediate` rules is FIXED at 1 (D-3): a single track
17316
17363
  * fires an immediate rule at most once, enforced durably by the outbox
@@ -17495,6 +17542,16 @@ var NcCrossingSchema = zod.z.enum([
17495
17542
  "exit",
17496
17543
  "any"
17497
17544
  ]);
17545
+ /**
17546
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
17547
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
17548
+ * - `cleared` — it went back: dry, clear, closed.
17549
+ * - absent — both edges, which is what every rule authored before this did.
17550
+ *
17551
+ * There is no third member for "both": absence already says it, and a value
17552
+ * that means the same as absence is a second way to write one rule.
17553
+ */
17554
+ var NcSensorTransitionSchema = zod.z.enum(["raised", "cleared"]);
17498
17555
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
17499
17556
  var NcZoneConditionSchema = zod.z.object({
17500
17557
  ids: zod.z.array(zod.z.string().min(1)).min(1),
@@ -17684,6 +17741,20 @@ var NcConditionsSchema = zod.z.object({
17684
17741
  scene: NcSceneConditionSchema.optional(),
17685
17742
  /** Device scope — absent = all devices. */
17686
17743
  devices: zod.z.array(zod.z.number()).optional(),
17744
+ /**
17745
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
17746
+ * subject ids {@link devices} is judged on (the camera, the sensor that
17747
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
17748
+ * linked camera is excluded exactly where an include would have tested it.
17749
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
17750
+ *
17751
+ * Not for `system-event` rules — those carry their own
17752
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
17753
+ * Editors present the two lists as ONE field (All | Only these | All except
17754
+ * these); the engine evaluates each independently, so a legacy rule
17755
+ * carrying both still means "these, minus those".
17756
+ */
17757
+ devicesExclude: zod.z.array(zod.z.number()).min(1).optional(),
17687
17758
  /** Detector class names (any overlap with the record's class set). */
17688
17759
  classes: zod.z.array(zod.z.string().min(1)).optional(),
17689
17760
  /** Veto classes — any overlap fails the rule. */
@@ -17817,6 +17888,16 @@ var NcConditionsSchema = zod.z.object({
17817
17888
  */
17818
17889
  sensorKinds: zod.z.array(zod.z.string().min(1)).optional(),
17819
17890
  /**
17891
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
17892
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
17893
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
17894
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
17895
+ * the condition fails open towards notifying, never towards silence. Editors
17896
+ * offer it only when every chosen {@link sensorKinds} member has a raise
17897
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
17898
+ */
17899
+ sensorTransition: NcSensorTransitionSchema.optional(),
17900
+ /**
17820
17901
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
17821
17902
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
17822
17903
  * when the subject's phase does not match (a subject always carries a phase
@@ -18449,6 +18530,7 @@ var NcConditionDescriptorSchema = zod.z.object({
18449
18530
  /** Editor widget the UI renders — never hardcode per-condition forms. */
18450
18531
  valueType: zod.z.enum([
18451
18532
  "deviceIdList",
18533
+ "deviceIdExcludeList",
18452
18534
  "stringList",
18453
18535
  "number01",
18454
18536
  "number",
@@ -18459,6 +18541,7 @@ var NcConditionDescriptorSchema = zod.z.object({
18459
18541
  "plateMatcher",
18460
18542
  "packagePhase",
18461
18543
  "crossingSelect",
18544
+ "sensorTransition",
18462
18545
  "polygonDraw",
18463
18546
  "occupancy",
18464
18547
  "audio",
@@ -18592,6 +18675,18 @@ var NC_CONDITION_CATALOG = [
18592
18675
  value: "device-consumable-normal",
18593
18676
  label: "Consumable replaced"
18594
18677
  },
18678
+ {
18679
+ value: "device-tank-low",
18680
+ label: "Tank low (water, detergent)"
18681
+ },
18682
+ {
18683
+ value: "device-tank-full",
18684
+ label: "Tank full (dirty water, dust)"
18685
+ },
18686
+ {
18687
+ value: "device-tank-normal",
18688
+ label: "Tank back to normal"
18689
+ },
18595
18690
  {
18596
18691
  value: "export-completed",
18597
18692
  label: "Export completed"
@@ -18657,6 +18752,21 @@ var NC_CONDITION_CATALOG = [
18657
18752
  phase: "P1",
18658
18753
  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."
18659
18754
  },
18755
+ {
18756
+ id: "devicesExclude",
18757
+ group: "scope",
18758
+ label: "Excluded devices",
18759
+ valueType: "deviceIdExcludeList",
18760
+ operator: "notIn",
18761
+ appliesTo: [
18762
+ "immediate",
18763
+ "track-end",
18764
+ "device-event",
18765
+ "package-event"
18766
+ ],
18767
+ phase: "P1",
18768
+ 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."
18769
+ },
18660
18770
  {
18661
18771
  id: "classes",
18662
18772
  group: "class",
@@ -18913,6 +19023,33 @@ var NC_CONDITION_CATALOG = [
18913
19023
  phase: "P1",
18914
19024
  description: "Raw device event-type tokens (e.g. doorbell press / press_long) from the event-emitter slice; absent on doorbell-pulse / passive sensors."
18915
19025
  },
19026
+ {
19027
+ id: "sensorTransition",
19028
+ group: "device",
19029
+ label: "Notify when",
19030
+ valueType: "sensorTransition",
19031
+ options: [
19032
+ {
19033
+ value: "",
19034
+ label: "It triggers and when it clears",
19035
+ hint: "Every change: water detected, and back to dry."
19036
+ },
19037
+ {
19038
+ value: "raised",
19039
+ label: "Only when it triggers",
19040
+ hint: "Water detected, smoke, a contact opening — never the return to normal."
19041
+ },
19042
+ {
19043
+ value: "cleared",
19044
+ label: "Only when it clears",
19045
+ hint: "Back to dry, smoke gone, a contact closing — never the alert itself."
19046
+ }
19047
+ ],
19048
+ operator: "in",
19049
+ appliesTo: ["device-event"],
19050
+ phase: "P2",
19051
+ 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."
19052
+ },
18916
19053
  {
18917
19054
  id: "packagePhase",
18918
19055
  group: "package",
@@ -19099,6 +19236,39 @@ var NcHistoryFilterSchema = zod.z.object({
19099
19236
  limit: zod.z.number().int().min(1).max(500).default(100)
19100
19237
  });
19101
19238
  /**
19239
+ * The newest delivery row of ONE rule — the "Last run" of a rules table.
19240
+ *
19241
+ * A projection of the same outbox rows `getHistory` returns, so it records
19242
+ * DELIVERIES only: a throttled or unmatched evaluation never produced a row
19243
+ * and is not a run here. When one fire reached several targets (several rows
19244
+ * at the same `lastAt`), `status` is the least successful of them
19245
+ * (`dead` > `pending` > `sent`): "the last run delivered" must be true for
19246
+ * every target it names.
19247
+ */
19248
+ var NcRuleLastRunSchema = zod.z.object({
19249
+ ruleId: zod.z.string(),
19250
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19251
+ lastAt: zod.z.number(),
19252
+ status: NcHistoryStatusSchema,
19253
+ /** The camera/device of that row (a digest row files under its first camera). */
19254
+ deviceId: zod.z.number()
19255
+ });
19256
+ /**
19257
+ * Every rule's last run, in one answer.
19258
+ *
19259
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19260
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19261
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19262
+ * its row bound: the answer never claims a window it did not read. `null` means
19263
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19264
+ * means "never on record". A UI derives its "not in the last N days" from this
19265
+ * value, never from a constant — and only when the call ANSWERED (D315).
19266
+ */
19267
+ var NcRuleLastRunsSchema = zod.z.object({
19268
+ runs: zod.z.array(NcRuleLastRunSchema),
19269
+ horizonMs: zod.z.number().nonnegative().nullable()
19270
+ });
19271
+ /**
19102
19272
  * What a snooze covers. Broader scopes win when several overlap, so one window
19103
19273
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
19104
19274
  * summarising the same silence.
@@ -19285,6 +19455,14 @@ var NcAlarmModeCoverageSchema = zod.z.object({
19285
19455
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19286
19456
  deviceIds: zod.z.array(zod.z.number().int()),
19287
19457
  /**
19458
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
19459
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
19460
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
19461
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
19462
+ * Defaulted so an answer computed before this field parses as "none".
19463
+ */
19464
+ allDevicesExcept: zod.z.array(zod.z.number().int()).default([]),
19465
+ /**
19288
19466
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19289
19467
  * excludes it.
19290
19468
  *
@@ -19526,6 +19704,15 @@ var notificationRulesCapability = {
19526
19704
  */
19527
19705
  getHistory: require_sleep.method(zod.z.object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), zod.z.object({ entries: zod.z.array(NcHistoryEntrySchema) }), { auth: "admin" }),
19528
19706
  /**
19707
+ * The newest delivery of every rule, in ONE bounded read of the outbox —
19708
+ * the "Last run" column. Never a `getHistory` per rule. A rule with no
19709
+ * delivery inside `horizonMs` is absent from `runs`; see
19710
+ * {@link NcRuleLastRunsSchema} for what absent means. A store that cannot
19711
+ * answer FAILS the call rather than answering "nothing ran". Admin-only,
19712
+ * like `getHistory`, of which it is a projection.
19713
+ */
19714
+ getRuleLastRuns: require_sleep.method(zod.z.object({}), NcRuleLastRunsSchema, { auth: "admin" }),
19715
+ /**
19529
19716
  * Mint a fresh, externally-reachable URL for one artefact a history row
19530
19717
  * named in {@link NcHistoryEntrySchema.shape.artifactIds} — the SAME
19531
19718
  * signed-link mechanism the dispatcher uses to attach media to an
@@ -31646,12 +31833,20 @@ reset: require_sleep.method(zod.z.object({
31646
31833
  },
31647
31834
  runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
31648
31835
  /**
31649
- * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
31836
+ * Runtime-state durability: **restored** — nothing else holds the value
31837
+ * (D642). A consumable can often be read only in a device's wake window (the
31838
+ * Dreame mower's CMS counters answer only while it charges), so a restart
31839
+ * that wiped the slice left the panel empty and the alerts blind until the
31840
+ * next wake — 2026-09-26, mower 503 idle with `items: []`. A restored item is
31841
+ * the previous observation, not a claim about now; the provider overwrites it
31842
+ * the moment it reads the device again.
31650
31843
  *
31651
31844
  * See `RuntimeStateDurability`. Enforced by
31652
31845
  * `scripts/check-runtime-state-durability.ts`.
31653
31846
  */
31654
- durability: "session"
31847
+ durability: "restored",
31848
+ /** Clocks, not content: a re-read that changed no item costs no commit. */
31849
+ volatileStateFields: ["lastChangedAt", "lastFetchedAt"]
31655
31850
  };
31656
31851
  //#endregion
31657
31852
  //#region src/capabilities/contact.cap.ts
@@ -52863,6 +53058,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
52863
53058
  addonId: null,
52864
53059
  access: "view"
52865
53060
  },
53061
+ "notificationRules.getRuleLastRuns": {
53062
+ capName: "notification-rules",
53063
+ capScope: "system",
53064
+ addonId: null,
53065
+ access: "view"
53066
+ },
52866
53067
  "notificationRules.getTemplateCatalog": {
52867
53068
  capName: "notification-rules",
52868
53069
  capScope: "system",
@@ -58905,7 +59106,7 @@ function getCapsByProviderKind(kind) {
58905
59106
  //#region src/generated/runtime-state-durability.ts
58906
59107
  /**
58907
59108
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
58908
- * `runtimeState`. 69 caps: 36 `restored`, 33 `session`.
59109
+ * `runtimeState`. 69 caps: 37 `restored`, 32 `session`.
58909
59110
  *
58910
59111
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
58911
59112
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -58964,8 +59165,8 @@ var RUNTIME_STATE_POLICY = {
58964
59165
  volatileFields: ["lastChangedAt"]
58965
59166
  },
58966
59167
  "consumables": {
58967
- durability: "session",
58968
- volatileFields: []
59168
+ durability: "restored",
59169
+ volatileFields: ["lastChangedAt", "lastFetchedAt"]
58969
59170
  },
58970
59171
  "contact": {
58971
59172
  durability: "restored",
@@ -59216,6 +59417,7 @@ var RESTORED_CAP_NAMES = [
59216
59417
  "binary",
59217
59418
  "carbon-monoxide",
59218
59419
  "connectivity",
59420
+ "consumables",
59219
59421
  "contact",
59220
59422
  "cover",
59221
59423
  "day-night",
@@ -60731,12 +60933,55 @@ function prepareNotification(caps, n) {
60731
60933
  }
60732
60934
  //#endregion
60733
60935
  //#region src/notification/system-event-filters.ts
60734
- /** Every filter, for the callers that must cover all four (guards, tests). */
60936
+ /**
60937
+ * WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
60938
+ * consumed by the rule ENGINE and by every rule EDITOR.
60939
+ *
60940
+ * ── The failure this file exists for ───────────────────────────────────────
60941
+ * `NcSystemEventConditionSchema` carries four optional narrowing lists
60942
+ * (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
60943
+ * `kinds` gate. They are not four filters over one vocabulary: each belongs to
60944
+ * a FAMILY of kinds and is meaningless outside it. A node going away has no
60945
+ * device type; a completed export has no package; `stream-offline` deliberately
60946
+ * carries no device type at all (a stream belongs to a camera by construction).
60947
+ *
60948
+ * The engine applied all four to every kind, so a filter from one family failed
60949
+ * a kind from another family CLOSED. The operator's live rule "Eventi di
60950
+ * sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
60951
+ * way to say "only my cameras" about the two liveness kinds — and NINE of the
60952
+ * eleven silently stopped being deliverable. Four days of history contained
60953
+ * `device-online` and `device-offline` and nothing else, across a window in
60954
+ * which agents were restarted and addons were updated.
60955
+ *
60956
+ * Both editors already computed this scoping, to decide which filter boxes to
60957
+ * render; the cap's own docblock already promised it ("Narrow a `device-*` kind
60958
+ * to these device TYPES"). Only the engine disagreed, and it disagreed in the
60959
+ * destructive direction. So the rule is stated ONCE, here, and the engine and
60960
+ * the editors are readers of it rather than three independent authors.
60961
+ *
60962
+ * ── The direction ─────────────────────────────────────────────────────────
60963
+ * Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
60964
+ * hides a box has already told the operator this filter does not speak about
60965
+ * these kinds, so honouring it as an exclusion contradicts the only
60966
+ * explanation the operator was ever given. Inside its family the filter still
60967
+ * narrows, and still fails CLOSED on a subject that cannot produce the identity
60968
+ * it tests (a device the mirror cannot name is not evidence of a camera).
60969
+ */
60970
+ /**
60971
+ * Every filter, for the callers that must cover all of them (guards, tests).
60972
+ *
60973
+ * `consumableLowPercent` (D642) is not a list but it is a filter in exactly
60974
+ * this sense: it belongs to a family of kinds (the consumable pair) and the
60975
+ * engine consults it only there. It differs in one way the readers must know —
60976
+ * its ABSENCE is not "no narrowing" but the default threshold, because a
60977
+ * consumable episode is always judged at SOME threshold.
60978
+ */
60735
60979
  var NC_SYSTEM_EVENT_FILTER_KEYS = [
60736
60980
  "deviceIds",
60737
60981
  "deviceTypes",
60738
60982
  "nodeIds",
60739
- "packageNames"
60983
+ "packageNames",
60984
+ "consumableLowPercent"
60740
60985
  ];
60741
60986
  /**
60742
60987
  * Does `filter` say anything about `kind`?
@@ -60750,6 +60995,7 @@ function systemEventFilterApplies(kind, filter) {
60750
60995
  case "deviceTypes": return kind.startsWith("device-") || kind === "detection-blind";
60751
60996
  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";
60752
60997
  case "packageNames": return kind.endsWith("update-available") || kind === "addon-updated" || kind === "server-updated";
60998
+ case "consumableLowPercent": return NC_CONSUMABLE_SYSTEM_EVENT_KINDS.includes(kind);
60753
60999
  }
60754
61000
  }
60755
61001
  /**
@@ -60798,6 +61044,9 @@ var NC_SYSTEM_EVENT_FAMILY = {
60798
61044
  "device-battery-normal": "device-events",
60799
61045
  "device-consumable-low": "device-events",
60800
61046
  "device-consumable-normal": "device-events",
61047
+ "device-tank-low": "device-events",
61048
+ "device-tank-full": "device-events",
61049
+ "device-tank-normal": "device-events",
60801
61050
  "stream-online": "device-events",
60802
61051
  "stream-offline": "device-events",
60803
61052
  "detection-blind": "device-events",
@@ -60830,7 +61079,7 @@ var NC_SYSTEM_EVENT_FAMILY = {
60830
61079
  var FAMILY_BY_NAME = new Map(Object.entries(NC_SYSTEM_EVENT_FAMILY));
60831
61080
  var NC_SYSTEM_EVENT_CONDITION_FILTER_KEYS = [...NC_SYSTEM_EVENT_FILTER_KEYS, "excludeDeviceIds"];
60832
61081
  /**
60833
- * The filters a family may carry. The four base filters are the union of
61082
+ * The filters a family may carry. The base filters are the union of
60834
61083
  * `systemEventFilterApplies` over the family's kinds (the spec pins that
60835
61084
  * equality). `excludeDeviceIds` is a device-events filter only: an alarm is
60836
61085
  * narrowed to the camera that triggered it and excludes nothing.
@@ -60839,7 +61088,8 @@ var NC_SYSTEM_EVENT_FAMILY_FILTERS = {
60839
61088
  "device-events": [
60840
61089
  "deviceIds",
60841
61090
  "excludeDeviceIds",
60842
- "deviceTypes"
61091
+ "deviceTypes",
61092
+ "consumableLowPercent"
60843
61093
  ],
60844
61094
  system: ["nodeIds", "packageNames"],
60845
61095
  alarm: ["deviceIds"]
@@ -60874,6 +61124,16 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
60874
61124
  label: "Consumables",
60875
61125
  kinds: ["device-consumable-low", "device-consumable-normal"]
60876
61126
  },
61127
+ {
61128
+ id: "tanks",
61129
+ family: "device-events",
61130
+ label: "Tanks",
61131
+ kinds: [
61132
+ "device-tank-low",
61133
+ "device-tank-full",
61134
+ "device-tank-normal"
61135
+ ]
61136
+ },
60877
61137
  {
60878
61138
  id: "detection-blind",
60879
61139
  family: "device-events",
@@ -61016,6 +61276,7 @@ function systemFamilyOfKind(kind) {
61016
61276
  */
61017
61277
  var NO_SENSOR_KIND = "An occupancy edge is not a sensor event: it carries no sensor kind, so this condition can never match one.";
61018
61278
  var NO_EVENT_TOKEN = "An occupancy edge carries no device event-type token — that comes from an event-emitter slice, which a count crossing has none of.";
61279
+ var NO_SENSOR_EDGE = "An occupancy edge is a count crossing, not a sensor reading: it has no raise or clear, so this choice could never narrow it.";
61019
61280
  var OCCUPANCY_SOURCE = "An occupancy edge is always produced by the pipeline, so any other source can never match and “pipeline” narrows nothing.";
61020
61281
  var AUDIO_SOURCE = "A confirmed sound window is always stamped as an audio subject, so any other source can never match.";
61021
61282
  var SOUND_NOT_A_PICTURE = "A sound rule fires on what the camera HEARD. Its subject carries no detection, so this condition can never match one — name the sounds in the Sound condition instead.";
@@ -61047,6 +61308,7 @@ var NC_RULE_KIND_SPECS = [
61047
61308
  excluded: {
61048
61309
  sensorKinds: NO_SENSOR_KIND,
61049
61310
  eventTypeTokens: NO_EVENT_TOKEN,
61311
+ sensorTransition: NO_SENSOR_EDGE,
61050
61312
  source: OCCUPANCY_SOURCE
61051
61313
  },
61052
61314
  editable: true,
@@ -61500,11 +61762,121 @@ function isScheduleActive(schedule, atMs) {
61500
61762
  return schedule.invert === true ? !inside : inside;
61501
61763
  }
61502
61764
  //#endregion
61765
+ //#region src/notification/alarm-membership.ts
61766
+ var ARMED_WORD_PREFIX = "armed_";
61767
+ var ALARM_PANEL_CAP = "alarm-panel";
61768
+ var ALARM_PANEL_TRIGGER_METHOD = "trigger";
61769
+ /**
61770
+ * The sensor-event kinds that are OPENINGS to the alarm — the keys of the
61771
+ * engine's opening table (`alarm-openings.ts`, which is checked against this
61772
+ * list with `satisfies`). Only these carry an opening edge the alarm refuses.
61773
+ */
61774
+ var NC_ALARM_OPENING_EVENT_KINDS = [
61775
+ "contact",
61776
+ "cover",
61777
+ "lock"
61778
+ ];
61779
+ /**
61780
+ * A rule belongs to the alarm when its `deviceState` gate names ANY device's
61781
+ * `armed_*` word, or an ENABLED `onTrigger` sequence pulls the alarm panel's
61782
+ * trigger (D630).
61783
+ */
61784
+ function ruleBelongsToAlarm(rule) {
61785
+ if ((rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX))) return true;
61786
+ 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));
61787
+ }
61788
+ function isOpeningKind(kind) {
61789
+ const k = kind.trim().toLowerCase();
61790
+ return NC_ALARM_OPENING_EVENT_KINDS.some((opening) => opening === k);
61791
+ }
61792
+ /**
61793
+ * An alarm rule over openings only, asking for the CLEAR: the alarm edge
61794
+ * refuses every closing, and the chosen edge refuses every opening, so nothing
61795
+ * is left (D640 × D630). Not refused on save — alarm membership is derived and
61796
+ * can change with an action edit — but every editor must SAY it.
61797
+ */
61798
+ function sensorTransitionNeverFires(rule) {
61799
+ const kinds = rule.conditions.sensorKinds ?? [];
61800
+ if (rule.conditions.sensorTransition !== "cleared" || kinds.length === 0) return false;
61801
+ if (!kinds.every(isOpeningKind)) return false;
61802
+ return ruleBelongsToAlarm(rule);
61803
+ }
61804
+ /** The warning an editor shows for {@link sensorTransitionNeverFires}. */
61805
+ var NC_SENSOR_TRANSITION_NEVER_FIRES_WARNING = "This rule belongs to the alarm, and an alarm rule fires only when an opening OPENS. Set to “only when it clears”, it can never fire.";
61806
+ //#endregion
61807
+ //#region src/notification/sensor-raised.ts
61808
+ /**
61809
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
61810
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
61811
+ *
61812
+ * A device-event rule over a flood sensor notified on BOTH edges: water
61813
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
61814
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
61815
+ *
61816
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
61817
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
61818
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
61819
+ * table), and a sensor event names its kind, not its cap. So the table here is
61820
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
61821
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
61822
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
61823
+ * light, and offering it there would be a knob with a misleading label.
61824
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
61825
+ * absent from the source table, so it is absent here.
61826
+ *
61827
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
61828
+ * button, a cover), or a slice whose field is missing or not a boolean. The
61829
+ * engine then does NOT filter — a flood alert must not go silent because one
61830
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
61831
+ *
61832
+ * Pure, RN-safe: the viewer imports the support rule from here.
61833
+ */
61834
+ /** Event kind → the boolean field of its cap slice that means "raised". */
61835
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
61836
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
61837
+ if (field === void 0 || descriptor.category !== "sensor") return [];
61838
+ return [[descriptor.kind, field]];
61839
+ })));
61840
+ /** The sensor kinds `sensorTransition` means something for. */
61841
+ var NC_SENSOR_TRANSITION_KINDS = Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
61842
+ function normaliseKind(kind) {
61843
+ return kind.trim().toLowerCase();
61844
+ }
61845
+ /**
61846
+ * true = the sensor is raised (water, smoke, open, tampered…), false = it
61847
+ * cleared, undefined = this kind has no raise or the slice cannot say.
61848
+ */
61849
+ function sensorRaisedOf(kind, value) {
61850
+ if (value === null) return void 0;
61851
+ const field = NC_SENSOR_RAISED_FIELD_BY_KIND[normaliseKind(kind)];
61852
+ if (field === void 0) return void 0;
61853
+ const raw = Object.entries(value).find(([key]) => key === field)?.[1];
61854
+ return typeof raw === "boolean" ? raw : void 0;
61855
+ }
61856
+ /**
61857
+ * THE SUPPORT RULE, shared by both editors: "Notify when" is offered only when
61858
+ * the rule names at least one sensor kind and EVERY kind it names has a raise.
61859
+ * A mixed rule (flood + doorbell) would hear "only when it clears" as "never
61860
+ * for the doorbell" in the operator's head while the engine lets the doorbell
61861
+ * through — so the choice is not offered, and an editor prunes it on save.
61862
+ */
61863
+ function sensorTransitionOffered(sensorKinds) {
61864
+ if (sensorKinds === void 0 || sensorKinds.length === 0) return false;
61865
+ return sensorKinds.every((kind) => NC_SENSOR_TRANSITION_KINDS.includes(normaliseKind(kind)));
61866
+ }
61867
+ /** What an editor tells the operator when it is about to prune the choice. */
61868
+ var NC_SENSOR_TRANSITION_UNSUPPORTED_REASON = "Only a sensor that raises and clears (water, smoke, gas, CO, vibration, tamper, motion, contact) can fire on one edge. Pick only such sensor kinds, or the rule fires on every change.";
61869
+ //#endregion
61503
61870
  //#region src/notification/template-vars.ts
61504
61871
  var RULE_BODY_ONLY = ["body"];
61505
61872
  var RULE_TEXT = ["title", "body"];
61506
61873
  var BATTERY = ["device-battery-low", "device-battery-normal"];
61507
61874
  var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
61875
+ var TANK = [
61876
+ "device-tank-low",
61877
+ "device-tank-full",
61878
+ "device-tank-normal"
61879
+ ];
61508
61880
  var UPDATES = [
61509
61881
  "addon-update-available",
61510
61882
  "server-update-available",
@@ -61530,6 +61902,7 @@ var DEVICE_EVENTS = [
61530
61902
  "device-enabled",
61531
61903
  ...BATTERY,
61532
61904
  ...CONSUMABLE,
61905
+ ...TANK,
61533
61906
  "stream-online",
61534
61907
  "stream-offline",
61535
61908
  "detection-blind",
@@ -61827,6 +62200,17 @@ var NC_TEMPLATE_VARS = [
61827
62200
  kinds: ["system"],
61828
62201
  systemEventKinds: [...CONSUMABLE]
61829
62202
  },
62203
+ {
62204
+ name: "tank",
62205
+ label: "Tank",
62206
+ description: "Which tank: clean water, dirty water, detergent or dust bin.",
62207
+ example: "clean water",
62208
+ group: "system",
62209
+ families: ["rule"],
62210
+ fields: [...RULE_TEXT],
62211
+ kinds: ["system"],
62212
+ systemEventKinds: [...TANK]
62213
+ },
61830
62214
  {
61831
62215
  name: "profile",
61832
62216
  label: "Stream profile",
@@ -62219,6 +62603,38 @@ var NC_TEMPLATE_VARS = [
62219
62603
  fields: [...RULE_TEXT]
62220
62604
  }
62221
62605
  ];
62606
+ /**
62607
+ * System-event kinds the SERVED catalog holds back (D642): the kinds the hub
62608
+ * produces before the installed viewers can parse them.
62609
+ *
62610
+ * The viewer parses `getTemplateCatalog` WHOLE, with its own strict
62611
+ * `NcSystemEventKindSchema` on every descriptor's `systemEventKinds`, so one
62612
+ * kind it does not know — in `{{deviceName}}`, which every device event names —
62613
+ * empties every {{var}} picker on every phone (the same trap
62614
+ * `NcTemplateVarKindSchema` froze the rule kinds for). The in-process catalog
62615
+ * keeps them: the producer parity and the preview render read it.
62616
+ *
62617
+ * SELF-EXPIRING: `scripts/check-nc-mirrors-in-sync.ts` reads this list as the
62618
+ * viewer's allowance and fails the day the viewer mirrors one of these kinds —
62619
+ * delete it from here then, and the served catalog names it again.
62620
+ */
62621
+ var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
62622
+ "device-tank-low",
62623
+ "device-tank-full",
62624
+ "device-tank-normal"
62625
+ ];
62626
+ /**
62627
+ * The catalog as `getTemplateCatalog` SERVES it: every descriptor, with the
62628
+ * held-back kinds removed from its `systemEventKinds`. A descriptor that named
62629
+ * only held-back kinds is served with an empty list — offered to no system
62630
+ * rule an installed viewer can author, which is exactly true.
62631
+ */
62632
+ function servedTemplateVars() {
62633
+ return NC_TEMPLATE_VARS.map((d) => d.systemEventKinds === void 0 ? d : {
62634
+ ...d,
62635
+ systemEventKinds: d.systemEventKinds.filter((kind) => !NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS.includes(kind))
62636
+ });
62637
+ }
62222
62638
  /** The caption fields see a fixed, smaller vocabulary (`timelapsePreviewText`,
62223
62639
  * `summaryCaptionText`). Declared here rather than as `fields` on every
62224
62640
  * descriptor so the whole rule stays in one line of code. */
@@ -65910,6 +66326,7 @@ exports.NATIVE_LEASE_TILE_BUDGET_KEY = NATIVE_LEASE_TILE_BUDGET_KEY;
65910
66326
  exports.NAVIGATION_ACTION_CATALOG = NAVIGATION_ACTION_CATALOG;
65911
66327
  exports.NAVIGATION_LIGHT_LEVEL_MAX = NAVIGATION_LIGHT_LEVEL_MAX;
65912
66328
  exports.NAVIGATION_LIGHT_LEVEL_MIN = NAVIGATION_LIGHT_LEVEL_MIN;
66329
+ exports.NC_ALARM_OPENING_EVENT_KINDS = NC_ALARM_OPENING_EVENT_KINDS;
65913
66330
  exports.NC_ALARM_SYSTEM_EVENT_KINDS = NC_ALARM_SYSTEM_EVENT_KINDS;
65914
66331
  exports.NC_AUDIO_CONFIRM_HITS_DEFAULT = NC_AUDIO_CONFIRM_HITS_DEFAULT;
65915
66332
  exports.NC_AUDIO_CONFIRM_HITS_MAX = NC_AUDIO_CONFIRM_HITS_MAX;
@@ -65937,6 +66354,9 @@ exports.NC_CONFIRM_DEFAULT_MAX_IMAGE_PX = NC_CONFIRM_DEFAULT_MAX_IMAGE_PX;
65937
66354
  exports.NC_CONFIRM_DEFAULT_TIMEOUT_MS = NC_CONFIRM_DEFAULT_TIMEOUT_MS;
65938
66355
  exports.NC_CONFIRM_MAX_TIMEOUT_MS = NC_CONFIRM_MAX_TIMEOUT_MS;
65939
66356
  exports.NC_CONFIRM_MIN_TIMEOUT_MS = NC_CONFIRM_MIN_TIMEOUT_MS;
66357
+ exports.NC_CONSUMABLE_LOW_PERCENT_DEFAULT = NC_CONSUMABLE_LOW_PERCENT_DEFAULT;
66358
+ exports.NC_CONSUMABLE_LOW_PERCENT_MAX = NC_CONSUMABLE_LOW_PERCENT_MAX;
66359
+ exports.NC_CONSUMABLE_SYSTEM_EVENT_KINDS = NC_CONSUMABLE_SYSTEM_EVENT_KINDS;
65940
66360
  exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
65941
66361
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
65942
66362
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
@@ -65946,6 +66366,10 @@ exports.NC_RULE_CLEARABLE_KEYS = NC_RULE_CLEARABLE_KEYS;
65946
66366
  exports.NC_RULE_EDITOR_SECTION_ORDER = NC_RULE_EDITOR_SECTION_ORDER;
65947
66367
  exports.NC_RULE_KIND_SPECS = NC_RULE_KIND_SPECS;
65948
66368
  exports.NC_RULE_SECTIONS = NC_RULE_SECTIONS;
66369
+ exports.NC_SENSOR_RAISED_FIELD_BY_KIND = NC_SENSOR_RAISED_FIELD_BY_KIND;
66370
+ exports.NC_SENSOR_TRANSITION_KINDS = NC_SENSOR_TRANSITION_KINDS;
66371
+ exports.NC_SENSOR_TRANSITION_NEVER_FIRES_WARNING = NC_SENSOR_TRANSITION_NEVER_FIRES_WARNING;
66372
+ exports.NC_SENSOR_TRANSITION_UNSUPPORTED_REASON = NC_SENSOR_TRANSITION_UNSUPPORTED_REASON;
65949
66373
  exports.NC_SERVER_PACKAGE_NAME = NC_SERVER_PACKAGE_NAME;
65950
66374
  exports.NC_SNOOZE_MAX_MINUTES = NC_SNOOZE_MAX_MINUTES;
65951
66375
  exports.NC_SYSTEM_DELIVERY = NC_SYSTEM_DELIVERY;
@@ -65956,7 +66380,9 @@ exports.NC_SYSTEM_EVENT_FAMILY_FILTERS = NC_SYSTEM_EVENT_FAMILY_FILTERS;
65956
66380
  exports.NC_SYSTEM_EVENT_FILTER_KEYS = NC_SYSTEM_EVENT_FILTER_KEYS;
65957
66381
  exports.NC_SYSTEM_EVENT_KIND_GROUP_SPECS = NC_SYSTEM_EVENT_KIND_GROUP_SPECS;
65958
66382
  exports.NC_SYSTEM_RULE_KINDS = NC_SYSTEM_RULE_KINDS;
66383
+ exports.NC_TANK_SYSTEM_EVENT_KINDS = NC_TANK_SYSTEM_EVENT_KINDS;
65959
66384
  exports.NC_TAXONOMY = NC_TAXONOMY;
66385
+ exports.NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS;
65960
66386
  exports.NC_TEMPLATE_VARS = NC_TEMPLATE_VARS;
65961
66387
  exports.NC_UPDATE_EVENT_PACKAGE_NAMES = NC_UPDATE_EVENT_PACKAGE_NAMES;
65962
66388
  exports.NETWORK_LINK_TYPES = NETWORK_LINK_TYPES;
@@ -66015,6 +66441,8 @@ exports.NcRuleActionsSchema = NcRuleActionsSchema;
66015
66441
  exports.NcRuleClearableKeySchema = NcRuleClearableKeySchema;
66016
66442
  exports.NcRuleInputSchema = NcRuleInputSchema;
66017
66443
  exports.NcRuleKindSchema = NcRuleKindSchema;
66444
+ exports.NcRuleLastRunSchema = NcRuleLastRunSchema;
66445
+ exports.NcRuleLastRunsSchema = NcRuleLastRunsSchema;
66018
66446
  exports.NcRuleNotificationButtonSchema = NcRuleNotificationButtonSchema;
66019
66447
  exports.NcRulePatchSchema = NcRulePatchSchema;
66020
66448
  exports.NcRuleSchema = NcRuleSchema;
@@ -66023,6 +66451,7 @@ exports.NcRuleUpdateInputSchema = NcRuleUpdateInputSchema;
66023
66451
  exports.NcSceneConditionSchema = NcSceneConditionSchema;
66024
66452
  exports.NcScheduleSchema = NcScheduleSchema;
66025
66453
  exports.NcScheduleWindowSchema = NcScheduleWindowSchema;
66454
+ exports.NcSensorTransitionSchema = NcSensorTransitionSchema;
66026
66455
  exports.NcSnoozeInputSchema = NcSnoozeInputSchema;
66027
66456
  exports.NcSnoozeSchema = NcSnoozeSchema;
66028
66457
  exports.NcSnoozeScopeSchema = NcSnoozeScopeSchema;
@@ -66602,6 +67031,7 @@ exports.conditionExclusionReason = conditionExclusionReason;
66602
67031
  exports.conditionVisibleForKind = conditionVisibleForKind;
66603
67032
  exports.connectionTestCapability = connectionTestCapability;
66604
67033
  exports.connectivityCapability = connectivityCapability;
67034
+ exports.consumableLowPercentOf = consumableLowPercentOf;
66605
67035
  exports.consumablesCapability = consumablesCapability;
66606
67036
  exports.contactCapability = contactCapability;
66607
67037
  exports.controlCapability = controlCapability;
@@ -66926,6 +67356,7 @@ exports.resolveStreamMaps = require_canonical_hash.resolveStreamMaps;
66926
67356
  exports.resolveVariantModelId = resolveVariantModelId;
66927
67357
  exports.resolveViewableDeviceIds = resolveViewableDeviceIds;
66928
67358
  exports.roleSpec = roleSpec;
67359
+ exports.ruleBelongsToAlarm = ruleBelongsToAlarm;
66929
67360
  exports.ruleEditorSectionsForKind = ruleEditorSectionsForKind;
66930
67361
  exports.ruleKindOf = ruleKindOf;
66931
67362
  exports.ruleKindSpec = ruleKindSpec;
@@ -66946,6 +67377,10 @@ exports.scoreRuntimes = scoreRuntimes;
66946
67377
  exports.scriptRunnerCapability = scriptRunnerCapability;
66947
67378
  exports.searchDisplayIcons = searchDisplayIcons;
66948
67379
  exports.selectAssignedProfileSlots = require_sleep.selectAssignedProfileSlots;
67380
+ exports.sensorRaisedOf = sensorRaisedOf;
67381
+ exports.sensorTransitionNeverFires = sensorTransitionNeverFires;
67382
+ exports.sensorTransitionOffered = sensorTransitionOffered;
67383
+ exports.servedTemplateVars = servedTemplateVars;
66949
67384
  exports.serverManagementCapability = serverManagementCapability;
66950
67385
  exports.setByPath = setByPath;
66951
67386
  exports.settingsStoreCapability = settingsStoreCapability;