@camstack/addon-post-analysis 1.2.310 → 1.2.312

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 (14) hide show
  1. package/dist/{clip-model-registry-xYrJkXdS.js → clip-model-registry-CRkjjUyj.js} +257 -18
  2. package/dist/{clip-model-registry-BROqj9pS.mjs → clip-model-registry-ivwvxxex.mjs} +210 -19
  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-KF5Fk8sD.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BocOElW2.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ds6vT2FJ.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-B8qeS8Da.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_KS96RA.mjs} +1 -1
  9. package/dist/pipeline-analytics/{hostInit-B3xcTHUM.mjs → hostInit-CRloCh1h.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +413 -31
  11. package/dist/pipeline-analytics/index.mjs +413 -31
  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-DytyGNZ-.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, $n as ruleDeviceCompatCapQueries, $t as TimelapseRuleInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as isDetectionMacroClass, Ar as sleep$1, At as NcTemplateVarDescriptorSchema, B as ListTrackRelationsInputSchema, Bn as memoizePinnedFfmpeg, Br as partialRecord, Bt as SCENE_DEFAULT_ANCHOR_THRESHOLD, C as DEFAULT_EVENT_COLOR, Cn as encodeVectorBase64, Cr as createEvent, Ct as NcSnoozeInputSchema, D as DeclaredDevices, Dn as gifDeriveSpeed, Dr as isDeviceScopedCap, Dt as NcTaxonomySchema, E as DETECTION_PIPELINE_CAP_NAME, En as failureContributionCapability, Er as hydrateSchema, Et as NcSystemEventKindSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as isSystemDelivery, Fr as discriminatedUnion, Ft as RECORDING_SOURCE_CAMSTACK_ADDON, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as plateGalleryCapability, Gr as union, Gt as SCENE_DIVERGED, H as LockStateSchema, Hn as occupancyScope, Hr as strictObject, Ht as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, I as FIRST_LEVEL_MACRO_CLASSES, In as isSystemEventSummaryRule, Ir as literal, It as RebuildObjectEmbeddingsRefusalSchema, J as MOTION_CLOSE_AFTER_MS, Jn as readTimelapseGeneratedAt, Jt as SetTrackPurityInputSchema, K as MAX_TRACK_PURITIES, Kn as readDeviceStateFrom, Kr as unknown, Kt as SCENE_RESET_RECAPTURES, L as FULL_IMAGE_BBOX, Ln as isVectorIndexDimConflict, Lr as looseObject, Lt as RetrainStatusSchema, M as EVENT_DENSITY_BATCH_MAX, Mn as isKnownTemplateVar, Mr as _enum, Mt as OpsLogEntrySchema, N as EVENT_KIND_BY_CAP, Nn as isMediaOwnerType, Nr as array, O as DetectionSourceSchema, On as gifFromMp4Args, Or as nodePin, Ot as NcTemplatePreviewInputSchema, P as EVENT_OWNER_TYPES, Pn as isScheduleActive, Pr as boolean, Pt as RECORDING_EXPORT_MAX_READ_BYTES, Q as NC_AUDIO_CONFIRM_HITS_DEFAULT, Qn as ruleBelongsToAlarm$1, Qt as TRACK_RELATIONS_DEFAULT_LIMIT, R as FailureCounters, Rn as kebabToCamel, Rr as number, Rt as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, S as CoverStateSchema, Sr as CamProfileSchema, St as NcScheduleSchema, T as DETECTION_MACRO_CLASSES, Tn as faceGalleryCapability, Tr as defineCustomActions, Tt as NcSnoozeSuppressedSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Ur as string, Ut as SCENE_DEFAULT_QUIET_SECONDS, V as ListTrackRelationsResultSchema, Vn as notificationRulesCapability, Vr as record, Vt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, W as MAX_BIRTH_DECISION_RECORDS, Wn as pipelineAnalyticsCapability, Wr as tuple, Wt as SCENE_DEFAULT_UNCOVERED_POLICY, X as MotionTriggeredBySchema, Xn as resolveLocationMode, Xt as SetTrackRelationInputSchema, Y as MediaFileKindEnum, Yn as resolveClipSearchMinScore, Yt as SetTrackPurityResultSchema, Z as NC_ALARM_SYSTEM_EVENT_KINDS, Zt as TIMELAPSE_DENSE_FLOOR_SEC, _ as BaseDevice, _n as describeSystemEventFamilyProblem, _r as evaluateSensorEdge, _t as NcRuleInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TrackSideSchema, ar as subKindsOf, at as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, b as ClearTrackRelationInputSchema, bn as embeddingRefusalOf, br as errMsg, bt as NcRuleTargetSchema, c as resolveClusterModelPin, cn as alarmPanelCapability, cr as systemEventFilterApplies, ct as NC_SNOOZE_MAX_MINUTES, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as audioModeOf, dr as templateCarriesSummary, dt as NC_TAXONOMY, en as TimelapseRulePatchSchema, er as ruleKindOf, et as NC_CLIP_POST_ROLL_DEFAULT_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as buildEventKindDescriptor, fr as templateVarsFor, ft as NC_TEMPLATE_VARS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as describeSystemEventDeliveryProblem, gr as canonicalHash, gt as NcConditionsSchema, h as ArchivedDebugNoteSchema, hn as deriveRecordingMode, hr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, ht as NcConditionDescriptorSchema, in as TrackRelationSchema, ir as storageOccupancyCapability, it as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as isEventOwnerType, jr as ZodObject, jt as OPS_LOG_DEFAULT_LIMIT, k as EMBEDDING_MODEL_FAILED, kn as isAudioClassName, kt as NcTemplatePreviewSchema, l as APPEARANCE_BIN_COUNT, ln as assertTimelapseCadences, lr as systemEventSummaryScheduleOf, lt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, m as AlarmArmModeSchema, mn as cosineSimilarity$1, mr as zoneAnalyticsCapability, mt as NcAlarmSettingsPatchSchema, nn as TrackPuritySchema, nr as sensorRaisedOf, nt as NC_CLIP_RETENTION_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, on as TrackSourceSchema, or as systemEventDeliveryProblems, ot as NC_DEFAULT_SNOOZE_MINUTES, p as AUDIO_MACRO_LABELS, pn as consumableLowPercentOf, pr as vectorDimFromBase64, pt as NcAlarmConfigSchema, q as MAX_TRACK_RELATIONS, qn as readMotionTriggeredBy, qr as EventCategory, qt as SceneMonitorSchema, rn as TrackRelationKindSchema, rr as servedTemplateVars, rt as NC_CONDITION_CATALOG, s as readClusterModelPinStrict, sn as addonWidgetsSourceCapability, sr as systemEventFamilyProblems, st as NC_SENSOR_TRANSITION_KINDS, t as BUILTIN_CLIP_MODELS, tn as TimelapseRuleSchema, tr as sceneMonitorCapability, tt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, u as APPEARANCE_BLOCKS, un as audioMetricsCapability, ur as systemEventSummarySplitOf, ut as NC_TANK_SYSTEM_EVENT_KINDS, v as BirthDecisionRecordSchema, vr as isSourceCap, vt as NcRulePatchSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as evictionPolicyOfLocation, wr as customAction, wt as NcSnoozeSchema, x as ClearTrackRelationResultSchema, xr as BaseAddon, xt as NcRuleUpdateInputSchema, y as COCO_TO_MACRO, yr as DeviceType, yt as NcRuleSchema, z as LabelAttributionSchema, zn as mayWriteToLocation, zr as object, zt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS } from "../clip-model-registry-BROqj9pS.mjs";
