@camstack/addon-post-analysis 1.2.309 → 1.2.311
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.
- package/dist/{clip-model-registry-CrBn6Qvt.mjs → clip-model-registry-DPqDIZrG.mjs} +409 -200
- package/dist/{clip-model-registry-B3M4ZI17.js → clip-model-registry-X457TLXn.js} +456 -199
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Bkf0-pki.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D0JVXXRd.mjs} +2 -2
- 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
- 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
- package/dist/pipeline-analytics/{hostInit-Dj0aaMhv.mjs → hostInit-Da_2XVg_.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +411 -34
- package/dist/pipeline-analytics/index.mjs +411 -34
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DWg2WU0a.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
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-DPqDIZrG.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
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
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.
|
|
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
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
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 (
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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) {
|
|
@@ -17004,7 +17331,8 @@ var ncActions = defineCustomActions({
|
|
|
17004
17331
|
* `getStatus`; this one opens up once it filters to the caller's grants.
|
|
17005
17332
|
* The config pair is the admin's, as `getAlarmConfig` / `setAlarmConfig` are
|
|
17006
17333
|
* on the cap: it carries the delays, the recipients and the non-blocking
|
|
17007
|
-
* lists.
|
|
17334
|
+
* lists. `getAlarmSensors` is the admin's too: it names which of the asked
|
|
17335
|
+
* devices are open.
|
|
17008
17336
|
*/
|
|
17009
17337
|
"nc.getAlarmStatus": customAction(object({}), NcAlarmStatusSchema, {
|
|
17010
17338
|
auth: "admin",
|
|
@@ -17045,6 +17373,10 @@ var ncActions = defineCustomActions({
|
|
|
17045
17373
|
auth: "admin",
|
|
17046
17374
|
caller: "required"
|
|
17047
17375
|
}),
|
|
17376
|
+
"nc.getAlarmSensors": customAction(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, {
|
|
17377
|
+
auth: "admin",
|
|
17378
|
+
caller: "required"
|
|
17379
|
+
}),
|
|
17048
17380
|
"nc.setAlarmConfig": customAction(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
|
|
17049
17381
|
kind: "mutation",
|
|
17050
17382
|
auth: "admin",
|
|
@@ -17837,6 +18169,10 @@ function makeNcActionHandlers(deps) {
|
|
|
17837
18169
|
requireAdmin(caller);
|
|
17838
18170
|
return requireAlarm("nc.getAlarmConfig").getConfig();
|
|
17839
18171
|
},
|
|
18172
|
+
"nc.getAlarmSensors": async (input, caller) => {
|
|
18173
|
+
requireAdmin(caller);
|
|
18174
|
+
return requireAlarm("nc.getAlarmSensors").getSensors(input);
|
|
18175
|
+
},
|
|
17840
18176
|
"nc.setAlarmConfig": async (input, caller) => {
|
|
17841
18177
|
requireAdmin(caller);
|
|
17842
18178
|
return requireAlarm("nc.setAlarmConfig").setConfig(input.patch);
|
|
@@ -32671,7 +33007,8 @@ function consumablesSliceOf(event) {
|
|
|
32671
33007
|
var NC_ALARM_SETTINGS_FALLBACK = {
|
|
32672
33008
|
...DEFAULT_DELAYS,
|
|
32673
33009
|
announceTargets: [],
|
|
32674
|
-
nonBlocking: []
|
|
33010
|
+
nonBlocking: [],
|
|
33011
|
+
modeDelays: []
|
|
32675
33012
|
};
|
|
32676
33013
|
/**
|
|
32677
33014
|
* How often the "matched NO rule" report may fire per device. Long enough that
|
|
@@ -33053,6 +33390,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33053
33390
|
alarmPanel = null;
|
|
33054
33391
|
/** The live view's read, logged on change only — see `alarm-live-read-log.ts`. */
|
|
33055
33392
|
alarmLiveReadLog = new NcAlarmLiveReadLog();
|
|
33393
|
+
alarmSensorsReadLog = new NcAlarmSensorsReadLog();
|
|
33056
33394
|
/**
|
|
33057
33395
|
* The words the panel answers the `deviceState` gate with — its state, plus
|
|
33058
33396
|
* the armed mode a `pending`/`triggered` belongs to (D630). Refreshed on
|
|
@@ -35371,6 +35709,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35371
35709
|
return { success: true };
|
|
35372
35710
|
},
|
|
35373
35711
|
getAlarmConfig: async () => this.alarmConfig(),
|
|
35712
|
+
getAlarmSensors: async (input) => this.alarmSensors(input),
|
|
35374
35713
|
setAlarmConfig: async ({ patch }) => this.setAlarmConfig(patch)
|
|
35375
35714
|
};
|
|
35376
35715
|
}
|
|
@@ -35598,6 +35937,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35598
35937
|
});
|
|
35599
35938
|
},
|
|
35600
35939
|
getConfig: () => this.alarmConfig(),
|
|
35940
|
+
getSensors: (input) => this.alarmSensors(input),
|
|
35601
35941
|
setConfig: (patch) => this.setAlarmConfig(patch)
|
|
35602
35942
|
};
|
|
35603
35943
|
}
|
|
@@ -35611,7 +35951,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35611
35951
|
exitDelaySec: settings.exitDelaySec,
|
|
35612
35952
|
entryDelaySec: settings.entryDelaySec,
|
|
35613
35953
|
triggeredDurationSec: settings.triggeredDurationSec,
|
|
35614
|
-
announceTargets: settings.announceTargets.length
|
|
35954
|
+
announceTargets: settings.announceTargets.length,
|
|
35955
|
+
modeDelays: settings.modeDelays.length
|
|
35615
35956
|
}
|
|
35616
35957
|
});
|
|
35617
35958
|
return this.alarmConfig();
|
|
@@ -35667,6 +36008,40 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35667
36008
|
name: this.deviceNameFromDirectory()
|
|
35668
36009
|
});
|
|
35669
36010
|
}
|
|
36011
|
+
/**
|
|
36012
|
+
* The opening states of the asked ids, for the alarm editors' sensor table.
|
|
36013
|
+
*
|
|
36014
|
+
* A separate read from `getAlarmConfig` on purpose: that one is polled every
|
|
36015
|
+
* 2–15 s and the household set needs a whole-fleet bindings pass, while the
|
|
36016
|
+
* client already holds the ids. Cost: one `loadRuntimeState` RPC per id, 8 in
|
|
36017
|
+
* flight (`readAlarmOpenings`); the client sends at most `NC_ALARM_SENSORS_MAX`
|
|
36018
|
+
* ids per call and polls every 15 s, every 5 s only while a countdown or the
|
|
36019
|
+
* siren runs. No log line per call (it is polled): one WARN/INFO per change of
|
|
36020
|
+
* wholesale read health (`NcAlarmSensorsReadLog`); a per-id read failure keeps
|
|
36021
|
+
* `readAlarmOpenings`' own debug line, and shows as `unknown` (D49).
|
|
36022
|
+
*
|
|
36023
|
+
* An id with no persisted device (removed) or whose provider never wrote an
|
|
36024
|
+
* opening slice reads `not-opening`, exactly as the arm check sees it — NOT
|
|
36025
|
+
* "verified not an opening". The client shows "Removed" from its fleet read.
|
|
36026
|
+
*/
|
|
36027
|
+
async alarmSensors(input) {
|
|
36028
|
+
const read = this.deps.readDeviceOpenings;
|
|
36029
|
+
if (read === void 0) throw new Error("the opening reader is not wired on this node");
|
|
36030
|
+
const ids = alarmSensorReadIds(input.deviceIds);
|
|
36031
|
+
const openings = await read(ids);
|
|
36032
|
+
const line = this.alarmSensorsReadLog.next(ids.length, openings.size);
|
|
36033
|
+
if (line !== null) {
|
|
36034
|
+
const panelId = this.alarmPanel?.deviceId;
|
|
36035
|
+
this.logger[line.level](line.message, {
|
|
36036
|
+
...panelId === void 0 ? {} : { tags: { deviceId: panelId } },
|
|
36037
|
+
meta: line.meta
|
|
36038
|
+
});
|
|
36039
|
+
}
|
|
36040
|
+
return {
|
|
36041
|
+
sensors: ids.map((id) => alarmSensorStateOf(id, openings.get(id), this.deviceDirectory.get(id)?.online ?? null)),
|
|
36042
|
+
readAt: Date.now()
|
|
36043
|
+
};
|
|
36044
|
+
}
|
|
35670
36045
|
/** Settings + the DERIVED coverage + the live panel. Read together — see the cap doc. */
|
|
35671
36046
|
async alarmConfig() {
|
|
35672
36047
|
const panel = this.alarmPanel;
|
|
@@ -92144,8 +92519,8 @@ function buildAudioLabels(input) {
|
|
|
92144
92519
|
* Mapped sensor/control caps that a CAMERA must NOT contribute for ITSELF.
|
|
92145
92520
|
*
|
|
92146
92521
|
* A sensor cap bound on a camera makes the camera both the source and the
|
|
92147
|
-
* subject — a doorbell
|
|
92148
|
-
* camera-side button. Those have no other route into the per-camera event
|
|
92522
|
+
* subject — a composed doorbell on camera 615 (D671), a native Reolink
|
|
92523
|
+
* doorbell, a camera-side button. Those have no other route into the per-camera event
|
|
92149
92524
|
* substrate, so the camera includes itself.
|
|
92150
92525
|
*
|
|
92151
92526
|
* `motion` is the exception, and today the ONLY one: camera motion already
|
|
@@ -92414,8 +92789,8 @@ var LinkedCamerasCache = class {
|
|
|
92414
92789
|
* THE camera-set derivation for a sensor-cap state change — one function, so
|
|
92415
92790
|
* every consumer of a device-event attributes it to the same cameras.
|
|
92416
92791
|
*
|
|
92417
|
-
* It is shared on purpose. The self-camera half (a doorbell
|
|
92418
|
-
*
|
|
92792
|
+
* It is shared on purpose. The self-camera half (a doorbell bound on camera
|
|
92793
|
+
* 615 makes 615 both source and subject) was added to the history-row
|
|
92419
92794
|
* ingest on 2026-08-07 and NOT to the synthetic-track projection beside it,
|
|
92420
92795
|
* which kept resolving `camerasFor` alone. The result: camera 615's real
|
|
92421
92796
|
* doorbell press on 2026-08-08 06:59Z produced a `SensorEvent` row and no
|
|
@@ -92503,8 +92878,8 @@ function isSliceRecord(slice) {
|
|
|
92503
92878
|
/**
|
|
92504
92879
|
* One `DeviceStateChanged` → N history rows, one per camera the event belongs
|
|
92505
92880
|
* to: the emitting device's LINKED cameras, plus the emitting device ITSELF
|
|
92506
|
-
* when it is a camera carrying the sensor cap (a doorbell
|
|
92507
|
-
*
|
|
92881
|
+
* when it is a camera carrying the sensor cap (a doorbell bound on camera
|
|
92882
|
+
* 615 — see `SELF_CAMERA_EXCLUDED_CAPS` for the one cap that is not
|
|
92508
92883
|
* self-ingested). The EVENT_KIND_BY_CAP gate exits first so non-sensor cap
|
|
92509
92884
|
* churn costs one map lookup. Returns the number of rows inserted (0 when
|
|
92510
92885
|
* unmapped, or when no camera takes a row for the device — a non-camera
|
|
@@ -93596,9 +93971,10 @@ var MotionEventSnapshotCapturer = class {
|
|
|
93596
93971
|
*
|
|
93597
93972
|
* **What this fixes.** The projection used to resolve its cameras with
|
|
93598
93973
|
* `camerasFor` alone — LINKED cameras only — while the history-row ingest
|
|
93599
|
-
* beside it had gained the self-camera half on 2026-08-07. A doorbell
|
|
93600
|
-
*
|
|
93601
|
-
* slice onto camera 615
|
|
93974
|
+
* beside it had gained the self-camera half on 2026-08-07. A doorbell can be
|
|
93975
|
+
* served on the CAMERA itself (a composed doorbell, D671, writes the `doorbell`
|
|
93976
|
+
* slice onto camera 615; Reolink registers it at camera level), so
|
|
93977
|
+
* `camerasFor(615)` answered `[]` and the
|
|
93602
93978
|
* projection returned on a `debug` line nobody runs at. Camera 615's real
|
|
93603
93979
|
* press at 2026-08-08 06:59Z therefore produced a `SensorEvent` row, a HomeKit
|
|
93604
93980
|
* ring, an Alexa post and a matched notification rule — and no track. Both
|
|
@@ -93746,16 +94122,17 @@ async function projectSensorMarkers(deps, data, timestamp) {
|
|
|
93746
94122
|
* ## Why the predicate is imported and not written here
|
|
93747
94123
|
*
|
|
93748
94124
|
* `evaluateSensorEdge` (`@camstack/types`) is already THE edge predicate: the
|
|
93749
|
-
*
|
|
93750
|
-
* the
|
|
94125
|
+
* recorder's sensor trigger feeds it (`addon-pipeline` `recorder/sensor-trigger.ts`)
|
|
94126
|
+
* and so does the orchestrator's camera-sensors bridge
|
|
94127
|
+
* (`addon-pipeline-orchestrator` `camera-sensors/source.ts`).
|
|
93751
94128
|
* Post-analysis was the third consumer of the same bus event and the only one
|
|
93752
94129
|
* that did not. A second predicate here would diverge on the first cap added —
|
|
93753
94130
|
* the exact failure [D62](../../../../../docs/decisions/adr-0062.md) names.
|
|
93754
94131
|
*
|
|
93755
94132
|
* ## Where it deliberately differs from the other two
|
|
93756
94133
|
*
|
|
93757
|
-
* The
|
|
93758
|
-
* is not a doorbell: a door CLOSING is an event an operator wants to see, and
|
|
94134
|
+
* The recorder and the camera-sensors bridge want the RISING edge only. A
|
|
94135
|
+
* timeline marker is not a doorbell: a door CLOSING is an event an operator wants to see, and
|
|
93759
94136
|
* the taxonomy has one `contact` kind for both directions. So this gate accepts
|
|
93760
94137
|
* `edge: 'rising'` **and** the `falling-edge` verdict, and rejects only the
|
|
93761
94138
|
* verdicts that mean "nothing moved" — which is precisely the 30.
|
|
@@ -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-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D0JVXXRd.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|