@camstack/addon-post-analysis 1.2.281 → 1.2.282

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.
@@ -5360,7 +5360,7 @@ var ZodIssueCode = {
5360
5360
  var ZodFirstPartyTypeKind;
5361
5361
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5362
5362
  //#endregion
5363
- //#region ../types/dist/sleep-D_4W7xuQ.mjs
5363
+ //#region ../types/dist/sleep-DU7mu4wb.mjs
5364
5364
  /**
5365
5365
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5366
5366
  * window to float samples (D455).
@@ -18178,6 +18178,16 @@ var NcCrossingSchema = _enum([
18178
18178
  "exit",
18179
18179
  "any"
18180
18180
  ]);
18181
+ /**
18182
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
18183
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
18184
+ * - `cleared` — it went back: dry, clear, closed.
18185
+ * - absent — both edges, which is what every rule authored before this did.
18186
+ *
18187
+ * There is no third member for "both": absence already says it, and a value
18188
+ * that means the same as absence is a second way to write one rule.
18189
+ */
18190
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
18181
18191
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
18182
18192
  var NcZoneConditionSchema = object({
18183
18193
  ids: array(string().min(1)).min(1),
@@ -18367,6 +18377,20 @@ var NcConditionsSchema = object({
18367
18377
  scene: NcSceneConditionSchema.optional(),
18368
18378
  /** Device scope — absent = all devices. */
18369
18379
  devices: array(number()).optional(),
18380
+ /**
18381
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
18382
+ * subject ids {@link devices} is judged on (the camera, the sensor that
18383
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
18384
+ * linked camera is excluded exactly where an include would have tested it.
18385
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
18386
+ *
18387
+ * Not for `system-event` rules — those carry their own
18388
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
18389
+ * Editors present the two lists as ONE field (All | Only these | All except
18390
+ * these); the engine evaluates each independently, so a legacy rule
18391
+ * carrying both still means "these, minus those".
18392
+ */
18393
+ devicesExclude: array(number()).min(1).optional(),
18370
18394
  /** Detector class names (any overlap with the record's class set). */
18371
18395
  classes: array(string().min(1)).optional(),
18372
18396
  /** Veto classes — any overlap fails the rule. */
@@ -18500,6 +18524,16 @@ var NcConditionsSchema = object({
18500
18524
  */
18501
18525
  sensorKinds: array(string().min(1)).optional(),
18502
18526
  /**
18527
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
18528
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
18529
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
18530
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
18531
+ * the condition fails open towards notifying, never towards silence. Editors
18532
+ * offer it only when every chosen {@link sensorKinds} member has a raise
18533
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
18534
+ */
18535
+ sensorTransition: NcSensorTransitionSchema.optional(),
18536
+ /**
18503
18537
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
18504
18538
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
18505
18539
  * when the subject's phase does not match (a subject always carries a phase
@@ -19118,6 +19152,7 @@ var NcConditionDescriptorSchema = object({
19118
19152
  /** Editor widget the UI renders — never hardcode per-condition forms. */
19119
19153
  valueType: _enum([
19120
19154
  "deviceIdList",
19155
+ "deviceIdExcludeList",
19121
19156
  "stringList",
19122
19157
  "number01",
19123
19158
  "number",
@@ -19128,6 +19163,7 @@ var NcConditionDescriptorSchema = object({
19128
19163
  "plateMatcher",
19129
19164
  "packagePhase",
19130
19165
  "crossingSelect",
19166
+ "sensorTransition",
19131
19167
  "polygonDraw",
19132
19168
  "occupancy",
19133
19169
  "audio",
@@ -19326,6 +19362,21 @@ var NC_CONDITION_CATALOG = [
19326
19362
  phase: "P1",
19327
19363
  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."
19328
19364
  },
19365
+ {
19366
+ id: "devicesExclude",
19367
+ group: "scope",
19368
+ label: "Excluded devices",
19369
+ valueType: "deviceIdExcludeList",
19370
+ operator: "notIn",
19371
+ appliesTo: [
19372
+ "immediate",
19373
+ "track-end",
19374
+ "device-event",
19375
+ "package-event"
19376
+ ],
19377
+ phase: "P1",
19378
+ 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."
19379
+ },
19329
19380
  {
19330
19381
  id: "classes",
19331
19382
  group: "class",
@@ -19582,6 +19633,33 @@ var NC_CONDITION_CATALOG = [
19582
19633
  phase: "P1",
19583
19634
  description: "Raw device event-type tokens (e.g. doorbell press / press_long) from the event-emitter slice; absent on doorbell-pulse / passive sensors."
19584
19635
  },
19636
+ {
19637
+ id: "sensorTransition",
19638
+ group: "device",
19639
+ label: "Notify when",
19640
+ valueType: "sensorTransition",
19641
+ options: [
19642
+ {
19643
+ value: "",
19644
+ label: "It triggers and when it clears",
19645
+ hint: "Every change: water detected, and back to dry."
19646
+ },
19647
+ {
19648
+ value: "raised",
19649
+ label: "Only when it triggers",
19650
+ hint: "Water detected, smoke, a contact opening — never the return to normal."
19651
+ },
19652
+ {
19653
+ value: "cleared",
19654
+ label: "Only when it clears",
19655
+ hint: "Back to dry, smoke gone, a contact closing — never the alert itself."
19656
+ }
19657
+ ],
19658
+ operator: "in",
19659
+ appliesTo: ["device-event"],
19660
+ phase: "P2",
19661
+ 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."
19662
+ },
19585
19663
  {
19586
19664
  id: "packagePhase",
19587
19665
  group: "package",
@@ -19765,6 +19843,28 @@ var NcHistoryFilterSchema = object({
19765
19843
  limit: number().int().min(1).max(500).default(100)
19766
19844
  });
19767
19845
  /**
19846
+ * Every rule's last run, in one answer.
19847
+ *
19848
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19849
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19850
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19851
+ * its row bound: the answer never claims a window it did not read. `null` means
19852
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19853
+ * means "never on record". A UI derives its "not in the last N days" from this
19854
+ * value, never from a constant — and only when the call ANSWERED (D315).
19855
+ */
19856
+ var NcRuleLastRunsSchema = object({
19857
+ runs: array(object({
19858
+ ruleId: string(),
19859
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19860
+ lastAt: number(),
19861
+ status: NcHistoryStatusSchema,
19862
+ /** The camera/device of that row (a digest row files under its first camera). */
19863
+ deviceId: number()
19864
+ })),
19865
+ horizonMs: number().nonnegative().nullable()
19866
+ });
19867
+ /**
19768
19868
  * What a snooze covers. Broader scopes win when several overlap, so one window
19769
19869
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
19770
19870
  * summarising the same silence.
@@ -19946,6 +20046,14 @@ var NcAlarmModeCoverageSchema = object({
19946
20046
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19947
20047
  deviceIds: array(number().int()),
19948
20048
  /**
20049
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
20050
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
20051
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
20052
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
20053
+ * Defaulted so an answer computed before this field parses as "none".
20054
+ */
20055
+ allDevicesExcept: array(number().int()).default([]),
20056
+ /**
19949
20057
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19950
20058
  * excludes it.
19951
20059
  *
@@ -20169,6 +20277,15 @@ var notificationRulesCapability = {
20169
20277
  */
20170
20278
  getHistory: method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }),
20171
20279
  /**
20280
+ * The newest delivery of every rule, in ONE bounded read of the outbox —
20281
+ * the "Last run" column. Never a `getHistory` per rule. A rule with no
20282
+ * delivery inside `horizonMs` is absent from `runs`; see
20283
+ * {@link NcRuleLastRunsSchema} for what absent means. A store that cannot
20284
+ * answer FAILS the call rather than answering "nothing ran". Admin-only,
20285
+ * like `getHistory`, of which it is a projection.
20286
+ */
20287
+ getRuleLastRuns: method(object({}), NcRuleLastRunsSchema, { auth: "admin" }),
20288
+ /**
20172
20289
  * Mint a fresh, externally-reachable URL for one artefact a history row
20173
20290
  * named in {@link NcHistoryEntrySchema.shape.artifactIds} — the SAME
20174
20291
  * signed-link mechanism the dispatcher uses to attach media to an
@@ -44946,6 +45063,12 @@ Object.freeze({
44946
45063
  addonId: null,
44947
45064
  access: "view"
44948
45065
  },
45066
+ "notificationRules.getRuleLastRuns": {
45067
+ capName: "notification-rules",
45068
+ capScope: "system",
45069
+ addonId: null,
45070
+ access: "view"
45071
+ },
44949
45072
  "notificationRules.getTemplateCatalog": {
44950
45073
  capName: "notification-rules",
44951
45074
  capScope: "system",
@@ -50870,6 +50993,66 @@ function isScheduleActive(schedule, atMs) {
50870
50993
  }
50871
50994
  return schedule.invert === true ? !inside : inside;
50872
50995
  }
50996
+ var ARMED_WORD_PREFIX = "armed_";
50997
+ var ALARM_PANEL_CAP = "alarm-panel";
50998
+ var ALARM_PANEL_TRIGGER_METHOD = "trigger";
50999
+ /**
51000
+ * A rule belongs to the alarm when its `deviceState` gate names ANY device's
51001
+ * `armed_*` word, or an ENABLED `onTrigger` sequence pulls the alarm panel's
51002
+ * trigger (D630).
51003
+ */
51004
+ function ruleBelongsToAlarm(rule) {
51005
+ if ((rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX))) return true;
51006
+ 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));
51007
+ }
51008
+ /**
51009
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
51010
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
51011
+ *
51012
+ * A device-event rule over a flood sensor notified on BOTH edges: water
51013
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
51014
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
51015
+ *
51016
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
51017
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
51018
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
51019
+ * table), and a sensor event names its kind, not its cap. So the table here is
51020
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
51021
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
51022
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
51023
+ * light, and offering it there would be a knob with a misleading label.
51024
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
51025
+ * absent from the source table, so it is absent here.
51026
+ *
51027
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
51028
+ * button, a cover), or a slice whose field is missing or not a boolean. The
51029
+ * engine then does NOT filter — a flood alert must not go silent because one
51030
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
51031
+ *
51032
+ * Pure, RN-safe: the viewer imports the support rule from here.
51033
+ */
51034
+ /** Event kind → the boolean field of its cap slice that means "raised". */
51035
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
51036
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
51037
+ if (field === void 0 || descriptor.category !== "sensor") return [];
51038
+ return [[descriptor.kind, field]];
51039
+ })));
51040
+ /** The sensor kinds `sensorTransition` means something for. */
51041
+ var NC_SENSOR_TRANSITION_KINDS = Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
51042
+ function normaliseKind(kind) {
51043
+ return kind.trim().toLowerCase();
51044
+ }
51045
+ /**
51046
+ * true = the sensor is raised (water, smoke, open, tampered…), false = it
51047
+ * cleared, undefined = this kind has no raise or the slice cannot say.
51048
+ */
51049
+ function sensorRaisedOf(kind, value) {
51050
+ if (value === null) return void 0;
51051
+ const field = NC_SENSOR_RAISED_FIELD_BY_KIND[normaliseKind(kind)];
51052
+ if (field === void 0) return void 0;
51053
+ const raw = Object.entries(value).find(([key]) => key === field)?.[1];
51054
+ return typeof raw === "boolean" ? raw : void 0;
51055
+ }
50873
51056
  var RULE_BODY_ONLY = ["body"];
50874
51057
  var RULE_TEXT = ["title", "body"];
50875
51058
  var BATTERY = ["device-battery-low", "device-battery-normal"];
@@ -52662,4 +52845,4 @@ function vectorDimFromBase64(encoded) {
52662
52845
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
52663
52846
  }
52664
52847
  //#endregion
52665
- export { RECORDING_EXPORT_MAX_READ_BYTES as $, vectorDimFromBase64 as $t, NC_SNOOZE_MAX_MINUTES as A, isKnownTemplateVar as At, NcRuleTargetSchema as B, pickClusterStepModels as Bt, MAX_BIRTH_DECISION_RECORDS as C, unknown as Cn, evaluateSensorEdge as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, hfModelUrl as Dt, MediaFileKindEnum as E, failureContributionCapability as Et, NcConditionDescriptorSchema as F, kebabToCamel as Ft, NcSnoozeSuppressedSchema as G, resolveLocationMode as Gt, NcScheduleSchema as H, plateGalleryCapability as Ht, NcConditionsSchema as I, mayWriteToLocation as It, NcTemplatePreviewInputSchema as J, storageOccupancyCapability as Jt, NcSystemEventKindSchema as K, resolvePoolMemoryPolicy as Kt, NcRuleInputSchema as L, notificationRulesCapability as Lt, NC_TEMPLATE_VARS as M, isScheduleActive as Mt, NcAlarmConfigSchema as N, isSourceCap as Nt, NC_CONDITION_CATALOG as O, isDetectionMacroClass as Ot, NcAlarmSettingsPatchSchema as P, isSystemDelivery as Pt, PoolMemoryWatchdog as Q, templateVarsFor as Qt, NcRulePatchSchema as R, occupancyScope as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, string as Sn, encodeVectorBase64 as St, MOTION_CLOSE_AFTER_MS as T, faceGalleryCapability as Tt, NcSnoozeInputSchema as U, readDeviceStateFrom as Ut, NcRuleUpdateInputSchema as V, pipelineAnalyticsCapability as Vt, NcSnoozeSchema as W, readTimelapseGeneratedAt as Wt, NcTemplateVarDescriptorSchema as X, systemEventFamilyProblems as Xt, NcTemplatePreviewSchema as Y, subKindsOf as Yt, OpsLogEntrySchema as Z, systemEventFilterApplies as Zt, FIRST_LEVEL_MACRO_CLASSES as _, literal as _n, buildEventKindDescriptor as _t, BirthDecisionRecordSchema as a, DeviceType as an, SCENE_DIVERGED as at, LabelAttributionSchema as b, partialRecord as bn, describeSystemEventFamilyProblem as bt, CoverStateSchema as c, defineCustomActions as cn, TimelapseRuleInputSchema as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, nodePin as dn, TrackSourceSchema as dt, zoneAnalyticsCapability as en, RECORDING_SOURCE_CAMSTACK_ADDON as et, DETECTION_MACRO_CLASSES as f, sleep as fn, addonWidgetsSourceCapability as ft, EVENT_OWNER_TYPES as g, discriminatedUnion as gn, audioModeOf as gt, EVENT_KIND_BY_CAP as h, boolean as hn, audioMetricsCapability as ht, BaseDevice as i, CamProfileSchema as in, SCENE_DEFAULT_UNCOVERED_POLICY as it, NC_TAXONOMY as j, isMediaOwnerType as jt, NC_DEFAULT_SNOOZE_MINUTES as k, isEventOwnerType as kt, DEFAULT_EVENT_COLOR as l, hydrateSchema as ln, TimelapseRulePatchSchema as lt, DeclaredDevices as m, array as mn, assertTimelapseCadences as mt, AlarmArmModeSchema as n, canonicalHash as nn, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as nt, CLUSTER_MODEL_SCOPED_STEPS as o, createEvent as on, SceneMonitorSchema as ot, DETECTION_PIPELINE_CAP_NAME as p, _enum as pn, alarmPanelCapability as pt, NcTaxonomySchema as q, sceneMonitorCapability as qt, ArchivedDebugNoteSchema as r, BaseAddon as rn, SCENE_DEFAULT_ANCHOR_THRESHOLD as rt, COCO_TO_MACRO as s, customAction as sn, TIMELAPSE_DENSE_FLOOR_SEC as st, AUDIO_MACRO_LABELS as t, errMsg as tn, RetrainStatusSchema as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, isDeviceScopedCap as un, TimelapseRuleSchema as ut, FULL_IMAGE_BBOX as v, number as vn, cosineSimilarity as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, EventCategory as wn, evictionPolicyOfLocation as wt, LockStateSchema as x, record as xn, embeddingEncoderCapability as xt, FailureCounters as y, object as yn, deriveRecordingMode as yt, NcRuleSchema as z, parseProcStatus as zt };
52848
+ export { PoolMemoryWatchdog as $, systemEventFamilyProblems as $t, NC_SENSOR_TRANSITION_KINDS as A, isEventOwnerType as At, NcRuleSchema as B, parseProcStatus as Bt, MAX_BIRTH_DECISION_RECORDS as C, partialRecord as Cn, encodeVectorBase64 as Ct, NC_ALARM_SYSTEM_EVENT_KINDS as D, EventCategory as Dn, failureContributionCapability as Dt, MediaFileKindEnum as E, unknown as En, faceGalleryCapability as Et, NcAlarmSettingsPatchSchema as F, isSystemDelivery as Ft, NcSnoozeSchema as G, readTimelapseGeneratedAt as Gt, NcRuleUpdateInputSchema as H, pipelineAnalyticsCapability as Ht, NcConditionDescriptorSchema as I, kebabToCamel as It, NcTaxonomySchema as J, ruleBelongsToAlarm as Jt, NcSnoozeSuppressedSchema as K, resolveLocationMode as Kt, NcConditionsSchema as L, mayWriteToLocation as Lt, NC_TAXONOMY as M, isMediaOwnerType as Mt, NC_TEMPLATE_VARS as N, isScheduleActive as Nt, NC_CONDITION_CATALOG as O, hfModelUrl as Ot, NcAlarmConfigSchema as P, isSourceCap as Pt, OpsLogEntrySchema as Q, subKindsOf as Qt, NcRuleInputSchema as R, notificationRulesCapability as Rt, MAX_ARCHIVED_DEBUG_NOTES as S, object as Sn, embeddingEncoderCapability as St, MOTION_CLOSE_AFTER_MS as T, string as Tn, evictionPolicyOfLocation as Tt, NcScheduleSchema as U, plateGalleryCapability as Ut, NcRuleTargetSchema as V, pickClusterStepModels as Vt, NcSnoozeInputSchema as W, readDeviceStateFrom as Wt, NcTemplatePreviewSchema as X, sensorRaisedOf as Xt, NcTemplatePreviewInputSchema as Y, sceneMonitorCapability as Yt, NcTemplateVarDescriptorSchema as Z, storageOccupancyCapability as Zt, FIRST_LEVEL_MACRO_CLASSES as _, array as _n, audioModeOf as _t, BirthDecisionRecordSchema as a, canonicalHash as an, SCENE_DEFAULT_UNCOVERED_POLICY as at, LabelAttributionSchema as b, literal as bn, deriveRecordingMode as bt, CoverStateSchema as c, DeviceType as cn, TIMELAPSE_DENSE_FLOOR_SEC as ct, DEFAULT_TIMELAPSE_PREVIEW_TEXT as d, defineCustomActions as dn, TimelapseRuleSchema as dt, systemEventFilterApplies as en, RECORDING_EXPORT_MAX_READ_BYTES as et, DETECTION_MACRO_CLASSES as f, hydrateSchema as fn, TrackSourceSchema as ft, EVENT_OWNER_TYPES as g, _enum as gn, audioMetricsCapability as gt, EVENT_KIND_BY_CAP as h, sleep as hn, assertTimelapseCadences as ht, BaseDevice as i, errMsg as in, SCENE_DEFAULT_ANCHOR_THRESHOLD as it, NC_SNOOZE_MAX_MINUTES as j, isKnownTemplateVar as jt, NC_DEFAULT_SNOOZE_MINUTES as k, isDetectionMacroClass as kt, DEFAULT_EVENT_COLOR as l, createEvent as ln, TimelapseRuleInputSchema as lt, DeclaredDevices as m, nodePin as mn, alarmPanelCapability as mt, AlarmArmModeSchema as n, vectorDimFromBase64 as nn, RetrainStatusSchema as nt, CLUSTER_MODEL_SCOPED_STEPS as o, BaseAddon as on, SCENE_DIVERGED as ot, DETECTION_PIPELINE_CAP_NAME as p, isDeviceScopedCap as pn, addonWidgetsSourceCapability as pt, NcSystemEventKindSchema as q, resolvePoolMemoryPolicy as qt, ArchivedDebugNoteSchema as r, zoneAnalyticsCapability as rn, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as rt, COCO_TO_MACRO as s, CamProfileSchema as sn, SceneMonitorSchema as st, AUDIO_MACRO_LABELS as t, templateVarsFor as tn, RECORDING_SOURCE_CAMSTACK_ADDON as tt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as u, customAction as un, TimelapseRulePatchSchema as ut, FULL_IMAGE_BBOX as v, boolean as vn, buildEventKindDescriptor as vt, MAX_BIRTH_LATENCY_PROXY_MS as w, record as wn, evaluateSensorEdge as wt, LockStateSchema as x, number as xn, describeSystemEventFamilyProblem as xt, FailureCounters as y, discriminatedUnion as yn, cosineSimilarity as yt, NcRulePatchSchema as z, occupancyScope as zt };
@@ -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).
@@ -18209,6 +18209,16 @@ var NcCrossingSchema = _enum([
18209
18209
  "exit",
18210
18210
  "any"
18211
18211
  ]);
18212
+ /**
18213
+ * Which edge of a BOOLEAN sensor fires a device-event rule (D640).
18214
+ * - `raised` — the sensor went high: water detected, smoke, open, tampered.
18215
+ * - `cleared` — it went back: dry, clear, closed.
18216
+ * - absent — both edges, which is what every rule authored before this did.
18217
+ *
18218
+ * There is no third member for "both": absence already says it, and a value
18219
+ * that means the same as absence is a second way to write one rule.
18220
+ */
18221
+ var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
18212
18222
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
18213
18223
  var NcZoneConditionSchema = object({
18214
18224
  ids: array(string().min(1)).min(1),
@@ -18398,6 +18408,20 @@ var NcConditionsSchema = object({
18398
18408
  scene: NcSceneConditionSchema.optional(),
18399
18409
  /** Device scope — absent = all devices. */
18400
18410
  devices: array(number()).optional(),
18411
+ /**
18412
+ * Device EXCLUSION (D641) — "every device except these". Judged on the SAME
18413
+ * subject ids {@link devices} is judged on (the camera, the sensor that
18414
+ * fired, a linked set's sensor and row cameras), so a sensor fanned out per
18415
+ * linked camera is excluded exactly where an include would have tested it.
18416
+ * `min(1)`: an empty exclusion excludes nothing and is written as absent.
18417
+ *
18418
+ * Not for `system-event` rules — those carry their own
18419
+ * `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
18420
+ * Editors present the two lists as ONE field (All | Only these | All except
18421
+ * these); the engine evaluates each independently, so a legacy rule
18422
+ * carrying both still means "these, minus those".
18423
+ */
18424
+ devicesExclude: array(number()).min(1).optional(),
18401
18425
  /** Detector class names (any overlap with the record's class set). */
18402
18426
  classes: array(string().min(1)).optional(),
18403
18427
  /** Veto classes — any overlap fails the rule. */
@@ -18531,6 +18555,16 @@ var NcConditionsSchema = object({
18531
18555
  */
18532
18556
  sensorKinds: array(string().min(1)).optional(),
18533
18557
  /**
18558
+ * DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
18559
+ * {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
18560
+ * is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
18561
+ * engine cannot place (a kind with no raise, an unreadable slice) PASSES:
18562
+ * the condition fails open towards notifying, never towards silence. Editors
18563
+ * offer it only when every chosen {@link sensorKinds} member has a raise
18564
+ * (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
18565
+ */
18566
+ sensorTransition: NcSensorTransitionSchema.optional(),
18567
+ /**
18534
18568
  * PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
18535
18569
  * (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
18536
18570
  * when the subject's phase does not match (a subject always carries a phase
@@ -19149,6 +19183,7 @@ var NcConditionDescriptorSchema = object({
19149
19183
  /** Editor widget the UI renders — never hardcode per-condition forms. */
19150
19184
  valueType: _enum([
19151
19185
  "deviceIdList",
19186
+ "deviceIdExcludeList",
19152
19187
  "stringList",
19153
19188
  "number01",
19154
19189
  "number",
@@ -19159,6 +19194,7 @@ var NcConditionDescriptorSchema = object({
19159
19194
  "plateMatcher",
19160
19195
  "packagePhase",
19161
19196
  "crossingSelect",
19197
+ "sensorTransition",
19162
19198
  "polygonDraw",
19163
19199
  "occupancy",
19164
19200
  "audio",
@@ -19357,6 +19393,21 @@ var NC_CONDITION_CATALOG = [
19357
19393
  phase: "P1",
19358
19394
  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
19395
  },
19396
+ {
19397
+ id: "devicesExclude",
19398
+ group: "scope",
19399
+ label: "Excluded devices",
19400
+ valueType: "deviceIdExcludeList",
19401
+ operator: "notIn",
19402
+ appliesTo: [
19403
+ "immediate",
19404
+ "track-end",
19405
+ "device-event",
19406
+ "package-event"
19407
+ ],
19408
+ phase: "P1",
19409
+ 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."
19410
+ },
19360
19411
  {
19361
19412
  id: "classes",
19362
19413
  group: "class",
@@ -19613,6 +19664,33 @@ var NC_CONDITION_CATALOG = [
19613
19664
  phase: "P1",
19614
19665
  description: "Raw device event-type tokens (e.g. doorbell press / press_long) from the event-emitter slice; absent on doorbell-pulse / passive sensors."
19615
19666
  },
19667
+ {
19668
+ id: "sensorTransition",
19669
+ group: "device",
19670
+ label: "Notify when",
19671
+ valueType: "sensorTransition",
19672
+ options: [
19673
+ {
19674
+ value: "",
19675
+ label: "It triggers and when it clears",
19676
+ hint: "Every change: water detected, and back to dry."
19677
+ },
19678
+ {
19679
+ value: "raised",
19680
+ label: "Only when it triggers",
19681
+ hint: "Water detected, smoke, a contact opening — never the return to normal."
19682
+ },
19683
+ {
19684
+ value: "cleared",
19685
+ label: "Only when it clears",
19686
+ hint: "Back to dry, smoke gone, a contact closing — never the alert itself."
19687
+ }
19688
+ ],
19689
+ operator: "in",
19690
+ appliesTo: ["device-event"],
19691
+ phase: "P2",
19692
+ 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."
19693
+ },
19616
19694
  {
19617
19695
  id: "packagePhase",
19618
19696
  group: "package",
@@ -19796,6 +19874,28 @@ var NcHistoryFilterSchema = object({
19796
19874
  limit: number().int().min(1).max(500).default(100)
19797
19875
  });
19798
19876
  /**
19877
+ * Every rule's last run, in one answer.
19878
+ *
19879
+ * A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
19880
+ * back this answer looked, in ms before the hub's now. It is the outbox prune
19881
+ * window (7 days by default, operator-configurable), NARROWED when the read hit
19882
+ * its row bound: the answer never claims a window it did not read. `null` means
19883
+ * the operator keeps the outbox forever AND the read reached its end, so absent
19884
+ * means "never on record". A UI derives its "not in the last N days" from this
19885
+ * value, never from a constant — and only when the call ANSWERED (D315).
19886
+ */
19887
+ var NcRuleLastRunsSchema = object({
19888
+ runs: array(object({
19889
+ ruleId: string(),
19890
+ /** Fire time of the newest row (`createdAt`, epoch ms). */
19891
+ lastAt: number(),
19892
+ status: NcHistoryStatusSchema,
19893
+ /** The camera/device of that row (a digest row files under its first camera). */
19894
+ deviceId: number()
19895
+ })),
19896
+ horizonMs: number().nonnegative().nullable()
19897
+ });
19898
+ /**
19799
19899
  * What a snooze covers. Broader scopes win when several overlap, so one window
19800
19900
  * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
19801
19901
  * summarising the same silence.
@@ -19977,6 +20077,14 @@ var NcAlarmModeCoverageSchema = object({
19977
20077
  /** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
19978
20078
  deviceIds: array(number().int()),
19979
20079
  /**
20080
+ * D641: the ids NOT covered although `allDevices` is true — every unscoped
20081
+ * covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
20082
+ * covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
20083
+ * which is not the same as skipped by a switch: never in `skippedDevices`.
20084
+ * Defaulted so an answer computed before this field parses as "none".
20085
+ */
20086
+ allDevicesExcept: array(number().int()).default([]),
20087
+ /**
19980
20088
  * Devices this mode NAMES but cannot actually arm, each with the switch that
19981
20089
  * excludes it.
19982
20090
  *
@@ -20200,6 +20308,15 @@ var notificationRulesCapability = {
20200
20308
  */
20201
20309
  getHistory: method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }),
20202
20310
  /**
20311
+ * The newest delivery of every rule, in ONE bounded read of the outbox —
20312
+ * the "Last run" column. Never a `getHistory` per rule. A rule with no
20313
+ * delivery inside `horizonMs` is absent from `runs`; see
20314
+ * {@link NcRuleLastRunsSchema} for what absent means. A store that cannot
20315
+ * answer FAILS the call rather than answering "nothing ran". Admin-only,
20316
+ * like `getHistory`, of which it is a projection.
20317
+ */
20318
+ getRuleLastRuns: method(object({}), NcRuleLastRunsSchema, { auth: "admin" }),
20319
+ /**
20203
20320
  * Mint a fresh, externally-reachable URL for one artefact a history row
20204
20321
  * named in {@link NcHistoryEntrySchema.shape.artifactIds} — the SAME
20205
20322
  * signed-link mechanism the dispatcher uses to attach media to an
@@ -44977,6 +45094,12 @@ Object.freeze({
44977
45094
  addonId: null,
44978
45095
  access: "view"
44979
45096
  },
45097
+ "notificationRules.getRuleLastRuns": {
45098
+ capName: "notification-rules",
45099
+ capScope: "system",
45100
+ addonId: null,
45101
+ access: "view"
45102
+ },
44980
45103
  "notificationRules.getTemplateCatalog": {
44981
45104
  capName: "notification-rules",
44982
45105
  capScope: "system",
@@ -50901,6 +51024,66 @@ function isScheduleActive(schedule, atMs) {
50901
51024
  }
50902
51025
  return schedule.invert === true ? !inside : inside;
50903
51026
  }
51027
+ var ARMED_WORD_PREFIX = "armed_";
51028
+ var ALARM_PANEL_CAP = "alarm-panel";
51029
+ var ALARM_PANEL_TRIGGER_METHOD = "trigger";
51030
+ /**
51031
+ * A rule belongs to the alarm when its `deviceState` gate names ANY device's
51032
+ * `armed_*` word, or an ENABLED `onTrigger` sequence pulls the alarm panel's
51033
+ * trigger (D630).
51034
+ */
51035
+ function ruleBelongsToAlarm(rule) {
51036
+ if ((rule.conditions.deviceState?.states ?? []).some((s) => s.trim().toLowerCase().startsWith(ARMED_WORD_PREFIX))) return true;
51037
+ 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));
51038
+ }
51039
+ /**
51040
+ * WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
51041
+ * persists (D640 "a sensor rule fires on the raise, the clear, or both").
51042
+ *
51043
+ * A device-event rule over a flood sensor notified on BOTH edges: water
51044
+ * detected, and back to dry. `NcConditions.sensorTransition` lets the rule
51045
+ * name one edge; this file answers, for one `SensorEvent`, which edge it is.
51046
+ *
51047
+ * NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
51048
+ * already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
51049
+ * (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
51050
+ * table), and a sensor event names its kind, not its cap. So the table here is
51051
+ * DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
51052
+ * Only kinds whose taxonomy category is `sensor` take part — a switch has an
51053
+ * `on` boolean too, but "it triggers / it clears" is not a sentence about a
51054
+ * light, and offering it there would be a knob with a misleading label.
51055
+ * `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
51056
+ * absent from the source table, so it is absent here.
51057
+ *
51058
+ * `undefined` = never guessed: a kind with no raise (a doorbell press, a
51059
+ * button, a cover), or a slice whose field is missing or not a boolean. The
51060
+ * engine then does NOT filter — a flood alert must not go silent because one
51061
+ * read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
51062
+ *
51063
+ * Pure, RN-safe: the viewer imports the support rule from here.
51064
+ */
51065
+ /** Event kind → the boolean field of its cap slice that means "raised". */
51066
+ var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
51067
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
51068
+ if (field === void 0 || descriptor.category !== "sensor") return [];
51069
+ return [[descriptor.kind, field]];
51070
+ })));
51071
+ /** The sensor kinds `sensorTransition` means something for. */
51072
+ var NC_SENSOR_TRANSITION_KINDS = Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
51073
+ function normaliseKind(kind) {
51074
+ return kind.trim().toLowerCase();
51075
+ }
51076
+ /**
51077
+ * true = the sensor is raised (water, smoke, open, tampered…), false = it
51078
+ * cleared, undefined = this kind has no raise or the slice cannot say.
51079
+ */
51080
+ function sensorRaisedOf(kind, value) {
51081
+ if (value === null) return void 0;
51082
+ const field = NC_SENSOR_RAISED_FIELD_BY_KIND[normaliseKind(kind)];
51083
+ if (field === void 0) return void 0;
51084
+ const raw = Object.entries(value).find(([key]) => key === field)?.[1];
51085
+ return typeof raw === "boolean" ? raw : void 0;
51086
+ }
50904
51087
  var RULE_BODY_ONLY = ["body"];
50905
51088
  var RULE_TEXT = ["title", "body"];
50906
51089
  var BATTERY = ["device-battery-low", "device-battery-normal"];
@@ -52891,6 +53074,12 @@ Object.defineProperty(exports, "NC_DEFAULT_SNOOZE_MINUTES", {
52891
53074
  return NC_DEFAULT_SNOOZE_MINUTES;
52892
53075
  }
52893
53076
  });
53077
+ Object.defineProperty(exports, "NC_SENSOR_TRANSITION_KINDS", {
53078
+ enumerable: true,
53079
+ get: function() {
53080
+ return NC_SENSOR_TRANSITION_KINDS;
53081
+ }
53082
+ });
52894
53083
  Object.defineProperty(exports, "NC_SNOOZE_MAX_MINUTES", {
52895
53084
  enumerable: true,
52896
53085
  get: function() {
@@ -53431,12 +53620,24 @@ Object.defineProperty(exports, "resolvePoolMemoryPolicy", {
53431
53620
  return resolvePoolMemoryPolicy;
53432
53621
  }
53433
53622
  });
53623
+ Object.defineProperty(exports, "ruleBelongsToAlarm", {
53624
+ enumerable: true,
53625
+ get: function() {
53626
+ return ruleBelongsToAlarm;
53627
+ }
53628
+ });
53434
53629
  Object.defineProperty(exports, "sceneMonitorCapability", {
53435
53630
  enumerable: true,
53436
53631
  get: function() {
53437
53632
  return sceneMonitorCapability;
53438
53633
  }
53439
53634
  });
53635
+ Object.defineProperty(exports, "sensorRaisedOf", {
53636
+ enumerable: true,
53637
+ get: function() {
53638
+ return sensorRaisedOf;
53639
+ }
53640
+ });
53440
53641
  Object.defineProperty(exports, "sleep", {
53441
53642
  enumerable: true,
53442
53643
  get: function() {
@@ -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-BtqlSCQT.js");
5
+ const require_dist = require("../dist-D0zzdvSd.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs);
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Dt as hfModelUrl, Kt as resolvePoolMemoryPolicy, Q as PoolMemoryWatchdog, rn as BaseAddon, xt as embeddingEncoderCapability, zt as parseProcStatus } from "../dist-DkERzZfY.mjs";
1
+ import { $ as PoolMemoryWatchdog, Bt as parseProcStatus, Ot as hfModelUrl, St as embeddingEncoderCapability, on as BaseAddon, qt as resolvePoolMemoryPolicy } from "../dist-C3zTKShG.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -1,8 +1,8 @@
1
1
  import { a as e, i as t, n, o as r, r as i, t as a } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs";
2
2
  import { t as o } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs";
3
- import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BA7N1yDM.mjs";
3
+ import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DWPDYuJS.mjs";
4
4
  import { n as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs";
5
- import { t as _ } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-OkXjMLHG.mjs";
5
+ import { t as _ } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DT-cQNv-.mjs";
6
6
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js
7
7
  var v = (...e) => e.filter((e, t, n) => !!e && e.trim() !== "" && n.indexOf(e) === t).join(" ").trim(), y = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), b = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), x = (e) => {
8
8
  let t = b(e);
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.255",
21
+ version: "1.2.256",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.204",
36
+ version: "1.2.205",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",