1
+ import { $ as NC_ALARM_SYSTEM_EVENT_KINDS, $n as readDeviceStateFrom, $r as union, $t as SetTrackPurityInputSchema, A as EMBEDDING_REQUIREMENTS_FAILED, An as encodeVectorBase64, Ar as CamProfileSchema, At as NcSnoozeSuppressedSchema, B as ListTrackRelationsInputSchema, Bn as isMediaOwnerType, Br as _enum, Bt as RECORDING_SOURCE_CAMSTACK_ADDON, C as DEFAULT_EVENT_COLOR, Cn as describeSystemEventDeliveryProblem, Cr as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Ct as NcRulePatchSchema, D as DeclaredDevices, Dn as embeddingRefusalOf, Dr as DeviceType, Dt as NcScheduleSchema, E as DETECTION_PIPELINE_CAP_NAME, Er as isSourceCap, Et as NcRuleUpdateInputSchema, F as EXPORT_DENSE_MAX_RANGES, Fn as gifFromMp4Args, Fr as isDeviceScopedCap, Ft as NcTemplateVarDescriptorSchema, G as MAX_BIRTH_LATENCY_PROXY_MS, Gn as kebabToCamel, Gr as looseObject, Gt as SCENE_DEFAULT_ANCHOR_THRESHOLD, H as LockStateSchema, Hn as isSystemDelivery, Hr as boolean, Ht as RetrainStatusSchema, I as FIRST_LEVEL_MACRO_CLASSES, In as isAudioClassName, Ir as nodePin, It as OPS_LOG_DEFAULT_LIMIT, J as MOTION_CLOSE_AFTER_MS, Jn as notificationRulesCapability, Jr as partialRecord, Jt as SCENE_DEFAULT_QUIET_SECONDS, K as MAX_TRACK_PURITIES, Kn as mayWriteToLocation, Kr as number, Kt as SCENE_DEFAULT_CHECK_INTERVAL_SEC, L as FULL_IMAGE_BBOX, Ln as isDetectionMacroClass, Lt as OpsLogEntrySchema, M as EVENT_DENSITY_BATCH_MAX, Mn as faceGalleryCapability, Mr as customAction, Mt as NcTaxonomySchema, N as EVENT_KIND_BY_CAP, Nn as failureContributionCapability, Nr as defineCustomActions, Nt as NcTemplatePreviewInputSchema, O as DetectionSourceSchema, Or as errMsg, Ot as NcSnoozeInputSchema, P as EVENT_OWNER_TYPES, Pn as gifDeriveSpeed, Pr as hydrateSchema, Pt as NcTemplatePreviewSchema, Q as NC_ALARM_DELAY_MAX_SEC, Qn as plateGalleryCapability, Qr as tuple, Qt as SceneMonitorSchema, R as FailureCounters, Rn as isEventOwnerType, Rr as sleep$1, S as CoverStateSchema, Sn as deriveRecordingMode, Sr as zoneAnalyticsCapability, St as NcRuleInputSchema, T as DETECTION_MACRO_CLASSES, Tr as evaluateSensorEdge, Tt as NcRuleTargetSchema, U as MAX_ARCHIVED_DEBUG_NOTES, Un as isSystemEventSummaryRule, Ur as discriminatedUnion, Ut as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, V as ListTrackRelationsResultSchema, Vn as isScheduleActive, Vr as array, Vt as RebuildObjectEmbeddingsRefusalSchema, W as MAX_BIRTH_DECISION_RECORDS, Wn as isVectorIndexDimConflict, Wr as literal, Wt as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, X as MotionTriggeredBySchema, Xr as strictObject, Xt as SCENE_DIVERGED, Y as MediaFileKindEnum, Yn as occupancyScope, Yr as record, Yt as SCENE_DEFAULT_UNCOVERED_POLICY, Z as NC_ALARM_DELAY_FIELDS, Zn as pipelineAnalyticsCapability, Zr as string, Zt as SCENE_RESET_RECAPTURES, _ as BaseDevice, _n as audioMetricsCapability, _r as systemEventSummaryScheduleOf, _t as NcAlarmSensorsInputSchema, a as CLIP_EMBEDDING_STEP_ID, an as TimelapseRulePatchSchema, at as NC_CLIP_RETENTION_MAX_SEC, b as ClearTrackRelationInputSchema, bn as consumableLowPercentOf, br as templateVarsFor, bt as NcConditionDescriptorSchema, c as resolveClusterModelPin, cn as TrackRelationKindSchema, cr as ruleKindOf, ct as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, d as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, dn as TrackSourceSchema, dr as servedTemplateVars, dt as NC_SNOOZE_MAX_MINUTES, ei as unknown, en as SetTrackPurityResultSchema, er as readMotionTriggeredBy, et as NC_ALARM_TRIGGERED_MAX_SEC, f as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, fn as addonWidgetsSourceCapability, fr as storageOccupancyCapability, ft as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, g as BIRTH_DECISION_DEFAULT_LIMIT, gn as assertTimelapseCadences, gr as systemEventFilterApplies, gt as NcAlarmConfigSchema, h as ArchivedDebugNoteSchema, hn as applyNonBlockingEdit, hr as systemEventFamilyProblems, ht as NC_TEMPLATE_VARS, in as TimelapseRuleInputSchema, ir as resolveModeDelays, it as NC_CLIP_PRE_ROLL_DEFAULT_SEC, j as EMBEDDING_REQUIREMENTS_UNAVAILABLE, jn as evictionPolicyOfLocation, jr as createEvent, jt as NcSystemEventKindSchema, k as EMBEDDING_MODEL_FAILED, kr as BaseAddon, kt as NcSnoozeSchema, l as APPEARANCE_BIN_COUNT, ln as TrackRelationSchema, lr as sceneMonitorCapability, lt as NC_DEFAULT_SNOOZE_MINUTES, m as AlarmArmModeSchema, mn as applyModeDelaysEdits, mr as systemEventDeliveryProblems, mt as NC_TAXONOMY, nn as TIMELAPSE_DENSE_FLOOR_SEC, nr as resolveClipSearchMinScore, nt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, o as FACE_EMBEDDING_STEP_ID, on as TimelapseRuleSchema, or as ruleBelongsToAlarm$1, ot as NC_CONDITION_CATALOG, p as AUDIO_MACRO_LABELS, pn as alarmPanelCapability, pr as subKindsOf, pt as NC_TANK_SYSTEM_EVENT_KINDS, q as MAX_TRACK_RELATIONS, qn as memoizePinnedFfmpeg, qr as object, qt as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, rn as TRACK_RELATIONS_DEFAULT_LIMIT, rr as resolveLocationMode, rt as NC_CLIP_POST_ROLL_DEFAULT_SEC, s as readClusterModelPinStrict, sn as TrackPuritySchema, sr as ruleDeviceCompatCapQueries, st as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as EventCategory, tn as SetTrackRelationInputSchema, tr as readTimelapseGeneratedAt, tt as NC_AUDIO_CONFIRM_HITS_DEFAULT, u as APPEARANCE_BLOCKS, un as TrackSideSchema, ur as sensorRaisedOf, ut as NC_SENSOR_TRANSITION_KINDS, v as BirthDecisionRecordSchema, vn as audioModeOf, vr as systemEventSummarySplitOf, vt as NcAlarmSensorsSchema, w as DEFAULT_TIMELAPSE_PREVIEW_TEXT, wn as describeSystemEventFamilyProblem, wr as canonicalHash, wt as NcRuleSchema, x as ClearTrackRelationResultSchema, xn as cosineSimilarity$1, xr as vectorDimFromBase64, xt as NcConditionsSchema, y as COCO_TO_MACRO, yn as buildEventKindDescriptor, yr as templateCarriesSummary, yt as NcAlarmSettingsPatchSchema, z as LabelAttributionSchema, zn as isKnownTemplateVar, zr as ZodObject, zt as RECORDING_EXPORT_MAX_READ_BYTES } from "../clip-model-registry-ivwvxxex.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";
@@ -1389,11 +1389,85 @@ var NcAlarmExclusions = class {
1389
1389
  }
