@camstack/addon-post-analysis 1.2.607 → 1.2.609

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.
Files changed (15) hide show
  1. package/dist/{clip-model-registry-VgOXFRFr.js → clip-model-registry-B11yS32V.js} +472 -47
  2. package/dist/{clip-model-registry-CTfe5v5_.mjs → clip-model-registry-BItfTZEx.mjs} +431 -48
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DUNQoBDI.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-LGLlz5Sk.mjs} +3 -3
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-iXmJsQ9Y.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Cm7pZeMH.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-B8mC_3oV.mjs → hostInit-Cko0y33G.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +583 -68
  11. package/dist/pipeline-analytics/index.mjs +583 -68
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-1AlitJg2.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DalpVM1q.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as MediaFileKindEnum, $i as record, $n as deleteAlarmProfile, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as errMsg, An as TrackSideSchema, Ar as isRootSuppliableStepId, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as nodePin, Bn as armRemovalReconcile, Br as occupancyScope, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as viewerLinkBaseOf, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isDetectionMacroClass, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as evaluateSensorEdge, Dn as TrackPuritySchema, Dr as isLabelSupplyStepId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as canonicalHash, En as TimelapseRuleSchema, Er as isKnownTemplateVar, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as customAction, Fn as alarmPanelCapability, Fr as kebabToCamel, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as array, Gn as buildViewerLink, Gr as readDeviceStateFrom, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as sleep$1, Hn as audioMetricsCapability, Hr as pickFaceRescoreIntervalMinutes, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as defineCustomActions, In as applyBlockingEdit, Ir as linkedStepOf, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as literal, Jn as cloneAlarmSettings, Jr as renameAlarmProfile, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as boolean, Kn as calendarEventLocalSpan, Kr as readMotionTriggeredBy, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as defineRecordKinds, Ln as applyModeDelaysEdits, Lr as mayWriteToLocation, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as CamProfileSchema, Mn as activateAlarmProfile, Mr as isSystemDelivery, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as RECORD_MAX_DATA_BYTES, Nn as addonWidgetsSourceCapability, Nr as isSystemEventSummaryRule, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as isSourceCap, On as TrackRelationKindSchema, Or as isMediaOwnerType, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as createEvent, Pn as alarmDeviceSecureOnArm, Pr as isVectorIndexDimConflict, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as partialRecord, Qn as createAlarmProfile, Qr as resolveModeNotifyPolicy, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as hydrateSchema, Rn as applyNonBlockingEdit, Rr as memoizePinnedFfmpeg, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as vectorDimFromBase64, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isDefaultAlarmProfileId, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Tn as TimelapseRulePatchSchema, Tr as isEventOwnerType, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as ZodObject, Un as audioModeOf, Ur as pipelineAnalyticsCapability, Ut as NcScheduleSchema, V as FailureCounters, Vn as assertTimelapseCadences, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as _enum, Wn as buildEventKindDescriptor, Wr as plateGalleryCapability, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as number, Xn as coreBlockIdFromAddonId, Xr as resolveLocationMode, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as looseObject, Yn as consumableLowPercentOf, Yr as resolveClipSearchMinScore, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as object, Zn as cosineSimilarity$1, Zr as resolveModeDelays, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as systemEventFilterApplies, _n as SCENE_RESET_RECAPTURES, _r as gifDeriveSpeed, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as unknown, ai as ruleGroupingApplies, an as RebuildObjectEmbeddingsRefusalSchema, ar as deviceRemovalHookCapability, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as templateCarriesSummary, bn as SetTrackPurityResultSchema, br as isAppLinkBase, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ci as secureActuationFor, cn as RetrainStatusSchema, cr as embeddingRefusalOf, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, di as storageOccupancyCapability, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as encodeVectorBase64, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as strictObject, ei as retrainRevisionConflictMessage, en as OPS_LOG_DEFAULT_LIMIT, er as deriveDetailCropRect, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as subKindsOf, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, fr as ensureAlarmProfiles, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, gi as systemEventFamilyProblems, gn as SCENE_DIVERGED, gr as failureContributionCapability, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, hi as systemEventDeliveryProblems, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as faceGalleryCapability, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as union, ii as ruleDeviceCompatCapQueries, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as describeSystemEventFamilyProblem, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as BaseAddon, jn as TrackSourceSchema, jr as isScheduleActive, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as DeviceType, kn as TrackRelationSchema, kr as isRetiredClearKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, li as sensorRaisedOf, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, mi as syncActiveAlarmProfileSettings, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as evictionPolicyOfLocation, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as templateLiteral, ni as ruleAutoTriggersAlarm, nr as deriveRecordingMode, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as EventCategory, oi as ruleKindOf, on as RetrainDismissedSchema, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as summarizeReview, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pr as eventKindLabel, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as discriminatedUnion, qn as classifyRetrainReview, qr as readTimelapseGeneratedAt, qt as NcSnoozeSuppressedSchema, ra as tuple, ri as ruleBelongsToAlarm$1, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as describeSystemEventDeliveryProblem, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, si as sceneMonitorCapability, sn as RetrainOriginSchema, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as string, ti as rootSupplyServes, tn as OpsLogEntrySchema, tr as derivePostTriggeredAction, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ui as servedTemplateVars, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as systemEventSummaryScheduleOf, vn as SceneMonitorSchema, vr as gifFromMp4Args, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as zoneAnalyticsCapability, wn as TimelapseRuleInputSchema, wr as isEventKindLabelKey, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as templateVarsFor, xn as SetTrackRelationInputSchema, xr as isAudioClassName, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as systemEventSummarySplitOf, yn as SetTrackPurityInputSchema, yr as isAlarmControllableCap, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as isDeviceScopedCap, zn as applySecureOnArmEdit, zr as notificationRulesCapability, zt as NcRulePatchSchema } from "../clip-model-registry-CTfe5v5_.mjs";
1
+ import { $ as MediaFileKindEnum, $i as boolean, $n as coreBlockIdFromAddonId, $r as readTimelapseGeneratedAt, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as viewerLinkBaseOf, An as TrackSideSchema, Ar as isEventOwnerType, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as RECORD_MAX_DATA_BYTES, Bn as applyModeDelaysEdits, Br as kebabToCamel, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as systemEventFamilyProblems, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isAlertEpisodeOpen, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as templateCarriesSummary, Dn as TrackPuritySchema, Dr as isDefaultAlarmProfileId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as systemEventSummarySplitOf, En as TimelapseRuleSchema, Er as isAudioClassName, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as isSourceCap, Fn as alarmPanelCapability, Fr as isRootSuppliableStepId, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as hydrateSchema, Gn as audioMetricsCapability, Gr as occupancyScope, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as customAction, Hn as applySecureOnArmEdit, Hr as mayWriteToLocation, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as DeviceType, In as alertEpisodeId, Ir as isScheduleActive, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Jn as buildViewerLink, Jr as pickFaceRescoreIntervalMinutes, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as isDeviceScopedCap, Kn as audioModeOf, Kr as parseAlertEpisodePayload, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as errMsg, Ln as alertEpisodeKind, Lr as isSystemDelivery, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Mn as activateAlarmProfile, Mr as isLabelSupplyStepId, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as canonicalHash, Nn as addonWidgetsSourceCapability, Nr as isMediaOwnerType, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as templateVarsFor, On as TrackRelationKindSchema, Or as isDetectionMacroClass, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as evaluateSensorEdge, Pn as alarmDeviceSecureOnArm, Pr as isRetiredClearKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as array, Qn as consumableLowPercentOf, Qr as readMotionTriggeredBy, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as BaseAddon, Rn as alertEpisodePayload, Rr as isSystemEventSummaryRule, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as systemEventDeliveryProblems, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isAlarmControllableCap, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as systemEventSummaryScheduleOf, Tn as TimelapseRulePatchSchema, Tr as isAppLinkBase, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as defineCustomActions, Un as armRemovalReconcile, Ur as memoizePinnedFfmpeg, Ut as NcScheduleSchema, V as FailureCounters, Vi as createEvent, Vn as applyNonBlockingEdit, Vr as linkedStepOf, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as defineRecordKinds, Wn as assertTimelapseCadences, Wr as notificationRulesCapability, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as ZodObject, Xn as classifyRetrainReview, Xr as plateGalleryCapability, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as sleep$1, Yn as calendarEventLocalSpan, Yr as pipelineAnalyticsCapability, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as _enum, Zn as cloneAlarmSettings, Zr as readDeviceStateFrom, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as startMoverUnderClearance, _n as SCENE_RESET_RECAPTURES, _r as evictionPolicyOfLocation, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as partialRecord, an as RebuildObjectEmbeddingsRefusalSchema, ar as deriveRecordingMode, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as summarizeReview, bn as SetTrackPurityResultSchema, br as gifDeriveSpeed, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as string, ci as ruleAutoTriggersAlarm, cn as RetrainStatusSchema, cr as deviceRemovalHookCapability, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as union, di as ruleGroupingApplies, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as embeddingRefusalOf, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as discriminatedUnion, ei as renameAlarmProfile, en as OPS_LOG_DEFAULT_LIMIT, er as cosineSimilarity$1, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as unknown, fi as ruleKindOf, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, gi as servedTemplateVars, gn as SCENE_DIVERGED, gr as eventKindLabel, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, hi as sensorRaisedOf, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as ensureAlarmProfiles, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as object, ii as resolveModeNotifyPolicy, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as derivePostTriggeredAction, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as zoneAnalyticsCapability, jn as TrackSourceSchema, jr as isKnownTemplateVar, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as vectorDimFromBase64, kn as TrackRelationSchema, kr as isEventKindLabelKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as templateLiteral, li as ruleBelongsToAlarm$1, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, mi as secureActuationFor, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as encodeVectorBase64, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as looseObject, ni as resolveLocationMode, nr as deleteAlarmProfile, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as record, oi as retrainRevisionConflictMessage, on as RetrainDismissedSchema, or as describeSystemEventDeliveryProblem, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as EventCategory, pi as sceneMonitorCapability, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as nodePin, qn as buildEventKindDescriptor, qt as NcSnoozeSuppressedSchema, ra as number, ri as resolveModeDelays, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as deriveDetailCropRect, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as strictObject, si as rootSupplyServes, sn as RetrainOriginSchema, sr as describeSystemEventFamilyProblem, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as literal, ti as resolveClipSearchMinScore, tn as OpsLogEntrySchema, tr as createAlarmProfile, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as tuple, ui as ruleDeviceCompatCapQueries, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as storageOccupancyCapability, vn as SceneMonitorSchema, vr as faceGalleryCapability, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as systemEventFilterApplies, wn as TimelapseRuleInputSchema, wr as isAlertEpisodeSeverity, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as syncActiveAlarmProfileSettings, xn as SetTrackRelationInputSchema, xr as gifFromMp4Args, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as subKindsOf, yn as SetTrackPurityInputSchema, yr as failureContributionCapability, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as CamProfileSchema, zn as applyBlockingEdit, zr as isVectorIndexDimConflict, zt as NcRulePatchSchema } from "../clip-model-registry-BItfTZEx.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";
@@ -10273,6 +10273,7 @@ var NC_SYSTEM_STATE_LEGACY = "notification-center:system-state";
10273
10273
  var NC_OCCUPANCY_LEGACY = "notification-center:occupancy";
