@camstack/addon-post-analysis 1.2.607 → 1.2.608

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 +457 -51
  11. package/dist/pipeline-analytics/index.mjs +457 -51
  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);
@@ -32855,6 +33037,12 @@ var en_default = {
32855
33037
  "system.backup-completed.title": "Backup completed",
32856
33038
  "system.backup-completed.body": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations ({{sizeMB}} MB)",
32857
33039
  "system.backup-completed.body.partial": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations — {{destinationsFailed}} failed",
33040
+ "system.alert-critical.title": "{{title}}",
33041
+ "system.alert-critical.body": "{{message}}",
33042
+ "system.alert-error.title": "{{title}}",
33043
+ "system.alert-error.body": "{{message}}",
33044
+ "system.alert-critical-resolved.title": "Resolved: {{title}}",
33045
+ "system.alert-critical-resolved.body": "{{message}}",
32858
33046
  "system.device-consumable-low.title": "Consumable low — {{deviceName}}",
32859
33047
  "system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
32860
33048
  "system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
@@ -33235,6 +33423,12 @@ var it_default = {
33235
33423
  "system.backup-completed.title": "Backup completato",
33236
33424
  "system.backup-completed.body": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} ({{sizeMB}} MB)",
33237
33425
  "system.backup-completed.body.partial": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} — {{destinationsFailed}} fallite",
33426
+ "system.alert-critical.title": "{{title}}",
33427
+ "system.alert-critical.body": "{{message}}",
33428
+ "system.alert-error.title": "{{title}}",
33429
+ "system.alert-error.body": "{{message}}",
33430
+ "system.alert-critical-resolved.title": "Risolto: {{title}}",
33431
+ "system.alert-critical-resolved.body": "{{message}}",
33238
33432
  "system.device-consumable-low.title": "Consumabile basso — {{deviceName}}",
33239
33433
  "system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
33240
33434
  "system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
@@ -35210,6 +35404,7 @@ function systemEventSubject(kind, scope) {
35210
35404
  if (kind === "node-updating") return `node-updating:${scope.nodeId ?? "hub"}:1.0.1`;
35211
35405
  if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
35212
35406
  if (kind === "backup-completed") return "backup:test";
35407
+ if (kind.startsWith("alert-")) return "alert:test";
35213
35408
  return `alarm:${scope.deviceId ?? 0}`;
35214
35409
  }
35215
35410
  /**
@@ -35350,7 +35545,12 @@ function planRuleTest(input) {
35350
35545
  nodes: "hub",
35351
35546
  fromVersion: "1.0.0",
35352
35547
  toVersion: "1.0.1",
35353
- title: "Export",
35548
+ title: systemEventKind.startsWith("alert-") ? "Recording location full" : "Export",
35549
+ message: "No free space left on the recording location",
35550
+ severity: systemEventKind === "alert-error" ? "error" : "critical",
35551
+ category: "recorder.placement",
35552
+ source: "recorder",
35553
+ actionUrl: "/system/addons",
35354
35554
  count: "1"
35355
35555
  },
35356
35556
  ...systemScope.deviceId !== void 0 ? { deviceId: systemScope.deviceId } : {},
@@ -51921,6 +52121,10 @@ function groupedAlarmPhaseKey(ruleId, trackId) {
51921
52121
  var NO_MATCH_REPORT_INTERVAL_MS = 6e4;
51922
52122
  var DEFAULT_DRAIN_INTERVAL_MS = 2e3;
51923
52123
  var DEFAULT_RULE_RELOAD_INTERVAL_MS = 3e4;
52124
+ /** How often open critical/error alerts are reconciled against the notified episodes. */
52125
+ var NC_ALERT_RECONCILE_INTERVAL_MS = 5 * 6e4;
52126
+ /** The most alerts one reconcile reads (the alert centre's own ceiling). */
52127
+ var NC_ALERT_RECONCILE_LIMIT = 5e3;
51924
52128
  var DEFAULT_RECONCILE_WINDOW_MS = 15 * 6e4;
51925
52129
  /** Overlap subtracted from the watermark on boot (idempotent, cheap). */
