@camstack/addon-post-analysis 1.2.371 → 1.2.373

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.
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.375",
39
+ version: "1.2.378",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.293",
84
+ version: "1.2.296",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-aWakBGm3.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-Cy2RokDO.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -22974,61 +22974,52 @@ function frozenConfirmPolicy(rule, isSystemEvent) {
22974
22974
  };
22975
22975
  }
22976
22976
  //#endregion
22977
+ //#region src/notification-center/device-data-removal.ts
22978
+ var LIST_LIMIT = 2e4;
22979
+ /** Which devices hold rows, and how many, per store. Throws when a read may be truncated. */
22980
+ async function listNcDeviceData(store) {
22981
+ const out = [];
22982
+ for (const { key, collection } of NC_DEVICE_ROW_COLLECTIONS) {
22983
+ const name = key.replace(/^nc-/, "");
22984
+ const rows = await store.query.query({
22985
+ collection,
22986
+ filter: { limit: LIST_LIMIT },
22987
+ columns: ["deviceId"]
22988
+ });
22989
+ if (rows.length >= LIST_LIMIT) throw new Error(`notification-center ${name}: ${rows.length} rows - possibly truncated`);
22990
+ const counts = /* @__PURE__ */ new Map();
22991
+ for (const row of rows) {
22992
+ const id = row.data["deviceId"];
22993
+ if (typeof id === "number" && Number.isInteger(id) && id > 0) counts.set(id, (counts.get(id) ?? 0) + 1);
22994
+ }
22995
+ for (const [deviceId, count] of counts) out.push({
22996
+ store: name,
22997
+ deviceId,
22998
+ count
22999
+ });
23000
+ }
23001
+ return out;
23002
+ }
23003
+ //#endregion
22977
23004
  //#region src/notification-center/device-mute-store.ts
23005
+ /** The per-device settings key a muted camera carries. Absent IS not-muted. */
23006
+ var NOTIFICATION_MUTED_KEY = "notificationMuted";
22978
23007
  /**
22979
- * @durable class=config owner=notification-center
22980
- * write="an admin muting a camera (`notificationRules.setDeviceMuted` → `setMuted(id,
22981
- * true)`) writes exactly one row, `{ id: String(deviceId), deviceId, mutedAt }`.
22982
- * Nothing else writes here — there is no `muted: false` row, absence IS not-muted"
22983
- * retention="unmute DELETES the row, and that is the only removal — no sweep, no
22984
- * expiry, no bound (the mute is indefinite by design, unlike a snooze). Losing the
22985
- * table un-silences every camera the operator muted, and the notification centre
22986
- * starts delivering for them without anyone asking."
22987
- */
22988
- var NC_DEVICE_MUTES_COLLECTION = "notification-center:device-mutes";
22989
- var NC_DEVICE_MUTES_COLUMNS = [
22990
- (
22991
- /** `String(deviceId)` — one row per muted camera; unmute DELETES the row. */
22992
- {
22993
- name: "id",
22994
- type: "TEXT",
22995
- primaryKey: true,
22996
- notNull: true
22997
- }),
22998
- {
22999
- name: "deviceId",
23000
- type: "INTEGER",
23001
- notNull: true
23002
- },
23003
- (
23004
- /** When an admin muted it — the only thing an audit ever wants to know. */
23005
- {
23006
- name: "mutedAt",
23007
- type: "INTEGER",
23008
- notNull: true
23009
- })
23010
- ];
23011
- /**
23012
- * Absence of a row is "not muted"; there is no `muted: false` row. That keeps
23013
- * the table proportional to what the operator actually silenced, and makes the
23014
- * hot-path read a `Set.has`.
23008
+ * Absence of a setting is "not muted"; there is no `muted: false` value. That
23009
+ * keeps the store proportional to what the operator actually silenced, makes the
23010
+ * hot-path read a `Set.has`, and lets the central device delete (D865) take the
23011
+ * mute with the device without a handler.
23015
23012
  */