1390
1390
  };
1391
1391
  //#endregion
1392
+ //#region src/notification-center/alarm/alarm-mode-delays.ts
1393
+ /**
1394
+ * The persisted per-mode delay overrides, read through a guard (D697).
1395
+ *
1396
+ * The panel's config schema keeps `mode` a plain string so a blob written by a later release
1397
+ * with a mode this one does not know cannot fail the whole config. This is where that string is
1398
+ * narrowed: an entry that cannot be trusted is dropped and REPORTED, never repaired. An entry of an unknown
1399
+ * mode is also handed back raw (`foreign`) so a write can keep it.
1400
+ */
1401
+ function inBounds(value, max) {
1402
+ return Number.isInteger(value) && value >= 0 && value <= max;
1403
+ }
1404
+ /** Read the persisted overrides through a guard, never a cast (like `nonBlockingFromPersisted`). */
1405
+ function modeDelaysFromPersisted(raw) {
1406
+ const seen = /* @__PURE__ */ new Set();
1407
+ const entries = [];
1408
+ const dropped = [];
1409
+ const foreign = [];
1410
+ for (const item of raw) {
1411
+ const mode = AlarmArmModeSchema.safeParse(item.mode);
1412
+ if (!mode.success) {
1413
+ dropped.push({
1414
+ mode: item.mode,
1415
+ why: "unknown-mode"
1416
+ });
1417
+ foreign.push(item);
1418
+ continue;
1419
+ }
1420
+ if (seen.has(mode.data)) {
1421
+ dropped.push({
1422
+ mode: item.mode,
1423
+ why: "duplicate-mode"
1424
+ });
1425
+ continue;
1426
+ }
1427
+ seen.add(mode.data);
1428
+ const kept = { mode: mode.data };
1429
+ const fields = [
1430
+ {
1431
+ field: "exitDelaySec",
1432
+ max: NC_ALARM_DELAY_MAX_SEC
1433
+ },
1434
+ {
1435
+ field: "entryDelaySec",
1436
+ max: NC_ALARM_DELAY_MAX_SEC
1437
+ },
1438
+ {
1439
+ field: "triggeredDurationSec",
1440
+ max: NC_ALARM_TRIGGERED_MAX_SEC
1441
+ }
1442
+ ];
1443
+ for (const { field, max } of fields) {
1444
+ const value = item[field];
1445
+ if (value === void 0) continue;
1446
+ if (!inBounds(value, max)) {
1447
+ dropped.push({
1448
+ mode: item.mode,
1449
+ why: "out-of-bounds",
1450
+ field
1451
+ });
1452
+ continue;
1453
+ }
1454
+ kept[field] = value;
1455
+ }
1456
+ if (kept.exitDelaySec !== void 0 || kept.entryDelaySec !== void 0 || kept.triggeredDurationSec !== void 0) entries.push(kept);
1457
+ }
1458
+ return {
1459
+ entries,
1460
+ dropped,
1461
+ foreign
1462
+ };
1463
+ }
1464
+ //#endregion
1392
1465
  //#region src/notification-center/alarm/alarm-state-machine.ts