51926
52130
  var RECONCILE_OVERLAP_MS = 6e4;
@@ -52493,6 +52697,15 @@ var NotificationCenter = class {
52493
52697
  * seeds, only a real flip notifies.
52494
52698
  */
52495
52699
  liveness;
52700
+ /**
52701
+ * Which alert episodes were already notified (`alert-episode-ledger.ts`): the
52702
+ * alert centre sends nothing of its own, so this is what makes its bus
52703
+ * announcements and the reconcile from the alerts store safe to run together.
52704
+ */
52705
+ alertEpisodes;
52706
+ /** The boot load of {@link alertEpisodes} has been ATTEMPTED (bus edges before it are the reconcile's). */
52707
+ alertEpisodesLoaded = false;
52708
+ alertReconcileTimer = null;
52496
52709
  /** Batches `addon.updated` (version-bearing) into one notification per 30 s. */
52497
52710
  addonUpdates;
52498
52711
  /** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
@@ -53194,6 +53407,10 @@ var NotificationCenter = class {
53194
53407
  ...durableStore,
53195
53408
  logger: this.logger.child("liveness")
53196
53409
  });
53410
+ this.alertEpisodes = new NcAlertEpisodeLedger({
53411
+ ...durableStore,
53412
+ logger: this.logger.child("alert-episodes")
53413
+ });
53197
53414
  this.addonUpdates = new AddonUpdateCoalescer((updates) => {
53198
53415
  const incoming = incomingFromAddonUpdates({
53199
53416
  eventId: `addon-updated:${updates.map((u) => u.packageName).join(",")}`,
@@ -53590,6 +53807,10 @@ var NotificationCenter = class {
53590
53807
  this.reloadTimer = setInterval(() => {
53591
53808
  this.reloadRules();
53592
53809
  }, this.deps.ruleReloadIntervalMs ?? DEFAULT_RULE_RELOAD_INTERVAL_MS);
53810
+ this.alertReconcileTimer = setInterval(() => {
53811
+ this.reconcileAlertEpisodes();
53812
+ }, this.deps.alertReconcileIntervalMs ?? NC_ALERT_RECONCILE_INTERVAL_MS);
53813
+ this.alertReconcileTimer.unref?.();
53593
53814
  const timelapseScheduler = this.timelapseScheduler;
53594
53815
  if (timelapseScheduler !== null) await timed("timelapseProducer", () => timelapseScheduler.start()).catch((err) => {
53595
53816
  this.logger.warn("timelapse producer did not start", { meta: { error: String(err) } });
@@ -53705,6 +53926,7 @@ var NotificationCenter = class {
53705
53926
  await this.deviceDirectory.refresh();
53706
53927
  })) return;
53707
53928
  if (!await phase("liveness", () => this.hydrateLiveness())) return;
53929
+ if (!await phase("alertEpisodes", () => this.hydrateAlertEpisodes())) return;
53708
53930
  if (!await phase("deviceAlerts", async () => {
53709
53931
  const rows = await this.alertStore.load();
53710
53932
  this.logger.info("device alert ledger reseeded", { meta: { rows } });
@@ -53747,6 +53969,10 @@ var NotificationCenter = class {
53747
53969
  clearInterval(this.reloadTimer);
53748
53970
  this.reloadTimer = null;
53749
53971
  }
53972
+ if (this.alertReconcileTimer !== null) {
53973
+ clearInterval(this.alertReconcileTimer);
53974
+ this.alertReconcileTimer = null;
53975
+ }
53750
53976
  this.timelapseScheduler?.stop();
53751
53977
  this.summaryProducer?.stop();
53752
53978
  await this.closeGroupedWindowsOnStop();
@@ -54758,6 +54984,10 @@ var NotificationCenter = class {
54758
54984
  return;
54759
54985
  }
54760
54986
  this.stateSummary.observeRawEvent(event.category, event.data, this.now(), event.source.nodeId);
54987
+ if (event.category === "alert.episode") {
54988
+ if (this.alertEpisodesLoaded) this.admitAlertEpisode(event, "bus");
54989
+ return;
54990
+ }
54761
54991
  if (event.category === "addon.started" || event.category === "addon.restarted") return;
54762
54992
  if (event.category === "pipeline.camera-assigned") return;
54763
54993
  const battery = batterySliceOf(event);
@@ -54791,10 +55021,6 @@ var NotificationCenter = class {
54791
55021
  const system = incoming.subject.systemEvent;
54792
55022
  if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
54793
55023
  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
55024
  const observation = system !== void 0 ? ledgeredLiveness(system) : null;
54799
55025
  if (observation !== null) {
54800
55026
  const previous = this.liveness.stateOf(observation.subject);
@@ -57446,6 +57672,89 @@ var NotificationCenter = class {
57446
57672
  await this.occupancyStore.pruneExcept(this.activeOccupancyKeys());
57447
57673
  }
57448
57674
  /**
57675
+ * One edge of an alert episode, from the bus or from the reconcile. The
57676
+ * ledger decides whether it is news; only news reaches the rules.
57677
+ */
57678
+ admitAlertEpisode(event, via) {
57679
+ const payload = parseAlertEpisodePayload(event.data);
57680
+ if (payload === null) {
57681
+ this.logger.warn("alert episode event with an unreadable payload ignored", { meta: {
57682
+ via,
57683
+ eventId: event.id
57684
+ } });
57685
+ return false;
57686
+ }
57687
+ const verdict = this.alertEpisodes.admit({
57688
+ kind: alertEpisodeKind(payload.severity, payload.phase),
57689
+ phase: payload.phase,
57690
+ episodeId: payload.episodeId,
57691
+ alertId: payload.alertId
57692
+ }, this.now());
57693
+ const meta = {
57694
+ via,
57695
+ alertId: payload.alertId,
57696
+ episodeId: payload.episodeId,
57697
+ phase: payload.phase,
57698
+ severity: payload.severity,
57699
+ verdict
57700
+ };
57701
+ if (verdict !== "notify") {
57702
+ this.logger.debug("alert episode edge not notified", { meta });
57703
+ return false;
57704
+ }
57705
+ const incoming = incomingFromSystemEvent(event, this.deviceDirectory.lookup());
57706
+ if (incoming === null) return false;
57707
+ this.logger.info("alert episode notified to the rules", { meta });
57708
+ this.consumeEvent(incoming);
57709
+ return true;
57710
+ }
57711
+ async hydrateAlertEpisodes() {
57712
+ try {
57713
+ const rows = await this.alertEpisodes.load();
57714
+ this.logger.info("alert episode ledger reseeded", { meta: { rows } });
57715
+ } finally {
57716
+ this.alertEpisodesLoaded = true;
57717
+ }
57718
+ await this.reconcileAlertEpisodes();
57719
+ }
57720
+ /**
57721
+ * The alerts store against the notified episodes (D11, D49): every OPEN
57722
+ * critical/error alert not yet notified is notified, and every notified
57723
+ * episode whose alert is CLOSED gets its resolution. Never runs on a ledger
57724
+ * the store has not answered — an empty, unread mirror would re-notify every
57725
+ * open alert — and never reads absence as closure: an alert that is not in
57726
+ * the answer is left alone (rows age out by {@link NC_ALERT_EPISODE_MAX_AGE_MS}).
57727
+ */
57728
+ async reconcileAlertEpisodes() {
57729
+ const read = this.deps.readAlerts;
57730
+ if (read === void 0 || !this.evaluationActive || !this.alertEpisodes.hasAnswered()) return;
57731
+ let alerts;
57732
+ try {
57733
+ alerts = await read();
57734
+ } catch (err) {
57735
+ this.logger.warn("alert episode reconcile could not read the alerts", { meta: { error: errMsg(err) } });
57736
+ return;
57737
+ }
57738
+ let notified = 0;
57739
+ for (const alert of alerts) {
57740
+ const episodeId = alertEpisodeId(alert);
57741
+ const open = isAlertEpisodeOpen(alert.status);
57742
+ if (open && !isAlertEpisodeSeverity(alert.severity)) continue;
57743
+ if (!open && !this.alertEpisodes.has(episodeId)) continue;
57744
+ const phase = open ? "raised" : "resolved";
57745
+ if (this.admitAlertEpisode(createEvent(EventCategory.AlertEpisode, {
57746
+ type: "addon",
57747
+ id: "notification-center"
57748
+ }, alertEpisodePayload(alert, phase, this.now())), "reconcile")) notified += 1;
57749
+ }
57750
+ const pruned = alerts.length < NC_ALERT_RECONCILE_LIMIT ? await this.alertEpisodes.pruneOlderThan(this.now() - NC_ALERT_EPISODE_MAX_AGE_MS) : 0;
57751
+ if (notified > 0 || pruned > 0) this.logger.info("alert episodes reconciled", { meta: {
57752
+ alerts: alerts.length,
57753
+ notified,
57754
+ pruned
57755
+ } });
57756
+ }
57757
+ /**
57449
57758
  * Boot reseed of the durable liveness ledger.
57450
57759
  *
57451
57760
  * The count is said OUT LOUD at info, because it is the one line that
@@ -94546,6 +94855,62 @@ function pickWritableMediaLocation(locations, stickyId) {
94546
94855
  return writable.toSorted((a, b) => b.headroomBytes - a.headroomBytes || a.id.localeCompare(b.id))[0]?.id ?? null;
94547
94856
  }
94548
94857
  //#endregion
94858
+ //#region src/pipeline-analytics/event-media-write-location.ts
94859
+ var EventMediaWriteLocator = class {
94860
+ deps;
94861
+ cached = null;
94862
+ /**
94863
+ * The last location actually picked. Survives {@link invalidate}, so a ring
94864
+ * keeps writing where it was writing while that stays writable — the
94865
+ * stickiness the class default used to provide (D383).
94866
+ */
94867
+ lastPick = null;
94868
+ constructor(deps) {
94869
+ this.deps = deps;
94870
+ }
94871
+ /** Drop the cached pick: the next write re-reads the location set. */
94872
+ invalidate() {
94873
+ this.cached = null;
94874
+ }
94875
+ async resolve(logger, deviceId) {
94876
+ if (this.cached !== null) return this.cached;
94877
+ let locations;
94878
+ try {
94879
+ locations = await this.deps.list();
94880
+ } catch (err) {
94881
+ logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
94882
+ this.cached = DEFAULT_MEDIA_LOCATION;
94883
+ return DEFAULT_MEDIA_LOCATION;
94884
+ }
94885
+ const picked = pickWritableMediaLocation(locations.map((l) => ({
94886
+ id: l.id,
94887
+ mode: resolveLocationMode(l),
94888
+ headroomBytes: l.capacity?.availableBytes ?? 0
94889
+ })), this.lastPick ?? void 0);
94890
+ if (picked === null) {
94891
+ const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
94892
+ logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
94893
+ tags: { deviceId },
94894
+ meta: { locationsSeen: seen }
94895
+ });
94896
+ throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
94897
+ }
94898
+ this.cached = picked;
94899
+ this.lastPick = picked;
94900
+ return picked;
94901
+ }
94902
+ };
94903
+ /**
94904
+ * Ring the locator from the location-set doorbell. `onRing` lets the caller drop
94905
+ * its other location-derived caches in the same breath.
94906
+ */
94907
+ function subscribeEventMediaWriteLocator(eventBus, locator, onRing) {
94908
+ return eventBus.subscribe({ category: EventCategory.StorageLocationsChanged }, () => {
94909
+ locator.invalidate();
94910
+ onRing();
94911
+ });
94912
+ }
94913
+ //#endregion
94549
94914
  //#region src/pipeline-analytics/media-relocate-engine.ts