10274
10274
  var NC_DEVICE_PRUNES_LEGACY = "notification-center:device-prunes";
10275
10275
  var NC_STATE_SUMMARY_STAMPS_LEGACY = "notification-center:state-summary-stamps";
10276
+ var NC_ALERT_EPISODES_LEGACY = "notification-center:alert-episodes";
10276
10277
  var NC_META_LEGACY = "notification-center:meta";
10277
10278
  var NC_SNOOZES_LEGACY = "notification-center:snoozes";
10278
10279
  var NC_SNOOZE_SUPPRESSED_LEGACY = "notification-center:snooze-suppressed";
@@ -10293,6 +10294,7 @@ var ncDurableRecordKinds = defineRecordKinds({
10293
10294
  liveness: ledger(NC_LIVENESS_LEGACY, "a liveness state TRANSITION the centre accepted (seed or flip); a no-flip writes nothing"),
10294
10295
  "device-alerts": ledger(NC_DEVICE_ALERTS_LEGACY, "a sustained device alert episode the centre confirmed or cleared"),
10295
10296
  "system-state": ledger(NC_SYSTEM_STATE_LEGACY, "a system-state subject flip the state summary accepted"),
10297
+ "alert-episodes": ledger(NC_ALERT_EPISODES_LEGACY, "an alert episode the centre notified (critical or error); its resolution deletes the row"),
10296
10298
  occupancy: ledger(NC_OCCUPANCY_LEGACY, "a confirmed occupancy level write-through after the watcher decided an edge"),
10297
10299
  "device-prunes": ledger(NC_DEVICE_PRUNES_LEGACY, "one row recording what a deleted-device prune removed"),
10298
10300
  "state-summary-stamps": ledger(NC_STATE_SUMMARY_STAMPS_LEGACY, "digest delivery stamps per rule kind"),
@@ -10312,6 +10314,7 @@ var NC_PHASE3_RECORD_BACKED_LEGACY_COLLECTIONS = [
10312
10314
  NC_LIVENESS_LEGACY,
10313
10315
  NC_DEVICE_ALERTS_LEGACY,
10314
10316
  NC_SYSTEM_STATE_LEGACY,
10317
+ NC_ALERT_EPISODES_LEGACY,
10315
10318
  NC_OCCUPANCY_LEGACY,
10316
10319
  NC_DEVICE_PRUNES_LEGACY,
10317
10320
  NC_STATE_SUMMARY_STAMPS_LEGACY,
@@ -10733,6 +10736,120 @@ var NcAlertStore = class {
10733
10736
  }
10734
10737
  };
10735
10738
  //#endregion
10739
+ //#region src/notification-center/alert-episode-ledger.ts
10740
+ /**
10741
+ * @durable class=ledger owner=notification-center
10742
+ * write="an alert episode the centre notified (alert-critical / alert-error); its resolution deletes the row"
10743
+ * retention="a row lives while its episode is open. A row whose alert was pruned by the alert centre's own retention is dropped after NC_ALERT_EPISODE_MAX_AGE_MS by the reconcile, never on absence alone (D49)."
10744
+ */
10745
+ var NC_ALERT_EPISODES_COLLECTION = "notification-center:alert-episodes";
10746
+ var NC_ALERT_EPISODES_COLUMNS = [
10747
+ {
10748
+ name: "episodeId",
10749
+ type: "TEXT",
10750
+ primaryKey: true,
10751
+ notNull: true
10752
+ },
10753
+ (
10754
+ /** The kind that was notified: `alert-critical` or `alert-error`. */
10755
+ {
10756
+ name: "kind",
10757
+ type: "TEXT",
10758
+ notNull: true
10759
+ }),
10760
+ {
10761
+ name: "alertId",
10762
+ type: "TEXT",
10763
+ notNull: true
10764
+ },
10765
+ {
10766
+ name: "since",
10767
+ type: "INTEGER",
10768
+ notNull: true
10769
+ }
10770
+ ];
10771
+ /** Longer than the alert centre's maximum retention (365 days). */
10772
+ var NC_ALERT_EPISODE_MAX_AGE_MS = 400 * 24 * 60 * 60 * 1e3;
10773
+ function isEpisodeKind(value) {
10774
+ return value === "alert-critical" || value === "alert-error";
10775
+ }
10776
+ var NC_ALERT_EPISODES_SPEC = {
10777
+ collection: NC_ALERT_EPISODES_COLLECTION,
10778
+ columns: NC_ALERT_EPISODES_COLUMNS,
10779
+ writeMode: "write-behind",
10780
+ keyOf: (row) => row.episodeId,
10781
+ toValue: (row) => ({
10782
+ kind: row.kind,
10783
+ alertId: row.alertId,
10784
+ since: row.since
10785
+ }),
10786
+ fromRecord: (key, data) => {
10787
+ const kind = data["kind"];
10788
+ const alertId = data["alertId"];
10789
+ const since = data["since"];
10790
+ if (!isEpisodeKind(kind) || typeof alertId !== "string" || typeof since !== "number") return null;
10791
+ return {
10792
+ episodeId: key,
10793
+ kind,
10794
+ alertId,
10795
+ since
10796
+ };
10797
+ }
10798
+ };
10799
+ NC_ALERT_EPISODES_SPEC.fromRecord, NC_ALERT_EPISODES_SPEC.toValue;
10800
+ var NcAlertEpisodeLedger = class {
10801
+ ledger;
10802
+ constructor(deps) {
10803
+ this.ledger = new DurableLedger({
10804
+ spec: NC_ALERT_EPISODES_SPEC,
10805
+ kind: resolveDurableKind(NC_ALERT_EPISODES_SPEC, ncDurableRecordKinds["alert-episodes"], deps),
10806
+ logger: deps.logger
10807
+ });
10808
+ }
10809
+ static declare(store) {
10810
+ return declareDurableSpecLegacy(store, NC_ALERT_EPISODES_SPEC);
10811
+ }
10812
+ async load() {
10813
+ return (await this.ledger.load()).length;
10814
+ }
10815
+ /** Has a load READ the store once? Until then the mirror is not the truth. */
10816
+ hasAnswered() {
10817
+ return this.ledger.hasAnswered();
10818
+ }
10819
+ has(episodeId) {
10820
+ return this.ledger.has(episodeId);
10821
+ }
10822
+ snapshot() {
10823
+ return this.ledger.snapshot();
10824
+ }
10825
+ /**
10826
+ * Judge one announced edge and advance the ledger. Synchronous: the verdict
10827
+ * is a function of the mirror (D49); the durable write trails it.
10828
+ */
10829
+ admit(edge, now) {
10830
+ const held = this.ledger.get(edge.episodeId);
10831
+ if (edge.phase === "raised") {
10832
+ if (edge.kind !== "alert-critical" && edge.kind !== "alert-error") return "duplicate";
10833
+ if (held !== void 0 && (held.kind === "alert-critical" || edge.kind === "alert-error")) return "duplicate";
10834
+ this.ledger.put({
10835
+ episodeId: edge.episodeId,
10836
+ kind: edge.kind,
10837
+ alertId: edge.alertId,
10838
+ since: held?.since ?? now
10839
+ });
10840
+ return "notify";
10841
+ }
10842
+ if (held === void 0) return "unknown-episode";
10843
+ this.ledger.forget(edge.episodeId);
10844
+ return held.kind === "alert-critical" && edge.kind === "alert-critical-resolved" ? "notify" : "closed-silently";
10845
+ }
10846
+ /** Drop rows older than the alert centre could still hold the alert. */
10847
+ async pruneOlderThan(cutoffMs) {
10848
+ const keep = new Set(this.ledger.snapshot().filter((row) => row.since >= cutoffMs).map((row) => row.episodeId));
10849
+ return this.ledger.pruneExcept(keep);
10850
+ }
10851
+ };
10852
+ //#endregion
10736
10853
  //#region src/notification-center/device-prune-ledger.ts
10737
10854
  /**
10738
10855
  * @durable class=ledger owner=notification-center
@@ -16018,6 +16135,34 @@ function bestMatchingKindPreference(signal, ownerKind) {
16018
16135
  }
16019
16136
  //#endregion
16020
16137
  //#region src/notification-center/event-intake.ts
16138
+ /**
16139
+ * The notification path's INTAKE SEAM — one event in, no knowledge of who
16140
+ * produced it.
16141
+ *
16142
+ * Why this file exists at all: until now every producer reached the centre
16143
+ * through its own record-shaped door (`onObjectEventPersisted(ObjectEvent)`,
16144
+ * `onSensorEventPersisted(SensorEvent)`, …). The doors did nothing but build a
16145
+ * subject and schedule an evaluation, yet their SIGNATURES made the notification
16146
+ * path look like a function of the pipeline's row types — so "notify about this"
16147
+ * was not expressible without first having a persisted row to hand.
16148
+ *
16149
+ * The split this file draws is between PRODUCING an event and CONSUMING one:
16150
+ *
16151
+ * - The producer owns persistence. The tracker persists its tracks, the sensor
16152
+ * ingest persists its rows, and a synthetic producer (the rule tester)
16153
+ * persists NOTHING. Whether a row exists is the producer's business and
16154
+ * never the consumer's.
16155
+ * - The consumer owns match → media → delivery. It receives an
16156
+ * {@link NcIncomingEvent} and cannot tell which producer built it, because
16157
+ * the only thing the matcher can read is `subject`, and `subject` carries no
16158
+ * provenance beyond the detection `source` a real record would carry anyway.
16159
+ *
16160
+ * {@link NcIncomingEvent.origin} exists for LOGS, and for the outbox row's
16161
+ * marker. It is deliberately NOT part of `NcRuleSubject`: a condition cannot
16162
+ * reach it, so no rule can be authored that behaves differently for a test. That
16163
+ * is the property the tester is worth nothing without — a test that takes a
16164
+ * different branch is a test of the branch, not of the rule.
16165
+ */
16021
16166
  /** Object-event insert (movement-state transitions + zone crossings). */
16022
16167
  function incomingFromObjectEvent(event) {
16023
16168
  return {
@@ -16861,9 +17006,45 @@ function incomingFromSystemEvent(event, lookup) {
16861
17006
  case "system.restarting": return incomingFromNodeRestarting(facts, data);
16862
17007
  case "recording.export.completed": return incomingFromExportCompleted(facts, data);
16863
17008
  case "backup.run-completed": return incomingFromBackupCompleted(facts, data);
17009
+ case "alert.episode": return incomingFromAlertEpisode(facts);
16864
17010
  default: return null;
16865
17011
  }
16866
17012
  }
17013
+ /**
17014
+ * An alert episode edge (`alert-critical`, `alert-error`, `alert-critical-resolved`).
17015
+ *
17016
+ * The WORDS are the alert's own: the alert centre composed the title and the
17017
+ * message when it opened the alert, and a rule's template may reword them with
17018
+ * `{{title}}`, `{{message}}`, `{{severity}}`, `{{category}}`, `{{source}}`,
17019
+ * `{{node}}` and `{{actionUrl}}`. The subject is the EPISODE, so the dedup slot
17020
+ * of a new episode is never the one a previous, resolved, episode held.
17021
+ *
17022
+ * Whether an edge is notified at all (once per episode, reconciled from the
17023
+ * alerts store) is decided before this point, by `NcAlertEpisodeLedger`.
17024
+ */
17025
+ function incomingFromAlertEpisode(facts) {
17026
+ const payload = parseAlertEpisodePayload(facts.data);
17027
+ if (payload === null) return null;
17028
+ const kind = alertEpisodeKind(payload.severity, payload.phase);
17029
+ if (kind === null) return null;
17030
+ return systemSubject({
17031
+ facts,
17032
+ kind,
17033
+ subject: `alert:${payload.episodeId}`,
17034
+ titleKey: `system.${kind}.title`,
17035
+ bodyKey: `system.${kind}.body`,
17036
+ textParams: {
17037
+ title: payload.title,
17038
+ message: payload.message,
17039
+ severity: payload.severity,
17040
+ category: payload.category,
17041
+ source: payload.sourceId ?? "",
17042
+ nodeId: payload.nodeId ?? "",
17043
+ actionUrl: payload.actionUrl ?? ""
17044
+ },
17045
+ ...payload.nodeId !== void 0 ? { nodeId: payload.nodeId } : {}
17046
+ });
17047
+ }
16867
17048
  /** The shells whose update has its own sentence. Anything else gets the plain
16868
17049
  * one: a body that names a shell nobody observed is worse than a vague body. */
16869
17050
  var WRAPPER_BODY_KINDS = new Set([
@@ -19346,6 +19527,7 @@ async function declareNcPhase3SettingsStoreLegacyCollections(store) {
19346
19527
  async function declareNcPhase3RecordBackedLegacyCollections(store) {
19347
19528
  await OccupancyStore.declare(store);
19348
19529
  await NcLivenessLedger.declare(store);
19530
+ await NcAlertEpisodeLedger.declare(store);
19349
19531
  await NcAlertStore.declare(store);
19350
19532
  await NcStateSummaryService.declare(store);
19351
19533
  await NcSnoozeStore.declare(store);
@@ -22157,6 +22339,41 @@ function audioDbfsByBucket(events, since, bucketMs) {
22157
22339
  }
22158
22340
  return out;
22159
22341
  }
22342
+ /**
22343
+ * PEAK dBFS per bucket index from device-history points (D431 follow-up).
22344
+ *
22345
+ * The bar answers "was it loud here", so a bucket reports the loudest window
22346
+ * in it. A raw point has `max === value`; a rollup row's `max` is the loudest
22347
+ * point it covers, which a mean would flatten. A bucket with no point is
22348
+ * absent — never zero. A rollup row also credits a held value across a quiet
22349
+ * gap, so its max can echo an earlier measured peak.
22350
+ */
22351
+ function audioPeakByBucket(points, since, bucketMs) {
22352
+ const out = /* @__PURE__ */ new Map();
22353
+ if (bucketMs <= 0) return out;
22354
+ for (const p of points) {
22355
+ if (!Number.isFinite(p.max)) continue;
22356
+ const idx = Math.floor((p.t - since) / bucketMs);
22357
+ if (idx < 0) continue;
22358
+ const prev = out.get(idx);
22359
+ if (prev === void 0 || p.max > prev) out.set(idx, p.max);
22360
+ }
22361
+ return out;
22362
+ }
22363
+ //#endregion
22364
+ //#region src/pipeline-analytics/store/analytics-history-keys.ts
22365
+ /** Declared addon series keys (D850). */
22366
+ var ANALYTICS_HISTORY_BUCKET_SERIES = {
22367
+ motion: "addon:pipeline-analytics/motion",
22368
+ "audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
22369
+ occupancy: "addon:pipeline-analytics/occupancy"
22370
+ };
22371
+ var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
22372
+ /** Map the public cap enum to the device-history key (read path). */
22373
+ function historyKeyForLtsSeries(series) {
22374
+ if (series === "battery") return "cap:battery.percentage";
22375
+ return ANALYTICS_HISTORY_BUCKET_SERIES[series];
22376
+ }
22160
22377
  //#endregion
22161
22378
  //#region src/pipeline-analytics/store/event-store.ts
22162
22379
  /**
@@ -22529,6 +22746,24 @@ function eventRowId(logger, operation, deviceId, key) {
22529
22746
  });
22530
22747
  return null;
22531
22748
  }
22749
+ var RAW_RETENTION_MS = 7 * 864e5;
22750
+ var FIVE_MIN_RETENTION_MS = 14 * 864e5;
22751
+ var FIVE_MIN_MS = 3e5;
22752
+ var HOUR_MS$2 = 36e5;
22753
+ /** The producer writes one raw point per window of this length. */
22754
+ var AUDIO_LEVEL_POINT_MS = 1e4;
22755
+ var HISTORY_MAX_POINTS = 5e3;
22756
+ /**
22757
+ * Pick the device-history tier for a density window: raw while the buckets are
22758
+ * finer than the 5m rollup, the window is inside raw retention and its points
22759
+ * fit one query; the 5m rollup while inside its retention; else 1h.
22760
+ */
22761
+ function audioLevelResolution(q, now) {
22762
+ const rawFits = Math.ceil((q.until - q.since) / AUDIO_LEVEL_POINT_MS) <= HISTORY_MAX_POINTS;
22763
+ if (q.bucketMs < FIVE_MIN_MS && rawFits && q.since >= now - RAW_RETENTION_MS) return "raw";
22764
+ if (q.bucketMs < HOUR_MS$2 && q.since >= now - FIVE_MIN_RETENTION_MS) return "5m";
22765
+ return "1h";
22766
+ }
22532
22767
  var EventStore = class EventStore {
22533
22768
  store;
22534
22769
  logger;
@@ -22536,6 +22771,10 @@ var EventStore = class EventStore {
22536
22771
  audioLedger;
22537
22772
  media;
22538
22773
  onMotionSample;
22774
+ audioLevelHistory;
22775
+ nowFn;
22776
+ /** Cameras whose history read already failed once (log once per camera, D315). */
22777
+ levelFallbackLogged = /* @__PURE__ */ new Set();
22539
22778
  constructor(deps) {
22540
22779
  this.store = deps.store;
22541
22780
  this.logger = deps.logger;
@@ -22544,6 +22783,8 @@ var EventStore = class EventStore {
22544
22783
  this.audioLedger = deps.audioLedger ?? wireAudioEventLedger(wiring);
22545
22784
  this.media = deps.media;
22546
22785
  this.onMotionSample = deps.onMotionSample;
22786
+ this.audioLevelHistory = deps.audioLevelHistory;
22787
+ this.nowFn = deps.now ?? (() => Date.now());
22547
22788
  }
22548
22789
  static async declareTracklessEventCollections(store) {
22549
22790
  await store.declareCollection.mutate({
@@ -23272,8 +23513,8 @@ var EventStore = class EventStore {
23272
23513
  const m = motionMap.get(i) ?? 0;
23273
23514
  const o = objectMap.get(i) ?? 0;
23274
23515
  const a = audioMap.get(i) ?? 0;
23275
- if (m + o + a === 0) continue;
23276
23516
  const dbfs = audioDbfsMap.get(i);
23517
+ if (m + o + a === 0 && dbfs === void 0) continue;
23277
23518
  out.push({
23278
23519
  bucketStart: q.since + i * q.bucketMs,
23279
23520
  motion: m,
@@ -23294,6 +23535,28 @@ var EventStore = class EventStore {
23294
23535
  * missing bar is the honest rendering of "not measured" (D431).
23295
23536
  */
23296
23537
  async audioDbfsPerBucket(q) {
23538
+ const history = this.audioLevelHistory;
23539
+ if (history !== void 0) try {
23540
+ const resolution = audioLevelResolution(q, this.nowFn());
23541
+ const points = (await history({
23542
+ deviceId: q.deviceId,
23543
+ key: ANALYTICS_AUDIO_LEVEL_KEY,
23544
+ scope: "",
23545
+ from: q.since,
23546
+ to: q.until,
23547
+ resolution,
23548
+ maxPoints: HISTORY_MAX_POINTS
23549
+ })).series.flatMap((s) => s.points);
23550
+ if (points.length > 0) return audioPeakByBucket(points, q.since, q.bucketMs);
23551
+ } catch (err) {
23552
+ if (!this.levelFallbackLogged.has(q.deviceId)) {
23553
+ this.levelFallbackLogged.add(q.deviceId);
23554
+ this.logger.warn("density: audio level history unreadable — falling back to audio events", {
23555
+ tags: { deviceId: q.deviceId },
23556
+ meta: { error: String(err) }
23557
+ });
23558
+ }
23559
+ }
23297
23560
  try {
23298
23561
  return audioDbfsByBucket((await this.queryAudio({
23299
23562
  deviceId: q.deviceId,
@@ -32855,6 +33118,12 @@ var en_default = {
32855
33118
  "system.backup-completed.title": "Backup completed",
32856
33119
  "system.backup-completed.body": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations ({{sizeMB}} MB)",
32857
33120
  "system.backup-completed.body.partial": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations — {{destinationsFailed}} failed",
33121
+ "system.alert-critical.title": "{{title}}",
33122
+ "system.alert-critical.body": "{{message}}",
33123
+ "system.alert-error.title": "{{title}}",
33124
+ "system.alert-error.body": "{{message}}",
33125
+ "system.alert-critical-resolved.title": "Resolved: {{title}}",
33126
+ "system.alert-critical-resolved.body": "{{message}}",
32858
33127
  "system.device-consumable-low.title": "Consumable low — {{deviceName}}",
32859
33128
  "system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
32860
33129
  "system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
@@ -33235,6 +33504,12 @@ var it_default = {
33235
33504
  "system.backup-completed.title": "Backup completato",
33236
33505
  "system.backup-completed.body": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} ({{sizeMB}} MB)",
33237
33506
  "system.backup-completed.body.partial": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} — {{destinationsFailed}} fallite",
33507
+ "system.alert-critical.title": "{{title}}",
33508
+ "system.alert-critical.body": "{{message}}",
33509
+ "system.alert-error.title": "{{title}}",
33510
+ "system.alert-error.body": "{{message}}",
33511
+ "system.alert-critical-resolved.title": "Risolto: {{title}}",
33512
+ "system.alert-critical-resolved.body": "{{message}}",
33238
33513
  "system.device-consumable-low.title": "Consumabile basso — {{deviceName}}",
33239
33514
  "system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
33240
33515
  "system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
@@ -35210,6 +35485,7 @@ function systemEventSubject(kind, scope) {
35210
35485
  if (kind === "node-updating") return `node-updating:${scope.nodeId ?? "hub"}:1.0.1`;
35211
35486
  if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
35212
35487
  if (kind === "backup-completed") return "backup:test";
35488
+ if (kind.startsWith("alert-")) return "alert:test";
35213
35489
  return `alarm:${scope.deviceId ?? 0}`;
35214
35490
  }
35215
35491
  /**
@@ -35350,7 +35626,12 @@ function planRuleTest(input) {
35350
35626
  nodes: "hub",
35351
35627
  fromVersion: "1.0.0",
35352
35628
  toVersion: "1.0.1",
35353
- title: "Export",
35629
+ title: systemEventKind.startsWith("alert-") ? "Recording location full" : "Export",
35630
+ message: "No free space left on the recording location",
35631
+ severity: systemEventKind === "alert-error" ? "error" : "critical",
35632
+ category: "recorder.placement",
35633
+ source: "recorder",
35634
+ actionUrl: "/system/addons",
35354
35635
  count: "1"
35355
35636
  },
35356
35637
  ...systemScope.deviceId !== void 0 ? { deviceId: systemScope.deviceId } : {},
@@ -51921,6 +52202,10 @@ function groupedAlarmPhaseKey(ruleId, trackId) {
51921
52202
  var NO_MATCH_REPORT_INTERVAL_MS = 6e4;
51922
52203
  var DEFAULT_DRAIN_INTERVAL_MS = 2e3;
51923
52204
  var DEFAULT_RULE_RELOAD_INTERVAL_MS = 3e4;
52205
+ /** How often open critical/error alerts are reconciled against the notified episodes. */
52206
+ var NC_ALERT_RECONCILE_INTERVAL_MS = 5 * 6e4;
52207
+ /** The most alerts one reconcile reads (the alert centre's own ceiling). */
52208
+ var NC_ALERT_RECONCILE_LIMIT = 5e3;
51924
52209
  var DEFAULT_RECONCILE_WINDOW_MS = 15 * 6e4;
51925
52210
  /** Overlap subtracted from the watermark on boot (idempotent, cheap). */
51926
52211
  var RECONCILE_OVERLAP_MS = 6e4;
@@ -52493,6 +52778,15 @@ var NotificationCenter = class {
52493
52778
  * seeds, only a real flip notifies.
52494
52779
  */
52495
52780
  liveness;
52781
+ /**
52782
+ * Which alert episodes were already notified (`alert-episode-ledger.ts`): the
52783
+ * alert centre sends nothing of its own, so this is what makes its bus
52784
+ * announcements and the reconcile from the alerts store safe to run together.
52785
+ */
52786
+ alertEpisodes;
52787
+ /** The boot load of {@link alertEpisodes} has been ATTEMPTED (bus edges before it are the reconcile's). */
52788
+ alertEpisodesLoaded = false;
52789
+ alertReconcileTimer = null;
52496
52790
  /** Batches `addon.updated` (version-bearing) into one notification per 30 s. */
52497
52791
  addonUpdates;
52498
52792
  /** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
@@ -53194,6 +53488,10 @@ var NotificationCenter = class {
53194
53488
  ...durableStore,
53195
53489
  logger: this.logger.child("liveness")
53196
53490
  });
53491
+ this.alertEpisodes = new NcAlertEpisodeLedger({
53492
+ ...durableStore,
53493
+ logger: this.logger.child("alert-episodes")
53494
+ });
53197
53495
  this.addonUpdates = new AddonUpdateCoalescer((updates) => {
53198
53496
  const incoming = incomingFromAddonUpdates({
53199
53497
  eventId: `addon-updated:${updates.map((u) => u.packageName).join(",")}`,
@@ -53590,6 +53888,10 @@ var NotificationCenter = class {
53590
53888
  this.reloadTimer = setInterval(() => {
53591
53889
  this.reloadRules();
53592
53890
  }, this.deps.ruleReloadIntervalMs ?? DEFAULT_RULE_RELOAD_INTERVAL_MS);
53891
+ this.alertReconcileTimer = setInterval(() => {
53892
+ this.reconcileAlertEpisodes();
53893
+ }, this.deps.alertReconcileIntervalMs ?? NC_ALERT_RECONCILE_INTERVAL_MS);
53894
+ this.alertReconcileTimer.unref?.();
53593
53895
  const timelapseScheduler = this.timelapseScheduler;
53594
53896
  if (timelapseScheduler !== null) await timed("timelapseProducer", () => timelapseScheduler.start()).catch((err) => {
53595
53897
  this.logger.warn("timelapse producer did not start", { meta: { error: String(err) } });
@@ -53705,6 +54007,7 @@ var NotificationCenter = class {
53705
54007
  await this.deviceDirectory.refresh();
53706
54008
  })) return;
53707
54009
  if (!await phase("liveness", () => this.hydrateLiveness())) return;
54010
+ if (!await phase("alertEpisodes", () => this.hydrateAlertEpisodes())) return;
53708
54011
  if (!await phase("deviceAlerts", async () => {
53709
54012
  const rows = await this.alertStore.load();
53710
54013
  this.logger.info("device alert ledger reseeded", { meta: { rows } });
@@ -53747,6 +54050,10 @@ var NotificationCenter = class {
53747
54050
  clearInterval(this.reloadTimer);
53748
54051
  this.reloadTimer = null;
53749
54052
  }
54053
+ if (this.alertReconcileTimer !== null) {
54054
+ clearInterval(this.alertReconcileTimer);
54055
+ this.alertReconcileTimer = null;
54056
+ }
53750
54057
  this.timelapseScheduler?.stop();
53751
54058
  this.summaryProducer?.stop();
53752
54059
  await this.closeGroupedWindowsOnStop();
@@ -54758,6 +55065,10 @@ var NotificationCenter = class {
54758
55065
  return;
54759
55066
  }
54760
55067
  this.stateSummary.observeRawEvent(event.category, event.data, this.now(), event.source.nodeId);
55068
+ if (event.category === "alert.episode") {
55069
+ if (this.alertEpisodesLoaded) this.admitAlertEpisode(event, "bus");
55070
+ return;
55071
+ }
54761
55072
  if (event.category === "addon.started" || event.category === "addon.restarted") return;
54762
55073
  if (event.category === "pipeline.camera-assigned") return;
54763
55074
  const battery = batterySliceOf(event);
@@ -54791,10 +55102,6 @@ var NotificationCenter = class {
54791
55102
  const system = incoming.subject.systemEvent;
54792
55103
  if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
54793
55104
  if (system !== void 0) this.updateLandings.observe(system, this.now());
54794
- if (system !== void 0 && system.kind === "addon-crash-loop") {
54795
- this.logger.debug("addon-crash-loop is pushed by the hub alert centre, not by a rule", { meta: { subject: system.subject } });
54796
- return;
54797
- }
54798
55105
  const observation = system !== void 0 ? ledgeredLiveness(system) : null;
54799
55106
  if (observation !== null) {
54800
55107
  const previous = this.liveness.stateOf(observation.subject);
@@ -57446,6 +57753,89 @@ var NotificationCenter = class {
57446
57753
  await this.occupancyStore.pruneExcept(this.activeOccupancyKeys());
57447
57754
  }
57448
57755
  /**
57756
+ * One edge of an alert episode, from the bus or from the reconcile. The
57757
+ * ledger decides whether it is news; only news reaches the rules.
57758
+ */
57759
+ admitAlertEpisode(event, via) {
57760
+ const payload = parseAlertEpisodePayload(event.data);
57761
+ if (payload === null) {
57762
+ this.logger.warn("alert episode event with an unreadable payload ignored", { meta: {
57763
+ via,
57764
+ eventId: event.id
57765
+ } });
57766
+ return false;
57767
+ }
57768
+ const verdict = this.alertEpisodes.admit({
57769
+ kind: alertEpisodeKind(payload.severity, payload.phase),
57770
+ phase: payload.phase,
57771
+ episodeId: payload.episodeId,
57772
+ alertId: payload.alertId
57773
+ }, this.now());
57774
+ const meta = {
57775
+ via,
57776
+ alertId: payload.alertId,
57777
+ episodeId: payload.episodeId,
57778
+ phase: payload.phase,
57779
+ severity: payload.severity,
57780
+ verdict
57781
+ };
57782
+ if (verdict !== "notify") {
57783
+ this.logger.debug("alert episode edge not notified", { meta });
57784
+ return false;
57785
+ }
57786
+ const incoming = incomingFromSystemEvent(event, this.deviceDirectory.lookup());
57787
+ if (incoming === null) return false;
57788
+ this.logger.info("alert episode notified to the rules", { meta });
57789
+ this.consumeEvent(incoming);
57790
+ return true;
57791
+ }
57792
+ async hydrateAlertEpisodes() {
57793
+ try {
57794
+ const rows = await this.alertEpisodes.load();
57795
+ this.logger.info("alert episode ledger reseeded", { meta: { rows } });
57796
+ } finally {
57797
+ this.alertEpisodesLoaded = true;
57798
+ }
57799
+ await this.reconcileAlertEpisodes();
57800
+ }
57801
+ /**
57802
+ * The alerts store against the notified episodes (D11, D49): every OPEN
57803
+ * critical/error alert not yet notified is notified, and every notified
57804
+ * episode whose alert is CLOSED gets its resolution. Never runs on a ledger
57805
+ * the store has not answered — an empty, unread mirror would re-notify every
57806
+ * open alert — and never reads absence as closure: an alert that is not in
57807
+ * the answer is left alone (rows age out by {@link NC_ALERT_EPISODE_MAX_AGE_MS}).
57808
+ */
57809
+ async reconcileAlertEpisodes() {
57810
+ const read = this.deps.readAlerts;
57811
+ if (read === void 0 || !this.evaluationActive || !this.alertEpisodes.hasAnswered()) return;
57812
+ let alerts;
57813
+ try {
57814
+ alerts = await read();
57815
+ } catch (err) {
57816
+ this.logger.warn("alert episode reconcile could not read the alerts", { meta: { error: errMsg(err) } });
57817
+ return;
57818
+ }
57819
+ let notified = 0;
57820
+ for (const alert of alerts) {
57821
+ const episodeId = alertEpisodeId(alert);
57822
+ const open = isAlertEpisodeOpen(alert.status);
57823
+ if (open && !isAlertEpisodeSeverity(alert.severity)) continue;
57824
+ if (!open && !this.alertEpisodes.has(episodeId)) continue;
57825
+ const phase = open ? "raised" : "resolved";
57826
+ if (this.admitAlertEpisode(createEvent(EventCategory.AlertEpisode, {
57827
+ type: "addon",
57828
+ id: "notification-center"
57829
+ }, alertEpisodePayload(alert, phase, this.now())), "reconcile")) notified += 1;
57830
+ }
57831
+ const pruned = alerts.length < NC_ALERT_RECONCILE_LIMIT ? await this.alertEpisodes.pruneOlderThan(this.now() - NC_ALERT_EPISODE_MAX_AGE_MS) : 0;
57832
+ if (notified > 0 || pruned > 0) this.logger.info("alert episodes reconciled", { meta: {
57833
+ alerts: alerts.length,
57834
+ notified,
57835
+ pruned
57836
+ } });
57837
+ }
57838
+ /**
57449
57839
  * Boot reseed of the durable liveness ledger.
57450
57840
  *
57451
57841
  * The count is said OUT LOUD at info, because it is the one line that
@@ -94546,6 +94936,62 @@ function pickWritableMediaLocation(locations, stickyId) {
94546
94936
  return writable.toSorted((a, b) => b.headroomBytes - a.headroomBytes || a.id.localeCompare(b.id))[0]?.id ?? null;
94547
94937
  }
94548
94938
  //#endregion
94939
+ //#region src/pipeline-analytics/event-media-write-location.ts
94940
+ var EventMediaWriteLocator = class {
94941
+ deps;
94942
+ cached = null;
94943
+ /**
94944
+ * The last location actually picked. Survives {@link invalidate}, so a ring
94945
+ * keeps writing where it was writing while that stays writable — the
94946
+ * stickiness the class default used to provide (D383).
94947
+ */
94948
+ lastPick = null;
94949
+ constructor(deps) {
94950
+ this.deps = deps;
94951
+ }
94952
+ /** Drop the cached pick: the next write re-reads the location set. */
94953
+ invalidate() {
94954
+ this.cached = null;
94955
+ }
94956
+ async resolve(logger, deviceId) {
94957
+ if (this.cached !== null) return this.cached;
94958
+ let locations;
94959
+ try {
94960
+ locations = await this.deps.list();
94961
+ } catch (err) {
94962
+ logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
94963
+ this.cached = DEFAULT_MEDIA_LOCATION;
94964
+ return DEFAULT_MEDIA_LOCATION;
94965
+ }
94966
+ const picked = pickWritableMediaLocation(locations.map((l) => ({
94967
+ id: l.id,
94968
+ mode: resolveLocationMode(l),
94969
+ headroomBytes: l.capacity?.availableBytes ?? 0
94970
+ })), this.lastPick ?? void 0);
94971
+ if (picked === null) {
94972
+ const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
94973
+ logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
94974
+ tags: { deviceId },
94975
+ meta: { locationsSeen: seen }
94976
+ });
94977
+ throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
94978
+ }
94979
+ this.cached = picked;
94980
+ this.lastPick = picked;
94981
+ return picked;
94982
+ }
94983
+ };
94984
+ /**
94985
+ * Ring the locator from the location-set doorbell. `onRing` lets the caller drop
94986
+ * its other location-derived caches in the same breath.
94987
+ */
94988
+ function subscribeEventMediaWriteLocator(eventBus, locator, onRing) {
94989
+ return eventBus.subscribe({ category: EventCategory.StorageLocationsChanged }, () => {
94990
+ locator.invalidate();
94991
+ onRing();
94992
+ });
94993
+ }
94994
+ //#endregion
94549
94995
  //#region src/pipeline-analytics/media-relocate-engine.ts
94550
94996
  /**
94551
94997
  * Event-media relocation engine (entity-routing spec, Phase 4) — the
@@ -104539,7 +104985,8 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
104539
104985
  EventCategory.SystemRestartCompleted,
104540
104986
  EventCategory.SystemRestarting,
104541
104987
  EventCategory.RecordingExportCompleted,
104542
- EventCategory.BackupRunCompleted
104988
+ EventCategory.BackupRunCompleted,
104989
+ EventCategory.AlertEpisode
104543
104990
  ];
104544
104991
  /**
104545
104992
  * Install the addon's bus subscriptions. Identical set + relative order to the
@@ -107427,6 +107874,15 @@ Object.freeze({
107427
107874
  "valueType": "boolean"
107428
107875
  }],
107429
107876
  "dishwasher": [
107877
+ {
107878
+ "path": "availability",
107879
+ "valueType": "enum",
107880
+ "enumValues": [
107881
+ "available",
107882
+ "offline",
107883
+ "description-missing"
107884
+ ]
107885
+ },
107430
107886
  {
107431
107887
  "path": "state",
107432
107888
  "valueType": "enum",
@@ -107439,12 +107895,35 @@ Object.freeze({
107439
107895
  "unavailable"
107440
107896
  ]
107441
107897
  },
107898
+ {
107899
+ "path": "operationState",
107900
+ "valueType": "enum",
107901
+ "enumValues": [
107902
+ "Inactive",
107903
+ "Ready",
107904
+ "DelayedStart",
107905
+ "Run",
107906
+ "Pause",
107907
+ "ActionRequired",
107908
+ "Finished",
107909
+ "Error",
107910
+ "Aborting"
107911
+ ]
107912
+ },
107442
107913
  {
107443
107914
  "path": "program",
107444
107915
  "valueType": "string"
107445
107916
  },
107446
107917
  {
107447
- "path": "remainingMinutes",
107918
+ "path": "selectedProgram",
107919
+ "valueType": "string"
107920
+ },
107921
+ {
107922
+ "path": "remainingSeconds",
107923
+ "valueType": "number"
107924
+ },
107925
+ {
107926
+ "path": "delayedStartSeconds",
107448
107927
  "valueType": "number"
107449
107928
  },
107450
107929
  {
@@ -107455,14 +107934,30 @@ Object.freeze({
107455
107934
  "path": "doorOpen",
107456
107935
  "valueType": "boolean"
107457
107936
  },
107937
+ {
107938
+ "path": "doorLocked",
107939
+ "valueType": "boolean"
107940
+ },
107458
107941
  {
107459
107942
  "path": "remoteStartAllowed",
107460
107943
  "valueType": "boolean"
107461
107944
  },
107945
+ {
107946
+ "path": "remoteControlActive",
107947
+ "valueType": "boolean"
107948
+ },
107949
+ {
107950
+ "path": "localControlActive",
107951
+ "valueType": "boolean"
107952
+ },
107462
107953
  {
107463
107954
  "path": "powerOn",
107464
107955
  "valueType": "boolean"
107465
107956
  },
107957
+ {
107958
+ "path": "powerState",
107959
+ "valueType": "string"
107960
+ },
107466
107961
  {
107467
107962
  "path": "saltLevel",
107468
107963
  "valueType": "enum",
@@ -111046,20 +111541,6 @@ var MigrationMarkerStore = class MigrationMarkerStore {
111046
111541
  };
111047
111542
  };
111048
111543
  //#endregion
111049
- //#region src/pipeline-analytics/store/analytics-history-keys.ts
111050
- /** Declared addon series keys (D850). */
111051
- var ANALYTICS_HISTORY_BUCKET_SERIES = {
111052
- motion: "addon:pipeline-analytics/motion",
111053
- "audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
111054
- occupancy: "addon:pipeline-analytics/occupancy"
111055
- };
111056
- var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
111057
- /** Map the public cap enum to the device-history key (read path). */
111058
- function historyKeyForLtsSeries(series) {
111059
- if (series === "battery") return "cap:battery.percentage";
111060
- return ANALYTICS_HISTORY_BUCKET_SERIES[series];
111061
- }
111062
- //#endregion
111063
111544
  //#region src/pipeline-analytics/store/analytics-history-producer.ts
111064
111545
  var BUCKET_SERIES_META = [
111065
111546
  {
@@ -111081,6 +111562,10 @@ var AUDIO_LEVEL_SERIES = {
111081
111562
  displayName: "Audio level (dBFS)",
111082
111563
  unit: "dBFS"
111083
111564
  };
111565
+ /** One audio level point per window; equals device-history's 1 point / 10 s record bound. */
111566
+ var AUDIO_LEVEL_WINDOW_MS = 1e4;
111567
+ /** Stored for a measured-silent window (−∞ dBFS cannot be a history value). */
111568
+ var AUDIO_LEVEL_SILENT_DBFS = -120;
111084
111569
  function refusalCounts(result) {
111085
111570
  const counts = {};
111086
111571
  for (const r of result.refused) counts[r.reason] = (counts[r.reason] ?? 0) + r.count;
@@ -111092,6 +111577,7 @@ var AnalyticsHistoryProducer = class {
111092
111577
  logger;
111093
111578
  nowFn;
111094
111579
  declared = false;
111580
+ levelWindows = /* @__PURE__ */ new Map();
111095
111581
  constructor(deps) {
111096
111582
  this.deviceHistory = deps.deviceHistory;
111097
111583
  this.logger = deps.logger;
@@ -111124,6 +111610,43 @@ var AnalyticsHistoryProducer = class {
111124
111610
  deviceId
111125
111611
  }, scope ?? "", value, at ?? this.nowFn());
111126
111612
  }
111613
+ /**
111614
+ * Feed EVERY analysed audio sample. One point per window of
111615
+ * {@link AUDIO_LEVEL_WINDOW_MS} is written, stamped at the window's first
111616
+ * sample, carrying the window's PEAK dBFS: a 2 s spike inside a stable window
111617
+ * is not averaged away, and stable audio still yields a point each window
111618
+ * (continuity over dedup — the level bar draws a continuous line).
111619
+ *
111620
+ * Measured silence (−∞) is stored at {@link AUDIO_LEVEL_SILENT_DBFS}: it is a
111621
+ * measurement, not an absence (D431); NaN carries no claim and is dropped.
111622
+ * A window closes on the first sample past its end, so no timer is needed and
111623
+ * points are always at least a window apart (the history's 1 point / 10 s
111624
+ * bound).
111625
+ */
111626
+ observeAudioLevel(deviceId, dbfs, at) {
111627
+ if (Number.isNaN(dbfs)) return;
111628
+ const value = Number.isFinite(dbfs) ? dbfs : AUDIO_LEVEL_SILENT_DBFS;
111629
+ const win = this.levelWindows.get(deviceId);
111630
+ if (win === void 0) {
111631
+ this.levelWindows.set(deviceId, {
111632
+ start: at,
111633
+ peak: value
111634
+ });
111635
+ return;
111636
+ }
111637
+ if (at - win.start < AUDIO_LEVEL_WINDOW_MS) {
111638
+ if (value > win.peak) this.levelWindows.set(deviceId, {
111639
+ start: win.start,
111640
+ peak: value
111641
+ });
111642
+ return;
111643
+ }
111644
+ this.levelWindows.set(deviceId, {
111645
+ start: at,
111646
+ peak: value
111647
+ });
111648
+ this.recordAudioLevel(deviceId, win.peak, win.start);
111649
+ }
111127
111650
  recordAudioLevel(deviceId, dbfs, at) {
111128
111651
  const record = this.deviceHistory.record;
111129
111652
  if (record === void 0) return;
@@ -113784,20 +114307,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113784
114307
  mediaRelocate = null;
113785
114308
  mediaLocationStorage = null;
113786
114309
  /**
113787
- * Cached pick for NEW `eventMedia` writes (`pickWritableMediaLocation`).
113788
- * Resolved once, never per blob (no-RPC-per-blob, same discipline as
113789
- * `mediaLocationStorage`'s own root cache) — dropped together with it by
113790
- * `refreshStorageLocationsForMigration`, since a location's `readOnly` /
113791
- * `enabled` flags only change via operator reconfig.
113792
- */
113793
- eventMediaWriteLocationCache = null;
113794
- /**
113795
- * The last location this process actually picked for an `eventMedia` write.
113796
- * Survives the cache invalidation a migration performs, so a refresh keeps
113797
- * writing where it was writing while that stays writable — the stickiness the
113798
- * class default used to provide before it was deleted (D383).
114310
+ * Where NEW `eventMedia` writes land. Its pick is cached per location-set
114311
+ * generation and dropped by the `StorageLocationsChanged` doorbell (D387) and
114312
+ * by `refreshStorageLocationsForMigration`, so a mode change or a repoint takes
114313
+ * effect without a restart.
113799
114314
  */
113800
- lastEventMediaWritePick = null;
114315
+ eventMediaWriteLocator = null;
114316
+ eventMediaLocationDoorbellUnsub = null;
113801
114317
  mediaWriteGate = new MediaWriteGate();
113802
114318
  storageMigrationLeaseId = null;
113803
114319
  /** Per-camera history of LINKED-device sensor state changes (Part B). */
@@ -115837,33 +116353,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
115837
116353
  }
115838
116354
  /** One line per process: this repeats on every occupancy refresh otherwise. */
115839
116355
  mediaOccupancyUnattributedLogged = false;
115840
- async resolveEventMediaWriteLocation(api, logger, deviceId) {
115841
- const cached = this.eventMediaWriteLocationCache;
115842
- if (cached !== null) return cached;
115843
- let locations;
115844
- try {
115845
- locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
115846
- } catch (err) {
115847
- logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
115848
- this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
115849
- return DEFAULT_MEDIA_LOCATION;
115850
- }
115851
- const picked = pickWritableMediaLocation(locations.map((l) => ({
115852
- id: l.id,
115853
- mode: resolveLocationMode(l),
115854
- headroomBytes: l.capacity?.availableBytes ?? 0
115855
- })), this.lastEventMediaWritePick ?? void 0);
115856
- if (picked === null) {
115857
- const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
115858
- logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
115859
- tags: { deviceId },
115860
- meta: { locationsSeen: seen }
115861
- });
115862
- throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
115863
- }
115864
- this.eventMediaWriteLocationCache = picked;
115865
- this.lastEventMediaWritePick = picked;
115866
- return picked;
116356
+ resolveEventMediaWriteLocation(api, logger, deviceId) {
116357
+ this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
116358
+ return this.eventMediaWriteLocator.resolve(logger, deviceId);
115867
116359
  }
115868
116360
  /** Constructs every SQLite-backed store plus the stationary/package-drop/
115869
116361
  * sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
@@ -115964,6 +116456,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
115964
116456
  resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
115965
116457
  });
115966
116458
  this.mediaStore = mediaStore;
116459
+ this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
116460
+ this.eventMediaLocationDoorbellUnsub?.();
116461
+ this.eventMediaLocationDoorbellUnsub = subscribeEventMediaWriteLocator(this.ctx.eventBus, this.eventMediaWriteLocator, () => this.mediaLocationStorage?.invalidateRoots());
115967
116462
  const eventLedgerWiring = this.eventLedgerWiring(api.settingsStore);
115968
116463
  const retrainFrames = new RetrainFrameStore({
115969
116464
  store: api.settingsStore,
@@ -116114,6 +116609,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
116114
116609
  const eventStore = new EventStore({
116115
116610
  store: api.settingsStore,
116116
116611
  logger: logger.child("EventStore"),
116612
+ audioLevelHistory: (input) => api.deviceHistory.query.query(input),
116117
116613
  motionLedger: wireMotionEventLedger(eventLedgerWiring),
116118
116614
  audioLedger: wireAudioEventLedger(eventLedgerWiring),
116119
116615
  onMotionSample: (deviceId, regionCount, at) => lts.note("motion", deviceId, regionCount, at),
@@ -116546,6 +117042,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
116546
117042
  coreBlocks: async () => (await api.coreBlocks.list.query({})).blocks,
116547
117043
  nodeAddons: (nodeId) => api.nodes.getNodeAddons.query({ nodeId })
116548
117044
  },
117045
+ readAlerts: () => api.alerts.list.query({ limit: 5e3 }),
116549
117046
  getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
116550
117047
  recognitionOutlook: ({ deviceId, trackId, className, labelKind, now }) => recognitionOutlook({
116551
117048
  className,
@@ -118559,6 +119056,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
118559
119056
  this.alarmPanelConvergence?.stop();
118560
119057
  this.alarmPanelConvergence = null;
118561
119058
  for (const unsub of this.subscriptionUnsubs) unsub();
119059
+ this.eventMediaLocationDoorbellUnsub?.();
119060
+ this.eventMediaLocationDoorbellUnsub = null;
118562
119061
  this.subscriptionUnsubs = [];
118563
119062
  await Promise.all([this.drainSensorHandlers(), this.motionEpisodes?.closeAll("addon-shutdown")]);
118564
119063
  await this.settleTrackFusions();
@@ -121586,6 +122085,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
121586
122085
  score: topClassification.score
121587
122086
  } : {}
121588
122087
  }, this.audioMarkerConfig(settings));
122088
+ if (level !== void 0) this.lts?.observeAudioLevel(deviceId, level.dbfs, timestamp);
121589
122089
  this.notificationCenter?.observeAudio(deviceId, ncAudioSampleFromFrame(frame, timestamp, settings));
121590
122090
  const route = classifyAudioFrame(topClassification ? {
121591
122091
  className: topClassification.macroClass,
@@ -121661,8 +122161,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
121661
122161
  });
121662
122162
  this.levelStateByDevice.set(deviceId, res.nextState);
121663
122163
  if (!res.emit) return;
121664
- const dbfs = level?.dbfs ?? Number.NEGATIVE_INFINITY;
121665
- this.lts?.recordAudioLevel(deviceId, dbfs, timestamp);
121666
122164
  }
121667
122165
  /**
121668
122166
  * Persist motion-analysis bus events into the EPISODE tracker (D475): a
@@ -125813,7 +126311,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
125813
126311
  async refreshStorageLocationsForMigration(input) {
125814
126312
  if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
125815
126313
  this.mediaLocationStorage?.invalidateRoots();
125816
- this.eventMediaWriteLocationCache = null;
126314
+ this.eventMediaWriteLocator?.invalidate();
125817
126315
  return { refreshed: true };
125818
126316
  }
125819
126317
  /**
@@ -126176,7 +126674,24 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
126176
126674
  async startStorageMigrationMove(input) {
126177
126675
  if (!this.mediaRelocate) throw new Error("media relocation unavailable");
126178
126676
  if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
126179
- return { jobId: this.mediaRelocate.start(input) };
126677
+ return { jobId: await this.startMediaMover(input) };
126678
+ }
126679
+ /**
126680
+ * Start a media move only while no single-location repoint holds an overlapping
126681
+ * physical root (the storage orchestrator owns that claim). A shared recordings
126682
+ * root is the case: a repoint of another class there must not run beside us.
126683
+ */
126684
+ startMediaMover(input) {
126685
+ const engine = this.mediaRelocate;
126686
+ if (!engine) throw new Error("media relocation unavailable");
126687
+ return startMoverUnderClearance({
126688
+ clearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
126689
+ start: () => engine.start(input),
126690
+ cancel: (jobId) => {
126691
+ engine.cancel(jobId);
126692
+ },
126693
+ locationIds: [input.fromLocationId ?? "*", input.toLocationId]
126694
+ });
126180
126695
  }
126181
126696
  async getStorageMigrationMoveStatus(input) {
126182
126697
  return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
@@ -126194,7 +126709,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
126194
126709
  */
126195
126710
  async relocateMedia(input) {
126196
126711
  if (!this.mediaRelocate) throw new Error("media relocation unavailable");
126197
- const jobId = this.mediaRelocate.start(input);
126712
+ const jobId = await this.startMediaMover(input);
126198
126713
  this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
126199
126714
  jobId,
126200
126715
  mode: input.mode ?? "move",