1393
1466
  /**
1394
- * Defaults an operator can change per panel. `home` has no exit delay on
1395
- * purpose — nobody is leaving — but the shared value is applied to every mode
1396
- * until per-mode delays are asked for.
1467
+ * The GLOBAL defaults an operator can change per panel — what a mode inherits
1468
+ * when it overrides nothing. A mode can override each of the three since D697
1469
+ * (`resolveModeDelays`): `home` has no exit delay on purpose — nobody is
1470
+ * leaving — and says so with its own entry, not by this shared value.
1397
1471
  *
1398
1472
  * `triggeredDurationSec: 0` = "sound until somebody disarms it". That is what
1399
1473
  * the panel did before the field existed, so an install that never opens the
@@ -1808,6 +1882,19 @@ var ncAlarmPanelSchema = object({
1808
1882
  nonBlocking: array(object({
1809
1883
  mode: string(),
1810
1884
  deviceIds: array(number().int())
1885
+ })).default([]),
1886
+ /**
1887
+ * Per arm mode, the delays that override the global ones (D697). Loose on purpose — `mode` is
1888
+ * a string and the numbers are unbounded here — and read back through
1889
+ * {@link modeDelaysFromPersisted}, never a cast: an entry this release cannot trust is dropped
1890
+ * and logged at boot, never the whole config. A blob written before D697 has no key and
1891
+ * parses to no overrides.
1892
+ */
1893
+ modeDelays: array(object({
1894
+ mode: string(),
1895
+ exitDelaySec: number().optional(),
1896
+ entryDelaySec: number().optional(),
1897
+ triggeredDurationSec: number().optional()
1811
1898
  })).default([])
1812
1899
  });
1813
1900
  /**
@@ -1878,7 +1965,6 @@ function machineFromPersisted(raw) {
1878
1965
  var NcAlarmPanelDevice = class extends BaseDevice {
1879
1966
  features = [];
1880
1967
  machine = DISARMED;
1881
- delays = DEFAULT_DELAYS;
1882
1968
  ticker = null;
1883
1969
  onPublish = null;
1884
1970
  /**
@@ -1909,7 +1995,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
1909
1995
  refusalSeq = 0;
1910
1996
  constructor(ctx) {
1911
1997
  super(ctx, ncAlarmPanelSchema, { type: DeviceType.AlarmPanel });
1912
- this.delays = this.readDelays();
1998
+ this.logDroppedModeDelays();
1913
1999
  this.restore();
1914
2000
  this.registerAlarmCap();
1915
2001
  this.publish();
@@ -1921,30 +2007,148 @@ var NcAlarmPanelDevice = class extends BaseDevice {
1921
2007
  ...this.readDelays(),
1922
2008
  announceArm: this.config.get("announceArm"),
1923
2009
  announceTargets: [...this.config.get("announceTargets")],
1924
- nonBlocking: nonBlockingFromPersisted(this.config.get("nonBlocking"))
2010
+ nonBlocking: nonBlockingFromPersisted(this.config.get("nonBlocking")),
2011
+ modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
1925
2012
  };
1926
2013
  }
1927
2014
  /**
1928
2015
  * Apply an editor's patch and return what the panel settled on.
1929
2016
  *
1930
2017
  * The new delays take effect on the NEXT transition, never retroactively: a
1931
- * machine already counting down keeps the instants it was given. Re-deriving
1932
- * `armedAt` from a delay the operator changed mid-exit would move the arm
1933
- * instant under somebody who is walking out of the door.
2018
+ * machine already counting down keeps the instants it was given (the exit
2019
+ * delay is read at arm, the entry delay and siren duration at trigger, and
2020
+ * the machine stores the instants, not the durations). Re-deriving `armedAt`
2021
+ * from a delay the operator changed mid-exit would move the arm instant under
2022
+ * somebody who is walking out of the door.
2023
+ *
2024
+ * Two phases, so a refused patch writes NOTHING:
2025
+ * 1. every refusal is checked first (a mode this panel does not offer, an
2026
+ * add to a full non-blocking list) — it logs why and throws;
2027
+ * 2. every LIST write is CALLED before the first `await`. `DeviceConfig.set`
2028
+ * replaces its in-memory value synchronously, so a list computed after an
2029
+ * await could be based on a list another call has since replaced — and a
2030
+ * whole-list write (the deleted-device prune sends one, computed
2031
+ * synchronously before calling: `device-prune-runner.ts` `applyAlarm`)
2032
+ * would erase the membership edit that landed in between.
1934
2033
  */