23016
23013
  var NcDeviceMuteStore = class {
23017
23014
  muted = /* @__PURE__ */ new Set();
23018
- store;
23015
+ settings;
23019
23016
  logger;
23020
23017
  now;
23021
23018
  constructor(deps) {
23022
- this.store = deps.store;
23019
+ this.settings = deps.settings;
23023
23020
  this.logger = deps.logger;
23024
23021
  this.now = deps.now ?? (() => Date.now());
23025
23022
  }
23026
- static async declare(store) {
23027
- await store.declareCollection.mutate({
23028
- collection: NC_DEVICE_MUTES_COLLECTION,
23029
- columns: [...NC_DEVICE_MUTES_COLUMNS]
23030
- });
23031
- }
23032
23023
  /**
23033
23024
  * (Re)hydrate. A failed load KEEPS what is already in memory rather than
23034
23025
  * clearing it: dropping every mute on a transient store error would start
@@ -23037,23 +23028,12 @@ var NcDeviceMuteStore = class {
23037
23028
  */
23038
23029
  async load() {
23039
23030
  try {
23040
- const rows = await this.store.query.query({
23041
- collection: NC_DEVICE_MUTES_COLLECTION,
23042
- filter: { limit: 1e4 }
23043
- });
23031
+ const ids = await this.settings.listDeviceStoreIds();
23032
+ const blobs = await this.settings.readDeviceStoreBatch(ids);
23044
23033
  const next = /* @__PURE__ */ new Set();
23045
- let skipped = 0;
23046
- for (const row of rows) {
23047
- const raw = row.data["deviceId"];
23048
- if (typeof raw !== "number" || !Number.isInteger(raw)) {
23049
- skipped += 1;
23050
- continue;
23051
- }
23052
- next.add(raw);
23053
- }
23034
+ for (const [deviceId, blob] of blobs) if (blob[NOTIFICATION_MUTED_KEY] !== void 0) next.add(deviceId);
23054
23035
  this.muted.clear();
23055
23036
  for (const id of next) this.muted.add(id);
23056
- if (skipped > 0) this.logger.warn("device-mute rows skipped as malformed — they silence NOTHING", { meta: { skipped } });
23057
23037
  } catch (err) {
23058
23038
  this.logger.warn("device-mute load failed — keeping the mutes already in memory", { meta: {
23059
23039
  error: String(err),
@@ -23075,23 +23055,11 @@ var NcDeviceMuteStore = class {
23075
23055
  * hot path believing a camera is silenced when the next boot will not.
23076
23056
  */
23077
23057
  async setMuted(deviceId, muted) {
23078
- const id = String(deviceId);
23079
23058
  if (muted) {
23080
- await this.store.set.mutate({
23081
- collection: NC_DEVICE_MUTES_COLLECTION,
23082
- key: id,
23083
- value: {
23084
- id,
23085
- deviceId,
23086
- mutedAt: this.now()
23087
- }
23088
- });
23059
+ await this.settings.writeDeviceStore(deviceId, { [NOTIFICATION_MUTED_KEY]: { mutedAt: this.now() } });
23089
23060
  this.muted.add(deviceId);
23090
23061
  } else {
23091
- await this.store.delete.mutate({
23092
- collection: NC_DEVICE_MUTES_COLLECTION,
23093
- key: id
23094
- });
23062
+ await this.settings.writeDeviceStore(deviceId, { [NOTIFICATION_MUTED_KEY]: void 0 });
23095
23063
  this.muted.delete(deviceId);
23096
23064
  }
23097
23065
  }
@@ -40227,7 +40195,7 @@ var NotificationCenter = class {
40227
40195
  ...deps.now !== void 0 ? { now: deps.now } : {}
40228
40196
  });
40229
40197
  this.deviceMutes = new NcDeviceMuteStore({
40230
- store: deps.store,
40198
+ settings: deps.deviceSettings,
40231
40199
  logger: this.logger.child("device-mute"),
40232
40200
  ...deps.now !== void 0 ? { now: deps.now } : {}
40233
40201
  });
@@ -40488,6 +40456,10 @@ var NotificationCenter = class {
40488
40456
  get ruleAiStore() {
40489
40457
  return this.ruleAi;
40490
40458
  }
40459
+ /** Which devices the notification centre holds data for, per store. */
40460
+ listDeviceData() {
40461
+ return listNcDeviceData(this.deps.store);
40462
+ }
40491
40463
  /**
40492
40464
  * Declare every Notification Center collection (idempotent, boot-time).
40493
40465
  *
@@ -40504,7 +40476,6 @@ var NotificationCenter = class {
40504
40476
  await NcAlertStore.declare(store);
40505
40477
  await NcStateSummaryService.declare(store);
40506
40478
  await NcSnoozeStore.declare(store);
40507
- await NcDeviceMuteStore.declare(store);
40508
40479
  await NcSequenceStore.declare(store);
40509
40480
  await TimelapseStore.declare(store);
40510
40481
  await NcSummaryStore.declare(store);
@@ -41793,6 +41764,10 @@ var NotificationCenter = class {
41793
41764
  const system = incoming.subject.systemEvent;
41794
41765
  if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
41795
41766
  if (system !== void 0) this.updateLandings.observe(system, this.now());
41767
+ if (system !== void 0 && system.kind === "addon-crash-loop") {
41768
+ this.logger.debug("addon-crash-loop is pushed by the hub alert centre, not by a rule", { meta: { subject: system.subject } });
41769
+ return;
41770
+ }
41796
41771
  const observation = system !== void 0 ? ledgeredLiveness(system) : null;
41797
41772
  if (observation !== null) {
41798
41773
  const previous = this.liveness.stateOf(observation.subject);
@@ -80947,6 +80922,10 @@ function createAnalyticsRemovalHook(deps) {
80947
80922
  deviceId,
80948
80923
  count: 1
80949
80924
  });
80925
+ for (const entry of await deps.listNotificationData()) out.push({
80926
+ ...entry,
80927
+ store: `notification-${entry.store}`
80928
+ });
80950
80929
  return out;
80951
80930
  }
80952
80931
  };
@@ -106532,6 +106511,18 @@ Object.freeze({
106532
106511
  {
106533
106512
  "path": "swingHorizontal",
106534
106513
  "valueType": "boolean"
106514
+ },
106515
+ {
106516
+ "path": "minTemp",
106517
+ "valueType": "number"
106518
+ },
106519
+ {
106520
+ "path": "maxTemp",
106521
+ "valueType": "number"
106522
+ },
106523
+ {
106524
+ "path": "tempStep",
106525
+ "valueType": "number"
106535
106526
  }
106536
106527
  ],
106537
106528
  "color": [
@@ -115463,9 +115454,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
115463
115454
  await refreshDeviceFacts();
115464
115455
  return deviceLocations.get(deviceId) ?? null;
115465
115456
  };
115457
+ const deviceSettings = this.ctxIfReady?.settings;
115458
+ if (deviceSettings === void 0) throw new Error("notification centre: the addon has no per-device settings");
115466
115459
  this.notificationCenter = new NotificationCenter({
115467
115460
  pruneWindows: () => this.pruneWindows(),
115468
115461
  store: api.settingsStore,
115462
+ deviceSettings,
115469
115463
  logger: logger.child("NotificationCenter"),
115470
115464
  listUsers: async () => {
115471
115465
  return (await api.userManagement.listUsers.query()).map((u) => ({
@@ -117444,7 +117438,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
117444
117438
  evictSensorEvents: async (deviceId, cutoffMs) => await this.sensorEventStore?.evictBefore(deviceId, cutoffMs) ?? 0,
117445
117439
  eventCountsByDevice: async () => await this.eventStore?.countByDevice() ?? /* @__PURE__ */ new Map(),
117446
117440
  trackDeviceIds: async () => await this.trackStore?.listDeviceIds() ?? [],
117447
- removeNotificationRows: async (deviceId) => await this.notificationCenter?.removeDeviceData(deviceId) ?? {}
117441
+ removeNotificationRows: async (deviceId) => await this.notificationCenter?.removeDeviceData(deviceId) ?? {},
117442
+ listNotificationData: async () => await this.notificationCenter?.listDeviceData() ?? []
117448
117443
  });
117449
117444
  return this.removalHookImpl;
117450
117445
  }
@@ -118976,7 +118971,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
118976
118971
  }
118977
118972
  /**
118978
118973
  * The addon's ONE read of its own per-device blob
118979
- * (`pipeline-analytics:addon-devices`).
118974
+ * (the shared per-device settings, D870).
118980
118975
  *
118981
118976
  * A chokepoint rather than six scattered `ctx.settings.readDeviceStore(…)`
118982
118977
  * calls, for two reasons that are the same reason: the census can name which
@@ -1,4 +1,4 @@
1
- import { $ as MotionTriggeredBySchema, $i as union, $n as deriveRecordingMode, $r as ruleAutoTriggersAlarm, A as DetectionSourceSchema, Ai as createEvent, An as addonWidgetsSourceCapability, Ar as isSystemEventSummaryRule, At as NcConditionDescriptorSchema, B as FailureCounters, Bi as array, Bn as audioModeOf, Br as pipelineAnalyticsCapability, Bt as NcScheduleSchema, C as CoverStateSchema, Ci as canonicalHash, Cn as TimelapseRuleSchema, Cr as isKnownTemplateVar, Ct as NC_TAXONOMY, D as DETECTION_MACRO_CLASSES, Di as errMsg, Dn as TrackSideSchema, Dr as isRootSuppliableStepId, Dt as NcAlarmSensorsInputSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as DeviceType, En as TrackRelationSchema, Er as isRetiredClearKey, Et as NcAlarmPostTriggeredActionSchema, F as EVENT_KIND_BY_CAP, Fi as nodePin, Fn as applyNonBlockingEdit, Fr as memoizePinnedFfmpeg, Ft as NcRuleInputSchema, G as LockStateSchema, Gi as number, Gn as cloneAlarmSettings, Gr as renameAlarmProfile, Gt as NcStateSummarySectionIdSchema, H as LabelAttributionSchema, Hi as discriminatedUnion, Hn as buildViewerLink, Hr as readDeviceStateFrom, Ht as NcSnoozeInputSchema, I as EVENT_OWNER_TYPES, In as applySecureOnArmEdit, Ir as notificationRulesCapability, It as NcRulePatchSchema, J as MAX_BIRTH_LATENCY_PROXY_MS, Ji as record, Jn as cosineSimilarity$1, Jr as resolveModeDelays, Jt as NcTemplatePreviewInputSchema, K as MAX_ARCHIVED_DEBUG_NOTES, Ki as object, Kn as consumableLowPercentOf, Kr as resolveClipSearchMinScore, Kt as NcSystemEventKindSchema, L as EXPORT_DENSE_MAX_RANGES, Li as sleep$1, Ln as armRemovalReconcile, Lr as occupancyScope, Lt as NcRuleSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as defineCustomActions, Mn as alarmPanelCapability, Mr as kebabToCamel, Mt as NcLegacyRuleActionSequenceSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as hydrateSchema, Nn as applyBlockingEdit, Nr as linkedStepOf, Nt as NcRuleActionSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as BaseAddon, On as TrackSourceSchema, Or as isScheduleActive, Ot as NcAlarmSensorsSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as isDeviceScopedCap, Pn as applyModeDelaysEdits, Pr as mayWriteToLocation, Pt as NcRuleActionsSchema, Q as MediaFileKindEnum, Qi as tuple, Qn as derivePostTriggeredAction, Qr as rootSupplyServes, Qt as OpsLogEntrySchema, R as FIRST_LEVEL_MACRO_CLASSES, Ri as ZodObject, Rn as assertTimelapseCadences, Rt as NcRuleTargetSchema, S as ClearTrackRelationResultSchema, Si as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Sn as TimelapseRulePatchSchema, Sr as isEventOwnerType, St as NC_TANK_SYSTEM_EVENT_KINDS, T as DEFAULT_EVENT_COLOR, Ti as isSourceCap, Tn as TrackRelationKindSchema, Tr as isMediaOwnerType, Tt as NcAlarmConfigSchema, U as ListTrackRelationsInputSchema, Ui as literal, Un as calendarEventLocalSpan, Ur as readMotionTriggeredBy, Ut as NcSnoozeSchema, V as LABEL_SUPPLY_MACRO, Vi as boolean, Vn as buildEventKindDescriptor, Vr as plateGalleryCapability, Vt as NcSharedSequenceSchema, W as ListTrackRelationsResultSchema, Wi as looseObject, Wn as classifyRetrainReview, Wr as readTimelapseGeneratedAt, Wt as NcSnoozeSuppressedSchema, X as MAX_TRACK_RELATIONS, Xi as string, Xn as deleteAlarmProfile, Xt as NcTemplateVarDescriptorSchema, Y as MAX_TRACK_PURITIES, Yi as strictObject, Yn as createAlarmProfile, Yr as resolveModeNotifyPolicy, Yt as NcTemplatePreviewSchema, Z as MOTION_CLOSE_AFTER_MS, Zi as templateLiteral, Zn as deriveDetailCropRect, Zr as retrainRevisionConflictMessage, Zt as OPS_LOG_DEFAULT_LIMIT, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as templateCarriesSummary, _n as SetTrackPurityResultSchema, _r as isAppLinkBase, _t as NC_GROUPING_MAX_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as secureActuationFor, an as RetrainStatusSchema, ar as embeddingRefusalOf, at as NC_ALARM_SYSTEM_EVENT_KINDS, b as COCO_TO_MACRO, bi as viewerLinkBaseOf, bn as TRACK_RELATIONS_DEFAULT_LIMIT, br as isDetectionMacroClass, bt as NC_SNOOZE_MAX_MINUTES, c as readClusterModelPinStrict, ci as storageOccupancyCapability, cn as SCENE_DEFAULT_ANCHOR_THRESHOLD, cr as encodeVectorBase64, ct as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, d as APPEARANCE_BLOCKS, di as syncActiveAlarmProfileSettings, dn as SCENE_DEFAULT_QUIET_SECONDS, dr as evictionPolicyOfLocation, dt as NC_CLIP_RETENTION_MAX_SEC, ea as unknown, ei as ruleBelongsToAlarm$1, en as RECORDING_EXPORT_MAX_READ_BYTES, er as describeSystemEventDeliveryProblem, et as NC_ALARM_DEFAULT_PROFILE_ID, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as systemEventDeliveryProblems, fn as SCENE_DEFAULT_UNCOVERED_POLICY, fr as faceGalleryCapability, ft as NC_CONDITION_CATALOG, g as ArchivedDebugNoteSchema, gi as systemEventSummarySplitOf, gn as SetTrackPurityInputSchema, gr as isAlarmControllableCap, gt as NC_GROUPING_DEFAULT_SEC, h as AlarmArmModeSchema, hi as systemEventSummaryScheduleOf, hn as SceneMonitorSchema, hr as gifFromMp4Args, ht as NC_DEFAULT_SNOOZE_MINUTES, ii as sceneMonitorCapability, in as RetrainOriginSchema, it as NC_ALARM_NOTIFY_FIELDS, j as EMBEDDING_MODEL_FAILED, ji as customAction, jn as alarmDeviceSecureOnArm, jr as isVectorIndexDimConflict, jt as NcConditionsSchema, k as DeclaredDevices, ki as CamProfileSchema, kn as activateAlarmProfile, kr as isSystemDelivery, kt as NcAlarmSettingsPatchSchema, l as resolveClusterModelPin, li as subKindsOf, ln as SCENE_DEFAULT_CHECK_INTERVAL_SEC, lr as ensureAlarmProfiles, lt as NC_CLIP_POST_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as systemEventFilterApplies, mn as SCENE_RESET_RECAPTURES, mr as gifDeriveSpeed, mt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, ni as ruleGroupingApplies, nn as RebuildObjectEmbeddingsRefusalSchema, nr as deviceRemovalHookCapability, nt as NC_ALARM_DELAY_FIELDS, o as CLUSTER_MODEL_OWNER_ADDON_ID, oi as sensorRaisedOf, on as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ot as NC_ALARM_TRIGGERED_MAX_SEC, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as systemEventFamilyProblems, pn as SCENE_DIVERGED, pr as failureContributionCapability, pt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, q as MAX_BIRTH_DECISION_RECORDS, qi as partialRecord, qn as coreBlockIdFromAddonId, qr as resolveLocationMode, qt as NcTaxonomySchema, ri as ruleKindOf, rn as RetrainDismissedSchema, rt as NC_ALARM_DELAY_MAX_SEC, s as FACE_EMBEDDING_STEP_ID, si as servedTemplateVars, sn as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, st as NC_AUDIO_CONFIRM_HITS_DEFAULT, t as BUILTIN_CLIP_MODELS, ta as EventCategory, ti as ruleDeviceCompatCapQueries, tn as RECORDING_SOURCE_CAMSTACK_ADDON, tr as describeSystemEventFamilyProblem, tt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, u as APPEARANCE_BIN_COUNT, ui as summarizeReview, un as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, ur as eventKindLabel, ut as NC_CLIP_PRE_ROLL_DEFAULT_SEC, v as BaseDevice, vi as templateVarsFor, vn as SetTrackRelationInputSchema, vr as isAudioClassName, vt as NC_GROUPING_MIN_SEC, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as evaluateSensorEdge, wn as TrackPuritySchema, wr as isLabelSupplyStepId, wt as NC_TEMPLATE_VARS, x as ClearTrackRelationInputSchema, xi as zoneAnalyticsCapability, xn as TimelapseRuleInputSchema, xr as isEventKindLabelKey, xt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, y as BirthDecisionRecordSchema, yi as vectorDimFromBase64, yn as TIMELAPSE_DENSE_FLOOR_SEC, yr as isDefaultAlarmProfileId, yt as NC_SENSOR_TRANSITION_KINDS, z as FULL_IMAGE_BBOX, zi as _enum, zn as audioMetricsCapability, zr as pickFaceRescoreIntervalMinutes, zt as NcRuleUpdateInputSchema } from "../clip-model-registry-DnJSHCsn.mjs";
1
+ import { $ as MotionTriggeredBySchema, $i as union, $n as deriveRecordingMode, $r as ruleAutoTriggersAlarm, A as DetectionSourceSchema, Ai as createEvent, An as addonWidgetsSourceCapability, Ar as isSystemEventSummaryRule, At as NcConditionDescriptorSchema, B as FailureCounters, Bi as array, Bn as audioModeOf, Br as pipelineAnalyticsCapability, Bt as NcScheduleSchema, C as CoverStateSchema, Ci as canonicalHash, Cn as TimelapseRuleSchema, Cr as isKnownTemplateVar, Ct as NC_TAXONOMY, D as DETECTION_MACRO_CLASSES, Di as errMsg, Dn as TrackSideSchema, Dr as isRootSuppliableStepId, Dt as NcAlarmSensorsInputSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as DeviceType, En as TrackRelationSchema, Er as isRetiredClearKey, Et as NcAlarmPostTriggeredActionSchema, F as EVENT_KIND_BY_CAP, Fi as nodePin, Fn as applyNonBlockingEdit, Fr as memoizePinnedFfmpeg, Ft as NcRuleInputSchema, G as LockStateSchema, Gi as number, Gn as cloneAlarmSettings, Gr as renameAlarmProfile, Gt as NcStateSummarySectionIdSchema, H as LabelAttributionSchema, Hi as discriminatedUnion, Hn as buildViewerLink, Hr as readDeviceStateFrom, Ht as NcSnoozeInputSchema, I as EVENT_OWNER_TYPES, In as applySecureOnArmEdit, Ir as notificationRulesCapability, It as NcRulePatchSchema, J as MAX_BIRTH_LATENCY_PROXY_MS, Ji as record, Jn as cosineSimilarity$1, Jr as resolveModeDelays, Jt as NcTemplatePreviewInputSchema, K as MAX_ARCHIVED_DEBUG_NOTES, Ki as object, Kn as consumableLowPercentOf, Kr as resolveClipSearchMinScore, Kt as NcSystemEventKindSchema, L as EXPORT_DENSE_MAX_RANGES, Li as sleep$1, Ln as armRemovalReconcile, Lr as occupancyScope, Lt as NcRuleSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as defineCustomActions, Mn as alarmPanelCapability, Mr as kebabToCamel, Mt as NcLegacyRuleActionSequenceSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as hydrateSchema, Nn as applyBlockingEdit, Nr as linkedStepOf, Nt as NcRuleActionSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as BaseAddon, On as TrackSourceSchema, Or as isScheduleActive, Ot as NcAlarmSensorsSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as isDeviceScopedCap, Pn as applyModeDelaysEdits, Pr as mayWriteToLocation, Pt as NcRuleActionsSchema, Q as MediaFileKindEnum, Qi as tuple, Qn as derivePostTriggeredAction, Qr as rootSupplyServes, Qt as OpsLogEntrySchema, R as FIRST_LEVEL_MACRO_CLASSES, Ri as ZodObject, Rn as assertTimelapseCadences, Rt as NcRuleTargetSchema, S as ClearTrackRelationResultSchema, Si as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Sn as TimelapseRulePatchSchema, Sr as isEventOwnerType, St as NC_TANK_SYSTEM_EVENT_KINDS, T as DEFAULT_EVENT_COLOR, Ti as isSourceCap, Tn as TrackRelationKindSchema, Tr as isMediaOwnerType, Tt as NcAlarmConfigSchema, U as ListTrackRelationsInputSchema, Ui as literal, Un as calendarEventLocalSpan, Ur as readMotionTriggeredBy, Ut as NcSnoozeSchema, V as LABEL_SUPPLY_MACRO, Vi as boolean, Vn as buildEventKindDescriptor, Vr as plateGalleryCapability, Vt as NcSharedSequenceSchema, W as ListTrackRelationsResultSchema, Wi as looseObject, Wn as classifyRetrainReview, Wr as readTimelapseGeneratedAt, Wt as NcSnoozeSuppressedSchema, X as MAX_TRACK_RELATIONS, Xi as string, Xn as deleteAlarmProfile, Xt as NcTemplateVarDescriptorSchema, Y as MAX_TRACK_PURITIES, Yi as strictObject, Yn as createAlarmProfile, Yr as resolveModeNotifyPolicy, Yt as NcTemplatePreviewSchema, Z as MOTION_CLOSE_AFTER_MS, Zi as templateLiteral, Zn as deriveDetailCropRect, Zr as retrainRevisionConflictMessage, Zt as OPS_LOG_DEFAULT_LIMIT, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as templateCarriesSummary, _n as SetTrackPurityResultSchema, _r as isAppLinkBase, _t as NC_GROUPING_MAX_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as secureActuationFor, an as RetrainStatusSchema, ar as embeddingRefusalOf, at as NC_ALARM_SYSTEM_EVENT_KINDS, b as COCO_TO_MACRO, bi as viewerLinkBaseOf, bn as TRACK_RELATIONS_DEFAULT_LIMIT, br as isDetectionMacroClass, bt as NC_SNOOZE_MAX_MINUTES, c as readClusterModelPinStrict, ci as storageOccupancyCapability, cn as SCENE_DEFAULT_ANCHOR_THRESHOLD, cr as encodeVectorBase64, ct as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, d as APPEARANCE_BLOCKS, di as syncActiveAlarmProfileSettings, dn as SCENE_DEFAULT_QUIET_SECONDS, dr as evictionPolicyOfLocation, dt as NC_CLIP_RETENTION_MAX_SEC, ea as unknown, ei as ruleBelongsToAlarm$1, en as RECORDING_EXPORT_MAX_READ_BYTES, er as describeSystemEventDeliveryProblem, et as NC_ALARM_DEFAULT_PROFILE_ID, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as systemEventDeliveryProblems, fn as SCENE_DEFAULT_UNCOVERED_POLICY, fr as faceGalleryCapability, ft as NC_CONDITION_CATALOG, g as ArchivedDebugNoteSchema, gi as systemEventSummarySplitOf, gn as SetTrackPurityInputSchema, gr as isAlarmControllableCap, gt as NC_GROUPING_DEFAULT_SEC, h as AlarmArmModeSchema, hi as systemEventSummaryScheduleOf, hn as SceneMonitorSchema, hr as gifFromMp4Args, ht as NC_DEFAULT_SNOOZE_MINUTES, ii as sceneMonitorCapability, in as RetrainOriginSchema, it as NC_ALARM_NOTIFY_FIELDS, j as EMBEDDING_MODEL_FAILED, ji as customAction, jn as alarmDeviceSecureOnArm, jr as isVectorIndexDimConflict, jt as NcConditionsSchema, k as DeclaredDevices, ki as CamProfileSchema, kn as activateAlarmProfile, kr as isSystemDelivery, kt as NcAlarmSettingsPatchSchema, l as resolveClusterModelPin, li as subKindsOf, ln as SCENE_DEFAULT_CHECK_INTERVAL_SEC, lr as ensureAlarmProfiles, lt as NC_CLIP_POST_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as systemEventFilterApplies, mn as SCENE_RESET_RECAPTURES, mr as gifDeriveSpeed, mt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, ni as ruleGroupingApplies, nn as RebuildObjectEmbeddingsRefusalSchema, nr as deviceRemovalHookCapability, nt as NC_ALARM_DELAY_FIELDS, o as CLUSTER_MODEL_OWNER_ADDON_ID, oi as sensorRaisedOf, on as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, ot as NC_ALARM_TRIGGERED_MAX_SEC, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as systemEventFamilyProblems, pn as SCENE_DIVERGED, pr as failureContributionCapability, pt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, q as MAX_BIRTH_DECISION_RECORDS, qi as partialRecord, qn as coreBlockIdFromAddonId, qr as resolveLocationMode, qt as NcTaxonomySchema, ri as ruleKindOf, rn as RetrainDismissedSchema, rt as NC_ALARM_DELAY_MAX_SEC, s as FACE_EMBEDDING_STEP_ID, si as servedTemplateVars, sn as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, st as NC_AUDIO_CONFIRM_HITS_DEFAULT, t as BUILTIN_CLIP_MODELS, ta as EventCategory, ti as ruleDeviceCompatCapQueries, tn as RECORDING_SOURCE_CAMSTACK_ADDON, tr as describeSystemEventFamilyProblem, tt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, u as APPEARANCE_BIN_COUNT, ui as summarizeReview, un as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, ur as eventKindLabel, ut as NC_CLIP_PRE_ROLL_DEFAULT_SEC, v as BaseDevice, vi as templateVarsFor, vn as SetTrackRelationInputSchema, vr as isAudioClassName, vt as NC_GROUPING_MIN_SEC, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as evaluateSensorEdge, wn as TrackPuritySchema, wr as isLabelSupplyStepId, wt as NC_TEMPLATE_VARS, x as ClearTrackRelationInputSchema, xi as zoneAnalyticsCapability, xn as TimelapseRuleInputSchema, xr as isEventKindLabelKey, xt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, y as BirthDecisionRecordSchema, yi as vectorDimFromBase64, yn as TIMELAPSE_DENSE_FLOOR_SEC, yr as isDefaultAlarmProfileId, yt as NC_SENSOR_TRANSITION_KINDS, z as FULL_IMAGE_BBOX, zi as _enum, zn as audioMetricsCapability, zr as pickFaceRescoreIntervalMinutes, zt as NcRuleUpdateInputSchema } from "../clip-model-registry-jeFu3pma.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs$1 from "node:fs";
4
4
  import { createWriteStream, promises } from "node:fs";
@@ -22951,61 +22951,52 @@ function frozenConfirmPolicy(rule, isSystemEvent) {
22951
22951
  };
22952
22952
  }
22953
22953
  //#endregion
22954
+ //#region src/notification-center/device-data-removal.ts
22955
+ var LIST_LIMIT = 2e4;
22956
+ /** Which devices hold rows, and how many, per store. Throws when a read may be truncated. */
22957
+ async function listNcDeviceData(store) {
22958
+ const out = [];
22959
+ for (const { key, collection } of NC_DEVICE_ROW_COLLECTIONS) {
22960
+ const name = key.replace(/^nc-/, "");
22961
+ const rows = await store.query.query({
22962
+ collection,
22963
+ filter: { limit: LIST_LIMIT },
22964
+ columns: ["deviceId"]
22965
+ });
22966
+ if (rows.length >= LIST_LIMIT) throw new Error(`notification-center ${name}: ${rows.length} rows - possibly truncated`);
22967
+ const counts = /* @__PURE__ */ new Map();
22968
+ for (const row of rows) {
22969
+ const id = row.data["deviceId"];
22970
+ if (typeof id === "number" && Number.isInteger(id) && id > 0) counts.set(id, (counts.get(id) ?? 0) + 1);
22971
+ }
22972
+ for (const [deviceId, count] of counts) out.push({
22973
+ store: name,
22974
+ deviceId,
22975
+ count
22976
+ });
22977
+ }
22978
+ return out;
22979
+ }
22980
+ //#endregion
22954
22981
  //#region src/notification-center/device-mute-store.ts
22982
+ /** The per-device settings key a muted camera carries. Absent IS not-muted. */
22983
+ var NOTIFICATION_MUTED_KEY = "notificationMuted";
22955
22984
  /**
22956
- * @durable class=config owner=notification-center
22957
- * write="an admin muting a camera (`notificationRules.setDeviceMuted` → `setMuted(id,
22958
- * true)`) writes exactly one row, `{ id: String(deviceId), deviceId, mutedAt }`.
22959
- * Nothing else writes here — there is no `muted: false` row, absence IS not-muted"
22960
- * retention="unmute DELETES the row, and that is the only removal — no sweep, no
22961
- * expiry, no bound (the mute is indefinite by design, unlike a snooze). Losing the
22962
- * table un-silences every camera the operator muted, and the notification centre
22963
- * starts delivering for them without anyone asking."
22964
- */
22965
- var NC_DEVICE_MUTES_COLLECTION = "notification-center:device-mutes";
22966
- var NC_DEVICE_MUTES_COLUMNS = [
22967
- (
22968
- /** `String(deviceId)` — one row per muted camera; unmute DELETES the row. */
22969
- {
22970
- name: "id",
22971
- type: "TEXT",
22972
- primaryKey: true,
22973
- notNull: true
22974
- }),
22975
- {
22976
- name: "deviceId",
22977
- type: "INTEGER",
22978
- notNull: true
22979
- },
22980
- (
22981
- /** When an admin muted it — the only thing an audit ever wants to know. */
22982
- {
22983
- name: "mutedAt",
22984
- type: "INTEGER",
22985
- notNull: true
22986
- })
22987
- ];
22988
- /**
22989
- * Absence of a row is "not muted"; there is no `muted: false` row. That keeps
22990
- * the table proportional to what the operator actually silenced, and makes the
22991
- * hot-path read a `Set.has`.
22985
+ * Absence of a setting is "not muted"; there is no `muted: false` value. That
22986
+ * keeps the store proportional to what the operator actually silenced, makes the
22987
+ * hot-path read a `Set.has`, and lets the central device delete (D865) take the
22988
+ * mute with the device without a handler.
22992
22989
  */
22993
22990
  var NcDeviceMuteStore = class {
22994
22991
  muted = /* @__PURE__ */ new Set();
22995
- store;
22992
+ settings;
22996
22993
  logger;
22997
22994
  now;
22998
22995
  constructor(deps) {
22999
- this.store = deps.store;
22996
+ this.settings = deps.settings;
23000
22997
  this.logger = deps.logger;
23001
22998
  this.now = deps.now ?? (() => Date.now());
23002
22999
  }
23003
- static async declare(store) {
23004
- await store.declareCollection.mutate({
23005
- collection: NC_DEVICE_MUTES_COLLECTION,
23006
- columns: [...NC_DEVICE_MUTES_COLUMNS]
23007
- });
23008
- }
23009
23000
  /**
23010
23001
  * (Re)hydrate. A failed load KEEPS what is already in memory rather than
23011
23002
  * clearing it: dropping every mute on a transient store error would start
@@ -23014,23 +23005,12 @@ var NcDeviceMuteStore = class {
23014
23005
  */
23015
23006
  async load() {
23016
23007
  try {
23017
- const rows = await this.store.query.query({
23018
- collection: NC_DEVICE_MUTES_COLLECTION,
23019
- filter: { limit: 1e4 }
23020
- });
23008
+ const ids = await this.settings.listDeviceStoreIds();
23009
+ const blobs = await this.settings.readDeviceStoreBatch(ids);
23021
23010
  const next = /* @__PURE__ */ new Set();
23022
- let skipped = 0;
23023
- for (const row of rows) {
23024
- const raw = row.data["deviceId"];
23025
- if (typeof raw !== "number" || !Number.isInteger(raw)) {
23026
- skipped += 1;
23027
- continue;
23028
- }
23029
- next.add(raw);
23030
- }
23011
+ for (const [deviceId, blob] of blobs) if (blob[NOTIFICATION_MUTED_KEY] !== void 0) next.add(deviceId);
23031
23012
  this.muted.clear();
23032
23013
  for (const id of next) this.muted.add(id);
23033
- if (skipped > 0) this.logger.warn("device-mute rows skipped as malformed — they silence NOTHING", { meta: { skipped } });
23034
23014
  } catch (err) {
23035
23015
  this.logger.warn("device-mute load failed — keeping the mutes already in memory", { meta: {
23036
23016
  error: String(err),
@@ -23052,23 +23032,11 @@ var NcDeviceMuteStore = class {
23052
23032
  * hot path believing a camera is silenced when the next boot will not.
23053
23033
  */
23054
23034
  async setMuted(deviceId, muted) {
23055
- const id = String(deviceId);
23056
23035
  if (muted) {
23057
- await this.store.set.mutate({
23058
- collection: NC_DEVICE_MUTES_COLLECTION,
23059
- key: id,
23060
- value: {
23061
- id,
23062
- deviceId,
23063
- mutedAt: this.now()
23064
- }
23065
- });
23036
+ await this.settings.writeDeviceStore(deviceId, { [NOTIFICATION_MUTED_KEY]: { mutedAt: this.now() } });
23066
23037
  this.muted.add(deviceId);
23067
23038
  } else {
23068
- await this.store.delete.mutate({
23069
- collection: NC_DEVICE_MUTES_COLLECTION,
23070
- key: id
23071
- });
23039
+ await this.settings.writeDeviceStore(deviceId, { [NOTIFICATION_MUTED_KEY]: void 0 });
23072
23040
  this.muted.delete(deviceId);
23073
23041
  }
23074
23042
  }
@@ -40204,7 +40172,7 @@ var NotificationCenter = class {
40204
40172
  ...deps.now !== void 0 ? { now: deps.now } : {}
40205
40173
  });
40206
40174
  this.deviceMutes = new NcDeviceMuteStore({
40207
- store: deps.store,
40175
+ settings: deps.deviceSettings,
40208
40176
  logger: this.logger.child("device-mute"),
40209
40177
  ...deps.now !== void 0 ? { now: deps.now } : {}
40210
40178
  });
@@ -40465,6 +40433,10 @@ var NotificationCenter = class {
40465
40433
  get ruleAiStore() {
40466
40434
  return this.ruleAi;
40467
40435
  }
40436
+ /** Which devices the notification centre holds data for, per store. */
40437
+ listDeviceData() {
40438
+ return listNcDeviceData(this.deps.store);
40439
+ }
40468
40440
  /**
40469
40441
  * Declare every Notification Center collection (idempotent, boot-time).
40470
40442
  *
@@ -40481,7 +40453,6 @@ var NotificationCenter = class {
40481
40453
  await NcAlertStore.declare(store);
40482
40454
  await NcStateSummaryService.declare(store);
40483
40455
  await NcSnoozeStore.declare(store);
40484
- await NcDeviceMuteStore.declare(store);
40485
40456
  await NcSequenceStore.declare(store);
40486
40457
  await TimelapseStore.declare(store);
40487
40458
  await NcSummaryStore.declare(store);
@@ -41770,6 +41741,10 @@ var NotificationCenter = class {
41770
41741
  const system = incoming.subject.systemEvent;
41771
41742
  if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
41772
41743
  if (system !== void 0) this.updateLandings.observe(system, this.now());
41744
+ if (system !== void 0 && system.kind === "addon-crash-loop") {
41745
+ this.logger.debug("addon-crash-loop is pushed by the hub alert centre, not by a rule", { meta: { subject: system.subject } });
41746
+ return;
41747
+ }
41773
41748
  const observation = system !== void 0 ? ledgeredLiveness(system) : null;
41774
41749
  if (observation !== null) {
41775
41750
  const previous = this.liveness.stateOf(observation.subject);
@@ -80852,6 +80827,10 @@ function createAnalyticsRemovalHook(deps) {
80852
80827
  deviceId,
80853
80828
  count: 1
80854
80829
  });
80830
+ for (const entry of await deps.listNotificationData()) out.push({
80831
+ ...entry,
80832
+ store: `notification-${entry.store}`
80833
+ });
80855
80834
  return out;
80856
80835
  }
80857
80836
  };
@@ -106437,6 +106416,18 @@ Object.freeze({
106437
106416
  {
106438
106417
  "path": "swingHorizontal",
106439
106418
  "valueType": "boolean"
106419
+ },
106420
+ {
106421
+ "path": "minTemp",
106422
+ "valueType": "number"
106423
+ },
106424
+ {
106425
+ "path": "maxTemp",
106426
+ "valueType": "number"
106427
+ },
106428
+ {
106429
+ "path": "tempStep",
106430
+ "valueType": "number"
106440
106431
  }
106441
106432
  ],
106442
106433
  "color": [
@@ -115368,9 +115359,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
115368
115359
  await refreshDeviceFacts();
115369
115360
  return deviceLocations.get(deviceId) ?? null;
115370
115361
  };
115362
+ const deviceSettings = this.ctxIfReady?.settings;
115363
+ if (deviceSettings === void 0) throw new Error("notification centre: the addon has no per-device settings");
115371
115364
  this.notificationCenter = new NotificationCenter({
115372
115365
  pruneWindows: () => this.pruneWindows(),
115373
115366
  store: api.settingsStore,
115367
+ deviceSettings,
115374
115368
  logger: logger.child("NotificationCenter"),
115375
115369
  listUsers: async () => {
115376
115370
  return (await api.userManagement.listUsers.query()).map((u) => ({
@@ -117349,7 +117343,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
117349
117343
  evictSensorEvents: async (deviceId, cutoffMs) => await this.sensorEventStore?.evictBefore(deviceId, cutoffMs) ?? 0,
117350
117344
  eventCountsByDevice: async () => await this.eventStore?.countByDevice() ?? /* @__PURE__ */ new Map(),
117351
117345
  trackDeviceIds: async () => await this.trackStore?.listDeviceIds() ?? [],
117352
- removeNotificationRows: async (deviceId) => await this.notificationCenter?.removeDeviceData(deviceId) ?? {}
117346
+ removeNotificationRows: async (deviceId) => await this.notificationCenter?.removeDeviceData(deviceId) ?? {},
117347
+ listNotificationData: async () => await this.notificationCenter?.listDeviceData() ?? []
117353
117348
  });
117354
117349
  return this.removalHookImpl;
117355
117350
  }
@@ -118881,7 +118876,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
118881
118876
  }
118882
118877
  /**
118883
118878
  * The addon's ONE read of its own per-device blob
118884
- * (`pipeline-analytics:addon-devices`).
118879
+ * (the shared per-device settings, D870).
118885
118880
  *
118886
118881
  * A chokepoint rather than six scattered `ctx.settings.readDeviceStore(…)`
118887
118882
  * calls, for two reasons that are the same reason: the census can name which
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DzBqyp65.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-ujfvEENv.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.371",
3
+ "version": "1.2.373",
4
4
  "description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",