94550
94915
  /**
94551
94916
  * Event-media relocation engine (entity-routing spec, Phase 4) — the
@@ -104539,7 +104904,8 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
104539
104904
  EventCategory.SystemRestartCompleted,
104540
104905
  EventCategory.SystemRestarting,
104541
104906
  EventCategory.RecordingExportCompleted,
104542
- EventCategory.BackupRunCompleted
104907
+ EventCategory.BackupRunCompleted,
104908
+ EventCategory.AlertEpisode
104543
104909
  ];
104544
104910
  /**
104545
104911
  * Install the addon's bus subscriptions. Identical set + relative order to the
@@ -107427,6 +107793,15 @@ Object.freeze({
107427
107793
  "valueType": "boolean"
107428
107794
  }],
107429
107795
  "dishwasher": [
107796
+ {
107797
+ "path": "availability",
107798
+ "valueType": "enum",
107799
+ "enumValues": [
107800
+ "available",
107801
+ "offline",
107802
+ "description-missing"
107803
+ ]
107804
+ },
107430
107805
  {
107431
107806
  "path": "state",
107432
107807
  "valueType": "enum",
@@ -107439,12 +107814,35 @@ Object.freeze({
107439
107814
  "unavailable"
107440
107815
  ]
107441
107816
  },
107817
+ {
107818
+ "path": "operationState",
107819
+ "valueType": "enum",
107820
+ "enumValues": [
107821
+ "Inactive",
107822
+ "Ready",
107823
+ "DelayedStart",
107824
+ "Run",
107825
+ "Pause",
107826
+ "ActionRequired",
107827
+ "Finished",
107828
+ "Error",
107829
+ "Aborting"
107830
+ ]
107831
+ },
107442
107832
  {
107443
107833
  "path": "program",
107444
107834
  "valueType": "string"
107445
107835
  },
107446
107836
  {
107447
- "path": "remainingMinutes",
107837
+ "path": "selectedProgram",
107838
+ "valueType": "string"
107839
+ },
107840
+ {
107841
+ "path": "remainingSeconds",
107842
+ "valueType": "number"
107843
+ },
107844
+ {
107845
+ "path": "delayedStartSeconds",
107448
107846
  "valueType": "number"
107449
107847
  },
107450
107848
  {
@@ -107455,14 +107853,30 @@ Object.freeze({
107455
107853
  "path": "doorOpen",
107456
107854
  "valueType": "boolean"
107457
107855
  },
107856
+ {
107857
+ "path": "doorLocked",
107858
+ "valueType": "boolean"
107859
+ },
107458
107860
  {
107459
107861
  "path": "remoteStartAllowed",
107460
107862
  "valueType": "boolean"
107461
107863
  },
107864
+ {
107865
+ "path": "remoteControlActive",
107866
+ "valueType": "boolean"
107867
+ },
107868
+ {
107869
+ "path": "localControlActive",
107870
+ "valueType": "boolean"
107871
+ },
107462
107872
  {
107463
107873
  "path": "powerOn",
107464
107874
  "valueType": "boolean"
107465
107875
  },
107876
+ {
107877
+ "path": "powerState",
107878
+ "valueType": "string"
107879
+ },
107466
107880
  {
107467
107881
  "path": "saltLevel",
107468
107882
  "valueType": "enum",
@@ -113784,20 +114198,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
113784
114198
  mediaRelocate = null;
113785
114199
  mediaLocationStorage = null;
113786
114200
  /**
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).
114201
+ * Where NEW `eventMedia` writes land. Its pick is cached per location-set
114202
+ * generation and dropped by the `StorageLocationsChanged` doorbell (D387) and
114203
+ * by `refreshStorageLocationsForMigration`, so a mode change or a repoint takes
114204
+ * effect without a restart.
113799
114205
  */