1935
2034
  async applySettings(patch) {
2035
+ const edits = patch.modeDelays ?? [];
2036
+ const membership = patch.nonBlockingEdit;
2037
+ for (const edit of edits) this.refuseUnofferedMode(edit.mode);
2038
+ if (membership !== void 0) {
2039
+ this.refuseUnofferedMode(membership.mode, membership.deviceId);
2040
+ const verdict = applyNonBlockingEdit(patch.nonBlocking !== void 0 ? patch.nonBlocking : nonBlockingFromPersisted(this.config.get("nonBlocking")), membership);
2041
+ if (verdict.kind === "refused") {
2042
+ this.ctx.logger.info("alarm settings refused", {
2043
+ tags: { deviceId: membership.deviceId },
2044
+ meta: {
2045
+ panelId: this.id,
2046
+ reason: "list-full",
2047
+ mode: verdict.mode
2048
+ }
2049
+ });
2050
+ throw new Error(`the ${verdict.mode} non-blocking list already holds ${verdict.max} devices — remove one first`);
2051
+ }
2052
+ }
2053
+ const listWrites = [];
2054
+ if (patch.nonBlocking !== void 0) listWrites.push(this.config.set("nonBlocking", patch.nonBlocking.map((l) => ({
2055
+ mode: l.mode,
2056
+ deviceIds: [...l.deviceIds]
2057
+ }))));
2058
+ let membershipChanged = null;
2059
+ if (membership !== void 0) {
2060
+ const result = applyNonBlockingEdit(nonBlockingFromPersisted(this.config.get("nonBlocking")), membership);
2061
+ if (result.kind === "applied") {
2062
+ membershipChanged = result.changed;
2063
+ if (result.changed) listWrites.push(this.config.set("nonBlocking", result.lists.map((l) => ({
2064
+ mode: l.mode,
2065
+ deviceIds: [...l.deviceIds]
2066
+ }))));
2067
+ }
2068
+ }
2069
+ const persistedDelays = modeDelaysFromPersisted(this.config.get("modeDelays"));
2070
+ const beforeDelays = persistedDelays.entries;
2071
+ const afterDelays = edits.length > 0 ? applyModeDelaysEdits(beforeDelays, edits) : beforeDelays;
2072
+ if (edits.length > 0) {
2073
+ for (const drop of persistedDelays.dropped) {
2074
+ if (drop.why === "unknown-mode") continue;
2075
+ this.ctx.logger.info("alarm per-mode delay discarded on write", {
2076
+ tags: { deviceId: this.id },
2077
+ meta: {
2078
+ mode: drop.mode,
2079
+ why: drop.why,
2080
+ field: drop.field
2081
+ }
2082
+ });
2083
+ }
2084
+ listWrites.push(this.config.set("modeDelays", [...afterDelays, ...persistedDelays.foreign]));
2085
+ }
2086
+ await Promise.all(listWrites);
2087
+ this.logModeDelayChanges(beforeDelays, afterDelays, edits);
1936
2088
  if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
1937
2089
  if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
1938
2090
  if (patch.triggeredDurationSec !== void 0) await this.config.set("triggeredDurationSec", patch.triggeredDurationSec);
1939
2091
  if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
1940
2092
  if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
1941
- if (patch.nonBlocking !== void 0) await this.config.set("nonBlocking", patch.nonBlocking.map((l) => ({
1942
- mode: l.mode,
1943
- deviceIds: [...l.deviceIds]
1944
- })));
1945
- this.delays = this.readDelays();
2093
+ if (membership !== void 0 && membershipChanged !== null) this.ctx.logger.info("alarm non-blocking list changed", {
2094
+ tags: { deviceId: membership.deviceId },
2095
+ meta: {
2096
+ panelId: this.id,
2097
+ mode: membership.mode,
2098
+ nonBlocking: membership.nonBlocking,
2099
+ changed: membershipChanged
2100
+ }
2101
+ });
1946
2102
  return this.settings();
1947
2103
  }
2104
+ /**
2105
+ * Refuse a mode this panel does not offer — logged by reason, then thrown. A membership edit
2106
+ * passes its SENSOR as `deviceId` (the tag); a per-mode delay edit defaults to the panel.
2107
+ */
2108
+ refuseUnofferedMode(mode, deviceId = this.id) {
2109
+ if (NC_ALARM_MODES.includes(mode)) return;
2110
+ this.ctx.logger.info("alarm settings refused", {
2111
+ tags: { deviceId },
2112
+ meta: {
2113
+ panelId: this.id,
2114
+ reason: "mode-not-offered",
2115
+ mode
2116
+ }
2117
+ });
2118
+ throw new Error(`alarm mode '${mode}' is not offered by this panel`);
2119
+ }
2120
+ /** One INFO line per mode-delay field an edit really changed (`null` = inherit). */
2121
+ logModeDelayChanges(before, after, edits) {
2122
+ for (const edit of edits) {
2123
+ const was = before.find((e) => e.mode === edit.mode);
2124
+ const now = after.find((e) => e.mode === edit.mode);
2125
+ for (const field of NC_ALARM_DELAY_FIELDS) {
2126
+ const from = was?.[field] ?? null;
2127
+ const to = now?.[field] ?? null;
2128
+ if (from === to) continue;
2129
+ this.ctx.logger.info("alarm mode delays changed", {
2130
+ tags: { deviceId: this.id },
2131
+ meta: {
2132
+ mode: edit.mode,
2133
+ field,
2134
+ from,
2135
+ to
2136
+ }
2137
+ });
2138
+ }
2139
+ }
2140
+ }
2141
+ /** One INFO line per persisted per-mode delay entry the guard refused to trust. */
2142
+ logDroppedModeDelays() {
2143
+ for (const drop of modeDelaysFromPersisted(this.config.get("modeDelays")).dropped) this.ctx.logger.info("alarm per-mode delay dropped at boot", {
2144
+ tags: { deviceId: this.id },
2145
+ meta: {
2146
+ mode: drop.mode,
2147
+ why: drop.why,
2148
+ field: drop.field
2149
+ }
2150
+ });
2151
+ }
1948
2152
  /** Wire the state mirror and the transition notifications. See
1949
2153
  * {@link NcAlarmPublishHook}. */
1950
2154
  setPublishHook(hook) {
@@ -2227,7 +2431,17 @@ var NcAlarmPanelDevice = class extends BaseDevice {
2227
2431
  else this.exclusions.clear();
2228
2432
  this.persist();
2229
2433
  }
2230
- this.machine = arm(mode, this.delays, now);
2434
+ const resolved = this.delaysFor(mode);
2435
+ this.machine = arm(mode, resolved, now);
2436
+ this.ctx.logger.info("alarm delays applied", {
2437
+ tags: { deviceId: this.id },
2438
+ meta: {
2439
+ mode,
2440
+ phase: "arm",
2441
+ exitDelaySec: resolved.exitDelaySec,
2442
+ source: resolved.source.exitDelaySec
2443
+ }
2444
+ });
2231
2445
  this.attribution.clear();
2232
2446
  this.publish(now);
2233
2447
  for (const { device, why } of exclusions ?? []) this.ctx.logger.info("alarm armed with an open device excluded", {
@@ -2327,7 +2541,9 @@ var NcAlarmPanelDevice = class extends BaseDevice {
2327
2541
  this.announceIgnoredTrigger(source, now);
2328
2542
  return;
2329
2543
  }
2330
- const next = trigger(this.machine, this.delays, now);
2544
+ const armed = armModeOf(this.machine.target, NC_ALARM_MODES);
2545
+ const resolved = this.delaysFor(armed);
2546
+ const next = trigger(this.machine, resolved, now);
2331
2547
  if (next === this.machine) {
2332
2548
  const state = stateAt(this.machine, now);
2333
2549
  this.ctx.logger.info(ignoredTriggerMessage(state), {
@@ -2345,6 +2561,19 @@ var NcAlarmPanelDevice = class extends BaseDevice {
2345
2561
  }
2346
2562
  });
2347
2563
  this.machine = next;
2564
+ this.ctx.logger.info("alarm delays applied", {
2565
+ tags: { deviceId: this.id },
2566
+ meta: {
2567
+ mode: armed,
2568
+ phase: "trigger",
2569
+ entryDelaySec: resolved.entryDelaySec,
2570
+ triggeredDurationSec: resolved.triggeredDurationSec,
2571
+ source: {
2572
+ entry: resolved.source.entryDelaySec,
2573
+ siren: resolved.source.triggeredDurationSec
2574
+ }
2575
+ }
2576
+ });
2348
2577
  this.publish(now);
2349
2578
  }
2350
2579
  });
@@ -2480,7 +2709,32 @@ var NcAlarmPanelDevice = class extends BaseDevice {
2480
2709
  });
2481
2710
  }
2482
2711
  }