113800
- lastEventMediaWritePick = null;
114206
+ eventMediaWriteLocator = null;
114207
+ eventMediaLocationDoorbellUnsub = null;
113801
114208
  mediaWriteGate = new MediaWriteGate();
113802
114209
  storageMigrationLeaseId = null;
113803
114210
  /** Per-camera history of LINKED-device sensor state changes (Part B). */
@@ -115837,33 +116244,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
115837
116244
  }
115838
116245
  /** One line per process: this repeats on every occupancy refresh otherwise. */
115839
116246
  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;
116247
+ resolveEventMediaWriteLocation(api, logger, deviceId) {
116248
+ this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
116249
+ return this.eventMediaWriteLocator.resolve(logger, deviceId);
115867
116250
  }
115868
116251
  /** Constructs every SQLite-backed store plus the stationary/package-drop/
115869
116252
  * sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
@@ -115964,6 +116347,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
115964
116347
  resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
115965
116348
  });
115966
116349
  this.mediaStore = mediaStore;
116350
+ this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
116351
+ this.eventMediaLocationDoorbellUnsub?.();
116352
+ this.eventMediaLocationDoorbellUnsub = subscribeEventMediaWriteLocator(this.ctx.eventBus, this.eventMediaWriteLocator, () => this.mediaLocationStorage?.invalidateRoots());
115967
116353
  const eventLedgerWiring = this.eventLedgerWiring(api.settingsStore);
115968
116354
  const retrainFrames = new RetrainFrameStore({
115969
116355
  store: api.settingsStore,
@@ -116546,6 +116932,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
116546
116932
  coreBlocks: async () => (await api.coreBlocks.list.query({})).blocks,
116547
116933
  nodeAddons: (nodeId) => api.nodes.getNodeAddons.query({ nodeId })
116548
116934
  },
116935
+ readAlerts: () => api.alerts.list.query({ limit: 5e3 }),
116549
116936
  getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
116550
116937
  recognitionOutlook: ({ deviceId, trackId, className, labelKind, now }) => recognitionOutlook({
116551
116938
  className,
@@ -118559,6 +118946,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
118559
118946
  this.alarmPanelConvergence?.stop();
118560
118947
  this.alarmPanelConvergence = null;
118561
118948
  for (const unsub of this.subscriptionUnsubs) unsub();
118949
+ this.eventMediaLocationDoorbellUnsub?.();
118950
+ this.eventMediaLocationDoorbellUnsub = null;
118562
118951
  this.subscriptionUnsubs = [];
118563
118952
  await Promise.all([this.drainSensorHandlers(), this.motionEpisodes?.closeAll("addon-shutdown")]);
118564
118953
  await this.settleTrackFusions();
@@ -125813,7 +126202,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
125813
126202
  async refreshStorageLocationsForMigration(input) {
125814
126203
  if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
125815
126204
  this.mediaLocationStorage?.invalidateRoots();
125816
- this.eventMediaWriteLocationCache = null;
126205
+ this.eventMediaWriteLocator?.invalidate();
125817
126206
  return { refreshed: true };
125818
126207
  }
125819
126208
  /**
@@ -126176,7 +126565,24 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
126176
126565
  async startStorageMigrationMove(input) {
126177
126566
  if (!this.mediaRelocate) throw new Error("media relocation unavailable");
126178
126567
  if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
126179
- return { jobId: this.mediaRelocate.start(input) };
126568
+ return { jobId: await this.startMediaMover(input) };
126569
+ }
126570
+ /**
126571
+ * Start a media move only while no single-location repoint holds an overlapping
126572
+ * physical root (the storage orchestrator owns that claim). A shared recordings
126573
+ * root is the case: a repoint of another class there must not run beside us.
126574
+ */
126575
+ startMediaMover(input) {
126576
+ const engine = this.mediaRelocate;
126577
+ if (!engine) throw new Error("media relocation unavailable");
126578
+ return startMoverUnderClearance({
126579
+ clearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
126580
+ start: () => engine.start(input),
126581
+ cancel: (jobId) => {
126582
+ engine.cancel(jobId);
126583
+ },
126584
+ locationIds: [input.fromLocationId ?? "*", input.toLocationId]
126585
+ });
126180
126586
  }
126181
126587
  async getStorageMigrationMoveStatus(input) {
126182
126588
  return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
@@ -126194,7 +126600,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
126194
126600
  */
126195
126601
  async relocateMedia(input) {
126196
126602
  if (!this.mediaRelocate) throw new Error("media relocation unavailable");
126197
- const jobId = this.mediaRelocate.start(input);
126603
+ const jobId = await this.startMediaMover(input);
126198
126604
  this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
126199
126605
  jobId,
126200
126606
  mode: input.mode ?? "move",