2483
- /** The three durations, read from the persisted config. */
2712
+ /** The global durations plus the per-mode overrides, read from the persisted config NOW. */
2713
+ delaySettings() {
2714
+ return {
2715
+ ...this.readDelays(),
2716
+ modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
2717
+ };
2718
+ }
2719
+ /**
2720
+ * What `mode` runs on, read at the transition that uses it — never cached. `null` (no armed
2721
+ * mode to look at) is the global values, every field `default`.
2722
+ */
2723
+ delaysFor(mode) {
2724
+ const settings = this.delaySettings();
2725
+ if (mode === null) return {
2726
+ exitDelaySec: settings.exitDelaySec,
2727
+ entryDelaySec: settings.entryDelaySec,
2728
+ triggeredDurationSec: settings.triggeredDurationSec,
2729
+ source: {
2730
+ exitDelaySec: "default",
2731
+ entryDelaySec: "default",
2732
+ triggeredDurationSec: "default"
2733
+ }
2734
+ };
2735
+ return resolveModeDelays(settings, mode);
2736
+ }
2737
+ /** The three global durations, read from the persisted config. */
2484
2738
  readDelays() {
2485
2739
  return {
2486
2740
  exitDelaySec: this.config.get("exitDelaySec"),
@@ -8345,6 +8599,79 @@ function sameWord(a, b) {
8345
8599
  return a !== void 0 && a.trim().toLowerCase() === b.trim().toLowerCase();
8346
8600
  }
8347
8601
  //#endregion
8602
+ //#region src/notification-center/alarm/alarm-sensors.ts
8603
+ /**
8604
+ * One id's state as the table shows it — spec §3.4's hub table, nothing else.
8605
+ *
8606
+ * Built ON `classifyCoveredOpening`, the arm check's classifier: what is
8607
+ * unknown, open or skipped is decided there, and this only splits a skip into
8608
+ * `closed` / `not-opening` for the table. A later change to the classifier
8609
+ * therefore moves the table with the arm.
8610
+ */
8611
+ function alarmSensorStateOf(deviceId, opening, online) {
8612
+ const verdict = classifyCoveredOpening(opening);
8613
+ switch (verdict.kind) {
8614
+ case "unknown": return {
8615
+ deviceId,
8616
+ reading: "unknown",
8617
+ state: null,
8618
+ unknownWhy: verdict.why,
8619
+ online
8620
+ };
8621
+ case "open": return {
8622
+ deviceId,
8623
+ reading: "open",
8624
+ state: verdict.state,
8625
+ unknownWhy: null,
8626
+ online
8627
+ };
8628
+ case "skip": return opening?.kind === "reading" ? {
8629
+ deviceId,
8630
+ reading: "closed",
8631
+ state: opening.state,
8632
+ unknownWhy: null,
8633
+ online
8634
+ } : {
8635
+ deviceId,
8636
+ reading: "not-opening",
8637
+ state: null,
8638
+ unknownWhy: null,
8639
+ online
8640
+ };
8641
+ }
8642
+ }
8643
+ /** The asked ids, de-duplicated and ascending. */
8644
+ function alarmSensorReadIds(ids) {
8645
+ return [...new Set(ids)].toSorted((a, b) => a - b);
8646
+ }
8647
+ /**
8648
+ * The sensors read's health, logged on CHANGE only (the call is polled every
8649
+ * 5-15 s by every open tab). "Wholesale failure" = ids were asked and not one
8650
+ * was answered (the device manager is unreachable): WARN once when it starts,
8651
+ * ONE INFO when answers return. A partial answer is not a health change — those
8652
+ * ids already leave their own debug line and show as `unknown`.
8653
+ */
8654
+ var NcAlarmSensorsReadLog = class {
8655
+ failing = false;
8656
+ next(asked, answered) {
8657
+ const wholesale = asked > 0 && answered === 0;
8658
+ if (wholesale === this.failing) return null;
8659
+ this.failing = wholesale;
8660
+ return wholesale ? {
8661
+ level: "warn",
8662
+ message: "alarm sensors read: no asked device answered — every row is unknown",
8663
+ meta: { asked }
8664
+ } : {
8665
+ level: "info",
8666
+ message: "alarm sensors read: devices answer again",
8667
+ meta: {
8668
+ asked,
8669
+ answered
8670
+ }
8671
+ };
8672
+ }
8673
+ };
8674
+ //#endregion
8348
8675
  //#region src/notification-center/alarm/alarm-live.ts
8349
8676
  /** Every covered id across all modes, ascending, once — what the one read asks for. */
8350
8677
  function alarmLiveReadIds(covered) {
@@ -11329,6 +11656,18 @@ function streamHealthAccepted(category, reason) {
11329
11656
  function discardedSystemEvent(event, lookup) {
11330
11657
  const accessory = discardedAccessoryLiveness(event, lookup);
11331
11658
  if (accessory !== null) return accessory;
11659
+ if (event.category === "worker.online" || event.category === "worker.offline") {
11660
+ const workerId = stringField(event.data, "workerId");
11661
+ return {
11662
+ category: event.category,
11663
+ reason: "a runner is not a node",
11664
+ tags: {},
11665
+ meta: {
11666
+ reason: "a runner is not a node",
11667
+ ...workerId !== void 0 ? { workerId } : {}
11668
+ }
11669
+ };
11670
+ }
11332
11671
  if (event.category !== "stream.online" && event.category !== "stream.offline") return null;
11333
11672
  const data = event.data;
11334
11673
  const reason = stringField(data, "reason");
@@ -11655,12 +11994,11 @@ function incomingFromSystemEvent(event, lookup) {
11655
11994
  deviceId
11656
11995
  });
11657
11996
  }
11658
- case "agent.online":
11659
- case "agent.offline":
11660
11997
  case "worker.online":
11661
- case "worker.offline": {
11662
- const agent = event.category.startsWith("agent.");
11663
- const nodeId = stringField(data, agent ? "agentId" : "workerId");
11998
+ case "worker.offline": return null;
11999
+ case "agent.online":
12000
+ case "agent.offline": {
12001
+ const nodeId = stringField(data, "agentId");
11664
12002
  if (nodeId === void 0) return null;
11665
12003
  const kind = event.category.endsWith(".online") ? "node-online" : "node-offline";
11666
12004
  return systemSubject({
@@ -11668,7 +12006,7 @@ function incomingFromSystemEvent(event, lookup) {
11668
12006
  kind,
11669
12007
  subject: `node:${nodeId}`,
11670
12008
  titleKey: `system.${kind}.title`,
11671
- bodyKey: agent ? `system.${kind}.body.agent` : `system.${kind}.body.worker`,
12009
+ bodyKey: `system.${kind}.body.agent`,
11672
12010
  textParams: { nodeId },
11673
12011
  nodeId
11674
12012
  });
@@ -13285,10 +13623,8 @@ var en_default = {
13285
13623
  "system.stream-offline.body.profiled": "{{profile}} stream for camera {{deviceName}} is offline",
13286
13624
  "system.node-online.title": "Node online",
13287
13625
  "system.node-online.body.agent": "Agent {{nodeId}} is online",
13288
- "system.node-online.body.worker": "Worker {{nodeId}} is online",
13289
13626
  "system.node-offline.title": "Node offline",
13290
13627
  "system.node-offline.body.agent": "Agent {{nodeId}} is offline",
13291
- "system.node-offline.body.worker": "Worker {{nodeId}} is offline",
13292
13628
  "system.node-inference-unavailable.title": "Node has no usable inference device",
13293
13629
  "system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
13294
13630
  "system.addon-crash-loop.title": "Addon stopped after repeated crashes",
@@ -13615,10 +13951,8 @@ var it_default = {
13615
13951
  "system.stream-offline.body.profiled": "Lo stream {{profile}} della camera {{deviceName}} è offline",
13616
13952
  "system.node-online.title": "Nodo online",
13617
13953
  "system.node-online.body.agent": "L'agent {{nodeId}} è online",
13618
- "system.node-online.body.worker": "Il worker {{nodeId}} è online",
13619
13954
  "system.node-offline.title": "Nodo offline",
13620
13955
  "system.node-offline.body.agent": "L'agent {{nodeId}} è offline",
13621
- "system.node-offline.body.worker": "Il worker {{nodeId}} è offline",
13622
13956
  "system.node-inference-unavailable.title": "Nodo senza dispositivi di inferenza utilizzabili",
13623
13957
  "system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
13624
13958
  "system.addon-crash-loop.title": "Addon fermato dopo ripetuti crash",
@@ -17004,7 +17338,8 @@ var ncActions = defineCustomActions({
17004
17338
  * `getStatus`; this one opens up once it filters to the caller's grants.
17005
17339
  * The config pair is the admin's, as `getAlarmConfig` / `setAlarmConfig` are
17006
17340
  * on the cap: it carries the delays, the recipients and the non-blocking
17007
- * lists.
17341
+ * lists. `getAlarmSensors` is the admin's too: it names which of the asked
17342
+ * devices are open.
17008
17343
  */
17009
17344
  "nc.getAlarmStatus": customAction(object({}), NcAlarmStatusSchema, {
17010
17345
  auth: "admin",
@@ -17045,6 +17380,10 @@ var ncActions = defineCustomActions({
17045
17380
  auth: "admin",
17046
17381
  caller: "required"
17047
17382
  }),
17383
+ "nc.getAlarmSensors": customAction(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, {
17384
+ auth: "admin",
17385
+ caller: "required"
17386
+ }),
17048
17387
  "nc.setAlarmConfig": customAction(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
17049
17388
  kind: "mutation",
17050
17389
  auth: "admin",
@@ -17837,6 +18176,10 @@ function makeNcActionHandlers(deps) {
17837
18176
  requireAdmin(caller);
17838
18177
  return requireAlarm("nc.getAlarmConfig").getConfig();
17839
18178
  },
18179
+ "nc.getAlarmSensors": async (input, caller) => {
18180
+ requireAdmin(caller);
18181
+ return requireAlarm("nc.getAlarmSensors").getSensors(input);
18182
+ },
17840
18183
  "nc.setAlarmConfig": async (input, caller) => {
17841
18184
  requireAdmin(caller);
17842
18185
  return requireAlarm("nc.setAlarmConfig").setConfig(input.patch);
@@ -32671,7 +33014,8 @@ function consumablesSliceOf(event) {
32671
33014
  var NC_ALARM_SETTINGS_FALLBACK = {
32672
33015
  ...DEFAULT_DELAYS,
32673
33016
  announceTargets: [],
32674
- nonBlocking: []
33017
+ nonBlocking: [],
33018
+ modeDelays: []
32675
33019
  };
32676
33020
  /**
32677
33021
  * How often the "matched NO rule" report may fire per device. Long enough that
@@ -33053,6 +33397,7 @@ var NotificationCenter = class NotificationCenter {
33053
33397
  alarmPanel = null;
33054
33398
  /** The live view's read, logged on change only — see `alarm-live-read-log.ts`. */
33055
33399
  alarmLiveReadLog = new NcAlarmLiveReadLog();
33400
+ alarmSensorsReadLog = new NcAlarmSensorsReadLog();
33056
33401
  /**
33057
33402
  * The words the panel answers the `deviceState` gate with — its state, plus
33058
33403
  * the armed mode a `pending`/`triggered` belongs to (D630). Refreshed on
@@ -35371,6 +35716,7 @@ var NotificationCenter = class NotificationCenter {
35371
35716
  return { success: true };
35372
35717
  },
35373
35718
  getAlarmConfig: async () => this.alarmConfig(),
35719
+ getAlarmSensors: async (input) => this.alarmSensors(input),
35374
35720
  setAlarmConfig: async ({ patch }) => this.setAlarmConfig(patch)
35375
35721
  };
35376
35722
  }
@@ -35598,6 +35944,7 @@ var NotificationCenter = class NotificationCenter {
35598
35944
  });
35599
35945
  },
35600
35946
  getConfig: () => this.alarmConfig(),
35947
+ getSensors: (input) => this.alarmSensors(input),
35601
35948
  setConfig: (patch) => this.setAlarmConfig(patch)
35602
35949
  };
35603
35950
  }
@@ -35611,7 +35958,8 @@ var NotificationCenter = class NotificationCenter {
35611
35958
  exitDelaySec: settings.exitDelaySec,
35612
35959
  entryDelaySec: settings.entryDelaySec,
35613
35960
  triggeredDurationSec: settings.triggeredDurationSec,
35614
- announceTargets: settings.announceTargets.length
35961
+ announceTargets: settings.announceTargets.length,
35962
+ modeDelays: settings.modeDelays.length
35615
35963
  }
35616
35964
  });
35617
35965
  return this.alarmConfig();
@@ -35667,6 +36015,40 @@ var NotificationCenter = class NotificationCenter {
35667
36015
  name: this.deviceNameFromDirectory()
35668
36016
  });
35669
36017
  }
36018
+ /**
36019
+ * The opening states of the asked ids, for the alarm editors' sensor table.
36020
+ *
36021
+ * A separate read from `getAlarmConfig` on purpose: that one is polled every
36022
+ * 2–15 s and the household set needs a whole-fleet bindings pass, while the
36023
+ * client already holds the ids. Cost: one `loadRuntimeState` RPC per id, 8 in
36024
+ * flight (`readAlarmOpenings`); the client sends at most `NC_ALARM_SENSORS_MAX`
36025
+ * ids per call and polls every 15 s, every 5 s only while a countdown or the
36026
+ * siren runs. No log line per call (it is polled): one WARN/INFO per change of
36027
+ * wholesale read health (`NcAlarmSensorsReadLog`); a per-id read failure keeps
36028
+ * `readAlarmOpenings`' own debug line, and shows as `unknown` (D49).
36029
+ *
36030
+ * An id with no persisted device (removed) or whose provider never wrote an
36031
+ * opening slice reads `not-opening`, exactly as the arm check sees it — NOT
36032
+ * "verified not an opening". The client shows "Removed" from its fleet read.
36033
+ */
36034
+ async alarmSensors(input) {
36035
+ const read = this.deps.readDeviceOpenings;
36036
+ if (read === void 0) throw new Error("the opening reader is not wired on this node");
36037
+ const ids = alarmSensorReadIds(input.deviceIds);
36038
+ const openings = await read(ids);
36039
+ const line = this.alarmSensorsReadLog.next(ids.length, openings.size);
36040
+ if (line !== null) {
36041
+ const panelId = this.alarmPanel?.deviceId;
36042
+ this.logger[line.level](line.message, {
36043
+ ...panelId === void 0 ? {} : { tags: { deviceId: panelId } },
36044
+ meta: line.meta
36045
+ });
36046
+ }
36047
+ return {
36048
+ sensors: ids.map((id) => alarmSensorStateOf(id, openings.get(id), this.deviceDirectory.get(id)?.online ?? null)),
36049
+ readAt: Date.now()
36050
+ };
36051
+ }
35670
36052
  /** Settings + the DERIVED coverage + the live panel. Read together — see the cap doc. */
35671
36053
  async alarmConfig() {
35672
36054
  const panel = this.alarmPanel;
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-KF5Fk8sD.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BocOElW2.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.310",
3
+ "version": "1.2.312",
4
4
  "description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",